@junojs/core
v0.4.9
Published
JunoJS Core Engine
Maintainers
Readme
🪐 JunoJS Core
⚡ Features
- ⚛️ Native Web Components: Future-proof, standard-based components that work natively in the browser.
- 🏝️ Island & Performance Architecture: Zero-JS server components (
"use server") alongside highly-interactive client islands ("use client"). - 🕹️ Vibe Coding: Clean, expressive HTML-First templates with powerful structural directives and zero runtime tension.
- ⚡ Signal Reactivity: Fine-grained, auto-tracking reactive primitives —
signal(),computed(), andeffect(). - 🛠️ Decorator-driven TypeScript: Build features intuitively using
@Component,@State, and Dependency Injection. - 🧩 Zero Virtual DOM: Hyper-fast, targeted reconciliation directly on the Light DOM for maximum speed.
- 🛣️ Slot-Based Routing: Robust client-side navigation with the native
<router-outlet>and automatic parameter synchronization. - 🚀 Built-in SSG & SSR: Automatic routing and static generation out of the box with the Juno CLI.
- 🤖 AI-Native Context: Optimized for modern LLMs with dedicated system prompts for seamless code generation.
⚖️ How it Compares
JunoJS vs. Angular
Angular is a powerful enterprise framework, but it comes with significant boilerplate and a proprietary runtime (Zone.js). JunoJS provides a similar decorator-driven experience but stays native to the browser.
| Feature | Angular | JunoJS | | :--- | :--- | :--- | | Components | Proprietary Template Engine | Native Web Components (Standard) | | Reactivity | Zone.js (Dirty Checking) | Proxy-based / Signals (Fine-grained) | | Bundle Size | ~50KB+ (Minimum) | ~8KB (Core Runtime) | | Learning Curve | Steep (Modules, RxJS, etc.) | Low (HTML, CSS, TS Decorators) |
🔍 Code Comparison
@Component({
selector: 'app-counter',
template: `
<button (click)="inc()">
{{count}}
</button>
`
})
export class Counter {
count = 0;
inc() { this.count++; }
}@Component({
tag: 'juno-counter',
template: `
<button @click="count++">
{count}
</button>
`
})
export class Counter {
@State() count = 0;
}JunoJS vs. Next.js
Next.js is the gold standard for React-based SSR. JunoJS takes the "Island" concept from Astro but integrates it into a unified, decorator-based framework that uses native components instead of a Virtual DOM.
| Feature | Next.js | JunoJS |
| :--- | :--- | :--- |
| Rendering | Full Page Hydration | Hybrid Islands (Selective Hydration) |
| DOM | Virtual DOM (React) | Native Light DOM (Zero VDOM) |
| Routing | File-system based | Slot-based + Declarative (<router-outlet>) |
| Static Build | Complex "use client" boundaries | Seamless "use server" directives |
🔍 Code Comparison
Next.js (Server/Client split):
// page.tsx (Server Component)
export default function Page() {
return <main><Counter /></main>;
}
// counter.tsx ("use client")
"use client";
export function Counter() {
const [count, setCount] = useState(0);
return <button onClick={() => setCount(c => c + 1)}>{count}</button>;
}JunoJS (Unified approach):
"use server";
@Component({
tag: "home-page",
static: true,
template: `
<main>
<h1>My Page</h1>
<juno-counter></juno-counter> <!-- Interactive island -->
</main>
`
})
export class HomePage {}🚀 Performance Benchmark
JunoJS is built for speed. By ditching the Virtual DOM and embracing Native Web Components, we achieve industry-leading performance metrics.
| Metric | React (Next.js) | Angular | JunoJS | | :--- | :--- | :--- | :--- | | Runtime Size (Gzipped) | ~45KB | ~60KB | ~8KB | | Reconciliation | Virtual DOM Diffing | Zone.js Dirty Checking | Targeted Proxy Updates | | DOM Interaction | Synthetic Events | Wrapper Elements | Native Light DOM | | Hydration Strategy | Full Page | Incremental | Selective (Islands) | | Startup TTI | Moderate | Slow | Blazing Fast |
💡 Why JunoJS is faster:
- Direct DOM Updates: When a
@Statevariable changes, JunoJS updates exactly the affected DOM node. No tree diffing, no unnecessary re-renders of parent/child components. - 0KB Static Pages: Pages composed purely of
"use server"components ship zero JavaScript to the browser. - Native Speed: By using the browser's built-in
CustomElementRegistry, JunoJS leverages the underlying optimizations of modern JS engines (V8, JavaScriptCore).
📦 Quick Start
Get a high-performance app running in seconds:
npx juno new my-app --tailwind --ui
cd my-app && npm run dev🕹️ The "Vibe"
JunoJS components feel like standard HTML, but with superpowers.
"use client";
import { Component, State, Action, bootstrap } from "@junojs/core";
@Component({
tag: "juno-counter",
template: `
<div class="p-6 bg-zinc-900 rounded-2xl border border-white/10">
<h1 class="text-3xl font-bold bg-gradient-to-r from-emerald-400 to-cyan-400 bg-clip-text text-transparent">
Count: {count}
</h1>
<button @click="count++" class="mt-4 px-6 py-2 bg-emerald-500 hover:bg-emerald-400 transition-colors rounded-xl text-black font-medium">
Increment
</button>
</div>
`,
})
export class CounterComponent {
@State() count = 0;
}
bootstrap(CounterComponent);
### 🔍 Key Concepts
- **`@Component`**: Registers a standard Custom Element.
- **`@State()`**: A Proxy-based reactive variable. Mutating it (e.g., `this.count++`) triggers a microtask-batched DOM update.
- **`@click`**: Declarative event binding. Support for modifiers like `@click.stop` or `@keyup.enter` is built-in.
- **Interpolation**: `{count}` is live-bound. No manual DOM manipulation needed.🏝️ Hybrid Rendering (Islands)
JunoJS allows you to mix static server-side content with interactive client islands seamlessly using the "use server" and "use client" directives.
"use server": Zero-JS. Executed at build time. Ideal for layout and data fetching."use client": Interactive. Hydrated on the client with full reactivity.
"use server";
@Component({
tag: "home-page",
static: true,
route: "/",
template: `
<main>
<h1>Static Header</h1>
<ui-client-island></ui-client-island> <!-- Only this part loads JS -->
</main>
`
})
export class HomePage {}
### 📂 How it works
1. **Build Time**: The Juno CLI discovers `static: true` and runs a headless render.
2. **Hydration**: On the client, `"use client"` components are resurrected with full state, while `"use server"` parts remain static HTML.
3. **Zero-JS**: If a page only contains server components, **0KB of JunoJS** is shipped to the browser.🛣️ Slot-Based Routing
JunoJS v0.4.0 introduces a robust, component-first routing system using the <router-outlet>. This replaces manual conditional rendering with a declarative slot-based architecture.
1. Register Routes
Register your routes in your application root using the component property (which refers to the component's tag name).
// main.ts
onInit() {
const router = RouterService.instance;
router.register([
{ path: '/', component: 'home-page', title: 'Home' },
{ path: '/profile/:id', component: 'profile-page', title: 'User Profile' }
]);
}2. Define the Outlet
Place the <router-outlet></router-outlet> anywhere in your template. It will automatically listen to route changes and inject the matched component.
<!-- app.html -->
<nav>
<button @click="Router.navigate('/')">Home</button>
<button @click="Router.navigate('/profile/123')">Profile</button>
</nav>
<main>
<router-outlet></router-outlet> <!-- Dynamic components are injected here -->
</main>3. Parameters Synchronization
Route parameters (like :id) are automatically passed to your component as both Properties (camelCase) and Attributes (kebab-case).
@Component({ tag: 'profile-page' })
export class ProfilePage {
@Input() id!: string; // Automatically populated from the route
}4. Programmatic Navigation
The cleanest way to navigate programmatically is to inject the RouterService using the @Use decorator. This automatically resolves to the framework's singleton instance.
import { Component, Use, RouterService } from "@junojs/core";
@Component({ tag: 'user-list' })
export class UserList {
@Use(RouterService) router!: RouterService;
goToUser(id: string) {
this.router.navigate(`/profile/${id}`); // Adds to history
}
forceGoToUser(id: string) {
this.router.replace(`/profile/${id}`); // Replaces current history entry
}
goBack() {
this.router.back();
}
}5. Route Guards
Protect routes using the canActivate() lifecycle hook or by passing guards in the route configuration.
export class AdminPage {
async canActivate() {
return AuthService.instance.isAdmin(); // Returns boolean or new path string
}
}🧬 The Reactivity Engine
JunoJS offers two ways to handle state. Choose the one that fits your architectural needs.
Quick Comparison
| Feature | @State() Decorator | signal() Primitive |
| :--- | :--- | :--- |
| Best For | Component-local UI state | Shared logic, Services, Global state |
| Mechanism | Proxy-based (Class properties) | Atom-based (Functional) |
| Syntax | this.count++ | count.value++ |
| Tracking | Manual property binding | Automatic via .value access |
🟢 Local Reactivity (@State)
Perfect for internal component variables. Simple, class-based, and intuitive.
@State() name = "Juno";
// Mutate directly: this.name = "New Name";🔵 Global Reactivity (Signals)
The powerhouse for services and cross-component communication.
Signals are fine-grained, auto-tracking reactive primitives.
1. Writable Signals
const count = signal(0);
count.value = 5; // Triggers subscribers2. Computed Signals (Derivatives)
const total = computed(() => price.value * quantity.value);3. Effects & Batching
effect(() => console.log(count.value)); // Runs on change
batch(() => {
a.value = 10;
b.value = 20;
}); // Notifies once4. Service Pattern (Cart Example)
Signals are ideal for global state. Here's how a Service and Component interact:
// cart.service.ts
export class CartService {
items = signal<Product[]>([]);
itemCount = computed(() => this.items.value.length);
add(p: Product) {
this.items.update(prev => [...prev, p]); // Immutable update triggers reactivity
}
}
// cart-icon.component.ts
@Component({
tag: "cart-icon",
template: `<span>🛒 {cart.itemCount.value}</span>`
})
export class CartIcon {
@Use(CartService) cart!: CartService;
}🛠️ Core Decorators API
Power up your components with built-in decorators:
@Component: Register your Custom Web Component.@Input: Two-way bindable props from HTML attributes.@Use: Easy Dependency Injection for singleton services.@Event: Custom bubbling events withEventEmitter.@Validate: Built-in reactive validation logic.@HostElement: Access the nativeHTMLElementdirectly.
🧩 Putting it all together
A robust component using the full power of JunoJS decorators:
@Component({
tag: "user-profile",
template: `
<div class="card">
<img src="{avatar}" />
<h2>{username}</h2>
<button @click="notify">Follow</button>
<if condition="{isVerified}">
<span class="badge">Verified</span>
</if>
</div>
`
})
export class UserProfile {
@Input() username = "Guest";
@Input() avatar = "/default.png";
@Input() isVerified = false;
@Event("follow") followEvent!: EventEmitter<string>;
@Use(LoggerService) logger!: LoggerService;
notify() {
this.logger.log(`Following ${this.username}`);
this.followEvent.emit(this.username);
}
}🏗️ Template Logic
JunoJS uses an AST-based template parser for high-performance structural directives.
Conditional Rendering
<if condition="{isLoggedIn}">
<user-dashboard></user-dashboard>
<else>
<login-form></login-form>
</if>List Rendering
Always provide a key for optimal DOM reconciliation.
<ul>
<for each="{(item, i) in items}">
<li key="{item.id}">{i}: {item.title}</li>
</for>
</ul>🔄 Lifecycle Hooks
Control your component's behavior at every stage:
| Hook | Timing | Best For |
| :--- | :--- | :--- |
| canActivate() | Before mount (Async) | Route guards, Auth checks. |
| onInit() | Before first render (Async) | Data fetching, API calls. |
| onRender(el) | After DOM sync | 3rd party libs (Chart.js, D3). |
| onDestroy() | Unmount | Cleaning up timers/subscriptions. |
🤖 AI-Native Development
JunoJS is designed to be the best framework for AI-assisted coding. We provide specialized System Prompts to turn your LLM into a JunoJS expert.
[!TIP] Copy these files into your project root as
.cursorrulesor.clauderulesto give your AI full framework context.
🔌 Tooling & Ecosystem
- Juno CLI: Scaffolding, Dev Server (Vite), and SSG Builder.
- Juno UI: Tailwind-powered, interactive component library.
- VS Code Plugin: Syntax highlighting and template intellisense.
