Getting Started
Installation
Install nuclo via npm, yarn, or pnpm:
npm install nuclo
yarn add nuclo
pnpm add nuclo
That's it! No additional dependencies, no complicated setup.
TypeScript Setup
nuclo is built with TypeScript and provides full type definitions for all APIs. To get global type definitions for all tag builders and utilities, add one of the following:
Option 1: tsconfig.json
Add to your tsconfig.json
:
{
"compilerOptions": {
"types": ["nuclo/types"]
}
}
Option 2: Environment Declaration File
Create or modify your vite-env.d.ts
(or similar):
/// <reference types="nuclo/types" />
After this setup, you'll have full IntelliSense for all 140+ HTML and SVG tags, plus all nuclo utilities.
Your First App
Let's build a simple counter application to understand the basics:
import 'nuclo';
// State - just plain JavaScript variables
let count = 0;
// Create the UI
const app = div(
h1(() => `Count: ${count}`),
button('Increment', on('click', () => {
count++;
update();
})),
button('Decrement', on('click', () => {
count--;
update();
})),
button('Reset', on('click', () => {
count = 0;
update();
}))
);
// Render to the DOM
render(app, document.body);
Breaking it down:
- Import:
import 'nuclo'
registers all global functions - State: Just use regular variables. No hooks, no stores, no magic.
- Reactive UI: Use functions
() => ...
for dynamic content - Events: Use
on('event', handler)
to attach listeners - Updates: Call
update()
to refresh the UI after state changes - Render: Use
render(element, container)
to mount to the DOM
Understanding update()
The update()
function is the heart of nuclo's reactivity system. nuclo requires you to explicitly trigger updates, giving you full control over when the UI refreshes.
Why explicit updates?
Advantages:
- Performance: Batch multiple mutations into a single update cycle
- Control: You decide exactly when the UI should refresh
- Predictability: Zero surprise re-renders, explicit update flow
- Simplicity: No proxies, no dependency graphs, just objects and functions
- Debugging: Set a breakpoint at
update()
to trace all state changes
Batching Updates
One of the key benefits is the ability to batch multiple state changes:
// Good: Multiple changes, single update
function handleSubmit() {
user.name = 'Alice';
user.email = 'alice@example.com';
user.age = 30;
todos.push({ id: 1, text: 'New todo', done: false });
update(); // One update for all changes
}
// Works but inefficient: Update after each change
function handleSubmit() {
user.name = 'Alice';
update();
user.email = 'alice@example.com';
update();
user.age = 30;
update();
// 3 updates instead of 1!
}
Reactive Functions
Any zero-argument function becomes reactive. nuclo will call these functions during updates to get the current value.
Text Content
let name = 'World';
div(
'Hello, ',
() => name, // Reactive
'!'
);
// After name changes and update() is called,
// the div will show "Hello, Alice!"
Attributes
let isActive = false;
div({
className: () => isActive ? 'active' : 'inactive',
'aria-pressed': () => isActive,
disabled: () => !isActive
});
Styles
let opacity = 1;
div({
style: {
opacity: () => opacity,
display: () => opacity > 0 ? 'block' : 'none'
}
});
Complex Expressions
let items = [1, 2, 3];
div(
() => `You have ${items.length} item${items.length !== 1 ? 's' : ''}`
);
Event Handling
Use the on()
modifier to attach event listeners:
Basic Events
button('Click me',
on('click', () => {
console.log('Clicked!');
})
);
Multiple Events
input(
on('input', (e) => {
inputValue = e.target.value;
update();
}),
on('focus', () => {
isFocused = true;
update();
}),
on('blur', () => {
isFocused = false;
update();
})
);
Event Options
div(
on('scroll', handleScroll, { passive: true }),
on('click', handleClick, { capture: true, once: true })
);
Keyboard Events
input(
on('keydown', (e) => {
if (e.key === 'Enter') {
handleSubmit();
} else if (e.key === 'Escape') {
handleCancel();
}
})
);
Best Practices
1. Batch Your Updates
// Good
function handleMultipleChanges() {
state.field1 = value1;
state.field2 = value2;
state.field3 = value3;
update();
}
// Avoid
function handleMultipleChanges() {
state.field1 = value1;
update();
state.field2 = value2;
update();
state.field3 = value3;
update();
}
2. Use Computed Values
// Extract complex logic into functions
function activeCount() {
return todos.filter(t => !t.done).length;
}
div(
() => `${activeCount()} tasks remaining`
);
3. Organize Code with Component Functions
function UserCard(user) {
return div(
{ className: 'user-card' },
img({ src: user.avatar, alt: user.name }),
h3(user.name),
p(user.bio)
);
}
// Use it
div(
UserCard(currentUser)
);
4. Keep State Simple
// Just use plain objects and arrays
let user = {
name: 'Alice',
email: 'alice@example.com',
preferences: {
theme: 'dark',
notifications: true
}
};
let todos = [
{ id: 1, text: 'Learn nuclo', done: true },
{ id: 2, text: 'Build awesome app', done: false }
];
5. Handle Async Operations
let status = 'idle'; // 'idle' | 'loading' | 'success' | 'error'
let data = null;
let error = null;
async function fetchData() {
status = 'loading';
update();
try {
const response = await fetch('/api/data');
data = await response.json();
status = 'success';
} catch (err) {
error = err.message;
status = 'error';
}
update();
}