Sub-3KB reactive framework — signals, web components, routing, and PWA.
Works standalone or embedded inside tina4-php / tina4-python.
| Feature | React | Preact | Vue | tina4-js |
|---|---|---|---|---|
| Size (gzip) | 42KB | 3KB | 33KB | ~2KB |
| Virtual DOM | Yes | Yes | Yes | No |
| Components | Custom | Custom | Custom | Native Web Components |
| Reactivity | Hooks | Hooks | Proxy | Signals |
| Router included | No | No | No | Yes |
| HTTP client included | No | No | No | Yes |
| PWA support | No | No | No | Yes |
| Backend integration | None | None | None | tina4-php/python |
| Works without build | No | No | No | Yes (ESM) |
No virtual DOM. Signals track exactly which DOM nodes need updating — O(1) updates.
npm install tina4jsOr use via CDN with zero build tools:
<script type="module">
import { signal, html } from 'https://cdn.jsdelivr.net/npm/tina4js/dist/tina4.esm.js';
</script>npm run dev # dev server with HMR
npm run build # production build
npm test # run testsimport { signal, computed, effect, batch } from 'tina4js';
// Create a reactive value
const count = signal(0);
count.value; // read: 0
count.value = 5; // write: triggers subscribers
// Derived value (auto-tracks dependencies)
const doubled = computed(() => count.value * 2);
doubled.value; // 10 (read-only)
// Side effect (auto-tracks dependencies)
const dispose = effect(() => {
console.log(`Count is ${count.value}`);
});
// Runs immediately, then re-runs when count changes.
// Call dispose() to stop.
// Batch multiple updates (one notification)
batch(() => {
a.value = 1;
b.value = 2;
}); // subscribers notified onceimport { html, signal } from 'tina4js';
const name = signal('World');
// Creates real DOM nodes (not strings)
const el = html`<h1>Hello ${name}!</h1>`;
document.body.append(el);
name.value = 'Tina4'; // DOM updates surgically — no diffing
// Event handlers
html`<button @click=${() => alert('clicked')}>Go</button>`;
// Conditional rendering
const show = signal(true);
html`<div>${() => show.value ? html`<p>Visible</p>` : null}</div>`;
// List rendering
const items = signal(['a', 'b', 'c']);
html`<ul>${() => items.value.map(i => html`<li>${i}</li>`)}</ul>`;
// Reactive attributes
const cls = signal('active');
html`<div class=${cls}>Styled</div>`;
// Boolean attributes
const disabled = signal(false);
html`<button ?disabled=${disabled}>Submit</button>`;import { Tina4Element, html, signal } from 'tina4js';
class MyCounter extends Tina4Element {
static props = { label: String };
static styles = `:host { display: block; }`;
count = signal(0);
render() {
return html`
<span>${this.prop('label')}: ${this.count}</span>
<button @click=${() => this.count.value++}>+</button>
`;
}
}
customElements.define('my-counter', MyCounter);<my-counter label="Clicks"></my-counter>import { route, router, navigate, html } from 'tina4js';
route('/', () => html`<h1>Home</h1>`);
route('/user/{id}', ({ id }) => html`<h1>User ${id}</h1>`);
route('/admin', {
guard: () => isLoggedIn() || '/login',
handler: () => html`<h1>Admin</h1>`,
});
route('*', () => html`<h1>404</h1>`);
router.start({ target: '#root', mode: 'history' });
// Programmatic navigation
navigate('/user/42');import { api } from 'tina4js';
api.configure({
baseUrl: '/api',
auth: true, // auto Bearer + formToken (tina4-php/python compatible)
});
const users = await api.get('/users');
const user = await api.get('/users/{id}', { id: 42 });
const result = await api.post('/users', { name: 'Andre' });
// Interceptors
api.intercept('request', (config) => {
config.headers['X-Custom'] = 'value';
return config;
});
api.intercept('response', (res) => {
if (res.status === 401) navigate('/login');
return res;
});import { pwa } from 'tina4js';
pwa.register({
name: 'My App',
shortName: 'App',
themeColor: '#1a1a2e',
cacheStrategy: 'network-first',
precache: ['/', '/css/default.css'],
offlineRoute: '/offline',
});A built-in debug overlay that shows live signal values, component tree, route history, and API calls.
// Always-on (remove for production)
import 'tina4js/debug';
// Dev-only (recommended) — tree-shaken out of production builds
if (import.meta.env.DEV) import('tina4js/debug');Once enabled, toggle the overlay with Ctrl+Shift+D.
The overlay shows four tabs:
| Tab | What it shows |
|---|---|
| Signals | All signals with current value, subscriber count, and update count |
| Components | Mounted Tina4Element web components |
| Routes | Navigation history with timing |
| API | Intercepted api.* requests and responses |
| Mode | Description |
|---|---|
| Standalone |
npm run build → deploy dist/ to any static host |
| tina4-php |
npm run build → JS bundle into src/public/js/, uses TINA4_APP_DOCUMENT_ROOT
|
| tina4-python |
npm run build → JS bundle into src/public/js/, with catch-all route |
| Islands | No SPA — hydrate individual web components in server-rendered pages |
npm test # run all tests
npm run test:watch # watch mode
npm run build # production build
npm run dev # dev server- Fix: Effects now properly unsubscribe from signals on dispose — prevents stale subscriptions accumulating in signal subscriber sets across navigations
-
Fix: Function bindings in
htmltemplates now dispose inner effects when re-evaluated — fixes duplicate DOM nodes from nested reactive lists and conditionals - Added 9 new tests covering effect subscription cleanup, inner effect disposal, and multi-navigation accumulation (116 total)
- Added router reactive effect cleanup tests (navigate away/back, stale effects, async handlers, stale async discard)
- Added debug overlay documentation to README and TINA4.md
-
Fix:
renderContentnow usesreplaceChildreninstead ofappendChild, preventing duplicate content when async route handlers resolve.
-
Fix: Router now disposes reactive effects when navigating between routes. Previously, signal subscriptions created by
htmltemplates survived DOM removal viainnerHTML = '', causing duplicate renders when revisiting a page. - Fix: Stale async route handlers are discarded if navigation occurs before they resolve.
- Debug overlay module with signal, component, route, and API inspectors
- Todo app example and exports map file extension fixes
- CLI scaffolding tool and TINA4.md AI context file
- Fetch, PWA, integration, and size tests (102 total)
MIT