import{_ as i,o as a,c as n,ae as h}from"./chunks/framework.C8AWLET_.js";const d=JSON.parse('{"title":"Pages API ๐","description":"","frontmatter":{},"headers":[],"relativePath":"api/pages.md","filePath":"api/pages.md"}'),t={name:"api/pages.md"};function l(k,s,p,e,E,r){return a(),n("div",null,[...s[0]||(s[0]=[h(`
Pages in SigPro are special components designed for route-based navigation with automatic cleanup. When you navigate away from a page, all signals, effects, and event listeners created within that page are automatically cleaned up - no memory leaks, no manual cleanup needed.
$.page(setupFunction) โCreates a page with automatic cleanup of all signals and effects when navigated away.
import { $, html } from 'sigpro';
export default $.page(() => {
// All signals and effects created here
// will be automatically cleaned up on navigation
const count = $(0);
$.effect(() => {
console.log(\`Count: \${count()}\`);
});
return html\`
<div>
<h1>My Page</h1>
<p>Count: \${count}</p>
<button @click=\${() => count(c => c + 1)}>+</button>
</div>
\`;
});| Parameter | Type | Description |
|---|---|---|
setupFunction | Function | Function that returns the page content. Receives context object with params and onUnmount |
| Property | Type | Description |
|---|---|---|
params | Object | Route parameters passed to the page |
onUnmount | Function | Register cleanup callbacks (alternative to automatic cleanup) |
// pages/home.js
import { $, html } from 'sigpro';
export default $.page(() => {
const title = $('Welcome to SigPro');
return html\`
<div class="home-page">
<h1>\${title}</h1>
<p>This page will clean itself up when you navigate away.</p>
</div>
\`;
});// pages/user.js
import { $, html } from 'sigpro';
export default $.page(({ params }) => {
// Access route parameters
const userId = params.id;
const userData = $(null);
const loading = $(false);
// Auto-cleaned effect
$.effect(() => {
loading(true);
$.fetch(\`/api/users/\${userId}\`, null, loading)
.then(data => userData(data));
});
return html\`
<div>
\${() => loading() ? html\`
<div class="spinner">Loading...</div>
\` : html\`
<h1>User Profile: \${userData()?.name}</h1>
<p>Email: \${userData()?.email}</p>
\`}
</div>
\`;
});The magic of $.page is automatic cleanup. Everything created inside the page is tracked and cleaned up:
export default $.page(() => {
// โ
Signals are auto-cleaned
const count = $(0);
const user = $(null);
// โ
Effects are auto-cleaned
$.effect(() => {
document.title = \`Count: \${count()}\`;
});
// โ
Event listeners are auto-cleaned
window.addEventListener('resize', handleResize);
// โ
Intervals and timeouts are auto-cleaned
const interval = setInterval(() => {
refreshData();
}, 5000);
return html\`<div>Page content</div>\`;
});
// When navigating away: all signals, effects, listeners, intervals STOPonUnmount โSometimes you need custom cleanup logic. Use onUnmount for that:
export default $.page(({ onUnmount }) => {
// WebSocket connection
const socket = new WebSocket('wss://api.example.com');
socket.onmessage = (event) => {
updateData(JSON.parse(event.data));
};
// Manual cleanup
onUnmount(() => {
socket.close();
console.log('WebSocket closed');
});
return html\`<div>Real-time updates</div>\`;
});Pages are designed to work seamlessly with $.router:
import { $, html } from 'sigpro';
import HomePage from './pages/Home.js';
import UserPage from './pages/User.js';
import SettingsPage from './pages/Settings.js';
const routes = [
{ path: '/', component: HomePage },
{ path: '/user/:id', component: UserPage },
{ path: '/settings', component: SettingsPage },
];
// Mount router
document.body.appendChild($.router(routes));// pages/posts.js
export default $.page(({ params }) => {
const posts = $([]);
const loading = $(true);
const error = $(null);
$.effect(() => {
fetch('/api/posts')
.then(res => res.json())
.then(data => {
posts(data);
loading(false);
})
.catch(err => {
error(err.message);
loading(false);
});
});
return html\`
<div class="posts-page">
<h1>Blog Posts</h1>
\${() => loading() ? html\`
<div class="loading">Loading posts...</div>
\` : error() ? html\`
<div class="error">Error: \${error()}</div>
\` : html\`
<div class="posts-grid">
\${posts().map(post => html\`
<article class="post-card">
<h2>\${post.title}</h2>
<p>\${post.excerpt}</p>
<a href="#/post/\${post.id}">Read more</a>
</article>
\`)}
</div>
\`}
</div>
\`;
});// pages/dashboard.js
export default $.page(({ onUnmount }) => {
const metrics = $({
cpu: 0,
memory: 0,
requests: 0
});
// Auto-refresh data
const refreshInterval = setInterval(async () => {
const data = await $.fetch('/api/metrics');
if (data) metrics(data);
}, 5000);
// Manual cleanup for interval
onUnmount(() => clearInterval(refreshInterval));
// Live clock
const currentTime = $(new Date());
const clockInterval = setInterval(() => {
currentTime(new Date());
}, 1000);
onUnmount(() => clearInterval(clockInterval));
return html\`
<div class="dashboard">
<h1>System Dashboard</h1>
<div class="time">
Last updated: \${() => currentTime().toLocaleTimeString()}
</div>
<div class="metrics-grid">
<div class="metric-card">
<h3>CPU Usage</h3>
<p class="metric-value">\${() => metrics().cpu}%</p>
</div>
<div class="metric-card">
<h3>Memory Usage</h3>
<p class="metric-value">\${() => metrics().memory}%</p>
</div>
<div class="metric-card">
<h3>Requests/min</h3>
<p class="metric-value">\${() => metrics().requests}</p>
</div>
</div>
</div>
\`;
});// pages/checkout.js
export default $.page(({ onUnmount }) => {
const step = $(1);
const formData = $({
email: '',
address: '',
payment: ''
});
// Warn user before leaving
const handleBeforeUnload = (e) => {
if (step() < 3) {
e.preventDefault();
e.returnValue = '';
}
};
window.addEventListener('beforeunload', handleBeforeUnload);
onUnmount(() => {
window.removeEventListener('beforeunload', handleBeforeUnload);
});
const nextStep = () => step(s => Math.min(s + 1, 3));
const prevStep = () => step(s => Math.max(s - 1, 1));
return html\`
<div class="checkout">
<h1>Checkout - Step \${step} of 3</h1>
\${() => {
switch(step()) {
case 1:
return html\`
<div class="step">
<h2>Email</h2>
<input
type="email"
:value=\${() => formData().email}
@input=\${(e) => formData({...formData(), email: e.target.value})}
/>
</div>
\`;
case 2:
return html\`
<div class="step">
<h2>Address</h2>
<textarea
:value=\${() => formData().address}
@input=\${(e) => formData({...formData(), address: e.target.value})}
></textarea>
</div>
\`;
case 3:
return html\`
<div class="step">
<h2>Payment</h2>
<input
type="text"
placeholder="Card number"
:value=\${() => formData().payment}
@input=\${(e) => formData({...formData(), payment: e.target.value})}
/>
</div>
\`;
}
}}
<div class="buttons">
\${() => step() > 1 ? html\`
<button @click=\${prevStep}>Previous</button>
\` : ''}
\${() => step() < 3 ? html\`
<button @click=\${nextStep}>Next</button>
\` : html\`
<button @click=\${submitOrder}>Place Order</button>
\`}
</div>
</div>
\`;
});// pages/profile.js
export default $.page(({ params }) => {
const activeTab = $('overview');
const userData = $(null);
// Load user data
$.effect(() => {
$.fetch(\`/api/users/\${params.id}\`)
.then(data => userData(data));
});
const tabs = {
overview: () => html\`
<div>
<h3>Overview</h3>
<p>Username: \${userData()?.username}</p>
<p>Member since: \${userData()?.joined}</p>
</div>
\`,
posts: () => html\`
<div>
<h3>Posts</h3>
\${userData()?.posts.map(post => html\`
<div class="post">\${post.title}</div>
\`)}
</div>
\`,
settings: () => html\`
<div>
<h3>Settings</h3>
<label>
<input type="checkbox" :checked=\${userData()?.emailNotifications} />
Email notifications
</label>
</div>
\`
};
return html\`
<div class="profile-page">
<h1>\${() => userData()?.name}</h1>
<div class="tabs">
\${Object.keys(tabs).map(tab => html\`
<button
class:active=\${() => activeTab() === tab}
@click=\${() => activeTab(tab)}
>
\${tab.charAt(0).toUpperCase() + tab.slice(1)}
</button>
\`)}
</div>
<div class="tab-content">
\${() => tabs[activeTab()]()}
</div>
</div>
\`;
});// pages/settings/index.js
export default $.page(({ params }) => {
const section = params.section || 'general';
const sections = {
general: () => import('./general.js').then(m => m.default),
security: () => import('./security.js').then(m => m.default),
notifications: () => import('./notifications.js').then(m => m.default)
};
const currentSection = $(null);
$.effect(() => {
sections[section]().then(comp => currentSection(comp));
});
return html\`
<div class="settings">
<nav>
<a href="#/settings/general">General</a>
<a href="#/settings/security">Security</a>
<a href="#/settings/notifications">Notifications</a>
</nav>
<div class="content">
\${currentSection}
</div>
</div>
\`;
});// pages/dashboard.js
export default $.page(({ onUnmount }) => {
const isAuthenticated = $(false);
const authCheck = $.effect(() => {
const token = localStorage.getItem('token');
isAuthenticated(!!token);
});
// Redirect if not authenticated
$.effect(() => {
if (!isAuthenticated()) {
$.router.go('/login');
}
});
return html\`
<div class="dashboard">
<h1>Protected Dashboard</h1>
<!-- Protected content -->
</div>
\`;
});| Feature | Description |
|---|---|
| Automatic Cleanup | All signals, effects, and resources auto-cleaned on navigation |
| Memory Safe | No memory leaks, even with complex nested effects |
| Router Integration | Designed to work perfectly with $.router |
| Parameters | Access route parameters via params object |
| Manual Cleanup | onUnmount for custom cleanup needs |
| Zero Configuration | Just wrap your page in $.page() and it works |
`,41)])])}const g=i(t,[["render",l]]);export{d as __pageData,g as default};Pro Tip: Always wrap route-based views in
$.page()to ensure proper cleanup. This prevents memory leaks and ensures your app stays performant even after many navigation changes.