Tina4

Tina4 JavaScript - Quick Reference#

๐Ÿ”ฅ Hot Tips

  • Signals for state, html tagged templates for rendering, Web Components for reuse
  • Client-side routing with {param} syntax matching tina4-php/python
  • Fetch API wrapper with Bearer + formToken auth compatible with tina4 backends
  • PWA support with runtime manifest and service worker generation
  • Tree-shakeable: import only what you need

Installation โ€ข Signals โ€ข HTML Templates โ€ข Components โ€ข Routing โ€ข API โ€ข WebSocket โ€ข SSE โ€ข Storage โ€ข PWA โ€ข Debug โ€ข Backend Integration โ€ข Bundle Size

Installation#

bash
npx tina4js create my-appcd my-appnpm installnpm run dev

More details on project setup, CLI options, and PWA scaffolding.

Signals#

ts
import { signal, computed, effect, batch } from 'tina4js';โ€‹const count = signal(0);const doubled = computed(() => count.value * 2);โ€‹effect(() => console.log(`Count: ${count.value}`));โ€‹count.value = 5;        // triggers effect โ†’ "Count: 5"console.log(doubled.value); // 10โ€‹// Batch multiple updates into one notificationbatch(() => { count.value = 10; count.value = 20; });

More details on reactive state, computed values, effects, and batching.

HTML Templates#

ts
import { signal, html } from 'tina4js';โ€‹const name = signal('World');โ€‹const view = html`  <div>    <h1>Hello, ${name}!</h1>    <input @input=${(e: Event) => {      name.value = (e.target as HTMLInputElement).value;    }}>    <button @click=${() => { name.value = 'World'; }}>Reset</button>  </div>`;โ€‹document.getElementById('root')!.appendChild(view);

More details on template syntax, event handlers, boolean attributes, conditionals, and lists.

Components#

ts
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`      <div>        <span>${this.prop('label')}: ${this.count}</span>        <button @click=${() => this.count.value++}>+</button>      </div>    `;  }โ€‹  onMount() { console.log('Connected!'); }  onUnmount() { console.log('Removed!'); }}โ€‹customElements.define('my-counter', MyCounter);
html
<my-counter label="Clicks"></my-counter>

More details on props, Shadow DOM, styles, lifecycle hooks, and events.

Routing#

ts
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' });โ€‹// Navigate programmaticallynavigate('/user/42');

More details on hash vs history mode, route params, guards, and change events.

API#

ts
import { api } from 'tina4js';โ€‹api.configure({ baseUrl: '/api', auth: true });โ€‹const users = await api.get('/users');const user  = await api.get('/users/42');await api.post('/users', { name: 'Andre' });await api.put('/users/42', { name: 'Updated' });await api.delete('/users/42');

More details on configuration, authentication, token rotation, interceptors, and error handling.

WebSocket#

ts
import { ws } from 'tina4js';โ€‹const socket = ws.connect('/ws/chat');   // status & connected are signals - bind them in templatesโ€‹const view = html`  <div>Status: ${socket.status}</div>  ${() => socket.connected.value ? html`<span>โ— live</span>` : html`<span>โ—‹ reconnecting...</span>`}`;โ€‹socket.on('message', (msg) => console.log(msg));socket.send({ type: 'hello' });

Auto-reconnect with exponential backoff; status/connected are signals, so the UI reacts to the connection state with no extra wiring. More details.

SSE / Streaming#

ts
import { sse } from 'tina4js';โ€‹const stream = sse.connect('/events', { json: true });   // Server-Sent Events / NDJSONstream.on('data', (row) => append(row));

Same signal-driven status + auto-reconnect shape as ws, for one-way server push and NDJSON. More details.

PWA#

ts
import { pwa } from 'tina4js';โ€‹pwa.register({  name: 'My App',  shortName: 'App',  themeColor: '#1a1a2e',  cacheStrategy: 'network-first',  // or 'cache-first', 'stale-while-revalidate'  precache: ['/', '/css/styles.css'],  offlineRoute: '/offline',});

More details on manifest generation, service worker strategies, and offline support.

Storage#

ts
import { signal } from 'tina4js';import { persist, clearPersistedKeys } from 'tina4js/storage';โ€‹const theme = persist(signal('light'), { key: 'theme' });   // survives reloads, syncs across tabstheme.value = 'dark';                                        // written to localStorage automaticallyโ€‹clearPersistedKeys(['theme']);                               // remove on logout

persist backs a signal with localStorage: versioned, migratable, and synced across tabs. Never store secrets, tokens, or personal data: localStorage is readable by any script on the page (XSS), so it is for UI preferences and non-sensitive view state only; keep auth tokens in memory or an httpOnly cookie.

Debug#

ts
// Enable the dev overlay (Ctrl+Shift+D to toggle). Dev only - tree-shaken from production.if (import.meta.env.DEV) import('tina4js/debug');

A side-effect import that mounts an overlay tracking live signals, mounted components, route changes, and API calls. Never ship it to production. More details.

Backend Integration#

With tina4-php#

bash
npx tina4js build --target php# Outputs JS to src/public/js/# Generates src/templates/pages/index.twig

The build drops your SPA's entry point at src/templates/pages/index.twig. Tina4's auto-routing serves it at /, with no env var or route needed. If your build emits a static index.html instead, drop it at src/public/index.html and Tina4 auto-serves it at / too (since v3.11.33).

With tina4-python#

bash
npx tina4js build --target python# Outputs JS to src/public/js/# Generates src/templates/pages/index.twig

Same auto-routing: src/templates/pages/*.twig becomes the page tree under /. Set TINA4_TEMPLATE_ROUTING=off if you want explicit routes only. More details on embedding in tina4-php/python, auth flow, and server-side state injection.

Bundle Size#

What your app actually downloads. tina4-js is code-split: a bundler ships one shared reactive-core chunk plus only the modules you import. These are real deduplicated bundles (esbuild --minify, then compressed), measured on macOS, v1.2.7; brotli is what most CDNs serve:

Your app importsgzipbrotli
Core only (signals + html + components)2.30 KB2.05 KB
Core + Router (typical SPA)3.14 KB2.78 KB
+ API4.00 KB3.54 KB
+ WebSocket + SSE5.32 KB4.73 KB
Everything (+ Storage + PWA, no Debug)7.52 KB6.68 KB

Marginal cost per feature is small (gzip): Router +0.8 KB, API +0.9 KB, WebSocket + SSE +1.3 KB. Debug is a separate dev-only entry (import 'tina4js/debug', \~5 KB gzip): guard it behind import.meta.env.DEV so your production bundler drops it entirely.

ts
// Import from sub-paths to help the bundler tree-shake:import { signal, html } from 'tina4js/core';import { route, router } from 'tina4js/router';import { api } from 'tina4js/api';import { ws } from 'tina4js/ws';import { sse } from 'tina4js/sse';import { persist } from 'tina4js/storage';import { pwa } from 'tina4js/pwa';

Don't add up the published dist/*.es.js file sizes. Each looks standalone, but the reactive core lives in one shared chunk that the others import, so summing the files counts core several times over (and includes the dev-only debug overlay). The table above is what actually ships.

โ†‘ Back to top


๐Ÿ“• Download the book#

tina4-js: The 1.5KB Reactive Core (PDF): full reference, printable, with clickable table of contents and PDF outline. Regenerated with every release.