<script>
NoJS.config({
// API
baseApiUrl: 'https://api.myapp.com/v1',
headers: { 'Authorization': 'Bearer xxx' },
timeout: 10000,
retries: 2,
retryDelay: 1000,
credentials: 'include', // fetch credentials mode
// CSRF
csrf: {
header: 'X-CSRF-Token',
token: '...'
},
// Caching
cache: {
strategy: 'memory', // 'none' | 'memory' | 'session' | 'local'
ttl: 300000 // 5 minutes
},
// Templates
templates: {
cache: true // Cache fetched .tpl HTML in memory (default: true)
},
// Router
router: {
useHash: false, // true = hash mode, false = history mode (default)
base: '/',
scrollBehavior: 'top', // 'top' | 'preserve' | 'smooth'
templates: 'pages', // Default base path for file-based routing (default: 'pages')
ext: '.tpl' // Default file extension for file-based routing (fallback: '.html')
},
// Note: Anchor links (href="#id") are automatically
// intercepted in both modes — they scroll to the target
// element without triggering route navigation.
// i18n
i18n: {
defaultLocale: 'en',
fallbackLocale: 'en',
detectBrowser: true,
loadPath: '/locales/{locale}.json', // Load from external JSON (default: null)
ns: ['common'], // Namespaces to preload (default: [])
cache: true // Cache fetched locale files (default: true)
},
// Debugging
debug: true, // Logs directive processing
devtools: true, // Enables browser devtools panel
// Security
sanitize: true, // Sanitize bind-html
// Performance
exprCacheSize: 500, // Max entries in the expression/statement LRU caches
});
</script>Use stores in NoJS.config() to create multiple global stores before the DOM is processed.
This is useful when stores need to exist before any HTML directive runs — for example, when setting auth state from a JWT, or hydrating from localStorage.
<script>
NoJS.config({
stores: {
auth: { user: null, token: localStorage.getItem('token') },
cart: { items: [], total: 0 },
theme: { mode: 'dark', accent: 'blue' }
}
});
</script>Stores created via config() behave exactly like those declared with the store HTML attribute — they are reactive contexts accessible via $store.name in expressions and NoJS.store.name in JavaScript.
If a store name already exists, config() will not overwrite it. This means you can safely call config() multiple times without resetting store data.
<script>
// Before every request
NoJS.interceptor('request', (url, options) => {
options.headers['X-Request-ID'] = crypto.randomUUID();
return options;
});
// After every response
NoJS.interceptor('response', (response, url) => {
if (response.status === 401) {
NoJS.store.auth.user = null;
NoJS.notify(); // flush DOM bindings before redirect
NoJS.router.push('/login');
throw new Error('Unauthorized');
}
return response;
});
</script>bindalways setstextContent, neverinnerHTML— safe by default.bind-htmlsanitizes content using a DOMParser-based structural sanitizer. Regex-based sanitizers are bypassable via SVG/MathML event handlers and entity encoding — DOMParser resolves entities first, making all vectors uniformly detectable.- Template expressions are evaluated by a custom sandboxed parser — no
eval()orFunction()is used, and dangerous properties like__proto__andconstructorare blocked.
To use an external sanitizer like DOMPurify instead of the built-in one:
NoJS.config({
sanitizeHtml: (html) => DOMPurify.sanitize(html)
});When sanitizeHtml is set to a function, the built-in sanitizer is bypassed entirely and the provided function is used for all bind-html content. Set sanitize: false to disable sanitization entirely (not recommended — see Security).
The built-in sanitizer blocks the following tags by default: script, style, iframe, object, embed, base, form, meta, link, noscript. It also strips on* event handler attributes and javascript:/vbscript: scheme attributes on any element, and data: URIs on URL attributes (href, src, action) unless they are safe image types.
<script>
NoJS.config({
csrf: {
header: 'X-CSRF-Token',
token: document.querySelector('meta[name="csrf-token"]').content
}
});
</script>No.JS uses a custom expression parser that is fully CSP-compliant — no eval() or Function() constructor is used. No unsafe-eval directive is required in your Content Security Policy.
Type: boolean | Default: true
Controls whether the HTML content of remotely-fetched .tpl files is stored in an in-memory Map after the first request. On repeated navigations to the same route, the cached HTML is used directly — no HTTP request is made. The cache lives for the duration of the page session (no TTL — template assets are static, not data).
// Disable template caching (always re-fetch .tpl files)
NoJS.config({ templates: { cache: false } });
// Default — caching is on, no configuration needed
NoJS.config({ templates: { cache: true } });Set to false during local development if you want changes to .tpl files to be reflected without a hard page reload.
Type: string | null | Default: null
URL template for loading locale JSON files via fetch. Use {locale} and optionally {ns} as placeholders. When null, translations must be provided inline via NoJS.i18n({ locales }).
NoJS.i18n({
loadPath: '/locales/{locale}.json' // Flat mode
loadPath: '/locales/{locale}/{ns}.json' // Namespace mode
});Type: string[] | Default: []
Array of namespace identifiers to preload at init(). Each namespace corresponds to a separate JSON file per locale. Additional namespaces can be loaded on-demand via the i18n-ns directive or route attribute.
NoJS.i18n({
loadPath: '/locales/{locale}/{ns}.json',
ns: ['common', 'auth']
});Type: boolean | Default: true
Controls whether fetched locale JSON files are stored in an in-memory Map after the first request. Set to false during development for hot-reload of translation files.
NoJS.i18n({ cache: false }); // Always re-fetch locale filesType: number | Default: 500
Maximum number of entries in each of the two internal LRU caches used by the expression evaluator: one for parsed expression ASTs (_exprCache) and one for parsed statement ASTs (_stmtCache). When the limit is reached the least-recently-used entry is evicted to make room.
The default of 500 is suitable for most applications. Increase it if your app evaluates a large number of distinct template expressions (e.g. a dynamic form with hundreds of unique field bindings). Decrease it to reduce memory pressure in memory-constrained environments.
// Larger cache for apps with many distinct expressions
NoJS.config({ exprCacheSize: 1000 });
// Smaller cache for memory-constrained environments
NoJS.config({ exprCacheSize: 100 });Non-positive or non-numeric values are ignored and the default of 500 is used.
No.JS includes a plugin system for extending the framework with reusable packages. The following methods are available on the NoJS object:
| Method | Description |
|---|---|
NoJS.use(plugin, options?) |
Register a plugin. Accepts an object with { name, install } or a named function |
NoJS.global(name, value) |
Inject a reactive variable accessible as $name in templates |
NoJS.dispose() |
Full app teardown — disposes plugins in reverse order, clears globals and interceptors |
NoJS.CANCEL |
Sentinel Symbol — return from a request interceptor to abort the request |
NoJS.RESPOND |
Sentinel Symbol — return from a request interceptor to serve a cached response |
NoJS.REPLACE |
Sentinel Symbol — return from a response interceptor to replace the response data |
See Plugins → for the full API reference, plugin lifecycle, security guidelines, and examples.
Next: Directive Cheatsheet →