SpikiJS v1.1
Documentation

SpikiJS Framework

SpikiJS is an ultra-lightweight (~5KB), high-performance JavaScript framework designed for building reactive user interfaces directly in HTML without complex build tooling or transpilation steps.

~5KB Footprint
Zero dependencies, loads instantaneously in any modern browser.
CSP Compliant
No unsafe inline eval/expressions. Clean separation of template & logic.
Deep Reactivity
ES6 Proxy powered auto-tracking for nested objects and arrays.
CSP Compliance & Zero Inline Logic Rule

SpikiJS enforces strict separation of concerns to comply with strict Content Security Policy (CSP). No eval() or unsafe inline code execution is used.

  • No Inline JS Code: HTML attributes cannot contain JS code, expressions, or operators (e.g. s-click="count++" is invalid). Allowed Exception: Boolean directives (s-if, :hidden, :disabled, :checked) accept state property names directly or with logical NOT ! (e.g. :hidden="!show").
  • No Attribute Parameters: Function parameters are not supported inside HTML attributes (e.g. s-click="save(item)" is invalid). Bind only the method name: s-click="save". Access this.item (or this.child, this.index in loops) directly inside the method!
  • No Dot Modifiers: Attribute names do NOT support dots or modifiers (e.g. s-submit.prevent="submitForm" is invalid). Write clean directive names: s-submit="submitForm" and call e.preventDefault() inside your method if needed.
  • State Access via this: Access and mutate variables directly inside JS methods via this.variableName.

Installation

1. Script Tag / CDN

Include the UMD script directly. Spiki will automatically bind to window.spiki.

<script src="https://unpkg.com/spikijs"></script>

2. ES Module / NPM

Install via NPM for modern bundlers (Vite, Webpack, Rollup).

npm install spikijs
import spiki from 'spikijs';

Quick Start

Define components with s-data="name", register state logic using spiki.data(), and start the engine.

Live Interactive Demo Active
Count:
// HTML
<div s-data="counterApp">
    <button s-click="decrement">-1</button>
    <span s-text="count"></span>
    <button s-click="increment">+1</button>
</div>
// JavaScript (ES Module)
<script type="module">
    import spiki from 'spikijs';

    // 1. Register Component State & Methods
    spiki.data('counterApp', () => ({
        count: 0,
        increment() { this.count++; },
        decrement() { this.count--; }
    }));

    // 2. Start Engine
    document.addEventListener('DOMContentLoaded', () => {
        spiki.start();
    });
</script>

Directives Reference Summary

Directive Description Example
s-data Defines component instance scope (accepts 1 or more modules/factories) <div s-data="myApp">
s-text Sets safe text content <span s-text="username">
s-html Sets inner HTML content (XSS caution) <div s-html="richText">
s-if Conditionally mounts/unmounts DOM nodes (supports ! negation) <div s-if="!isOnline">Offline</div>
:hidden Toggles native hide-show (supports ! negation) <div hidden :hidden="!isVisible">
s-for Loops array items inside <template> with auto-injected context <template s-for="(item, idx) in list" s-key="id">
s-key Unique key tracking for list items <template ... s-key="id">
s-model Two-way data binding for form inputs <input s-model="query">
s-value One-way value binding for input elements <input s-value="formattedText" readonly>
s-effect Automatic reactive watcher (runs side effects on state mutation) <div s-effect="saveToStorage">
s-ref Registers DOM element reference in this.$refs <input s-ref="textInput">
s-init Executes hook on component mount <div s-init="onMount">
s-destroy Executes hook on component unmount <div s-destroy="onUnmount">
s-ignore Skips DOM compilation for static blocks <div s-ignore>
:[attr] Binds dynamic attribute (HTML & SVG safe) <img :src="userAvatar">
:class Toggles dynamic classes (HTML & SVG safe) <div :class="activeClass">
s-click Attaches click event listener to element <button s-click="saveItem">
s-[event] Attaches native event listener (s-input, s-keyup, etc.) <input s-input="handleInput">
this.$root Accesses component's root container DOM element this.$root.classList.add('active')
this.$refs Object containing all s-ref DOM elements this.$refs.textInput.focus()
this.$store Accesses registered global stores reactively this.$store.settings.theme
spiki.store() Registers global store (executes init() automatically) spiki.store('settings', { ... })
spiki.data() Registers component state with one or more modules/factories spiki.data('app', createAuth, createCart)
spiki.raw() Unwraps Spiki Proxy to a plain JavaScript object const obj = spiki.raw(this.user)

s-data Component Scope

s-data="componentName" marks an element as the root of a Spiki component. State returned by the component factory function becomes reactive.

spiki.data('myApp', () => ({
    title: 'Hello Spiki',
    user: { name: 'Alice' }
}));

s-text & s-html

Use s-text for safe text insertion and s-html for rendering raw HTML. Functions passed to s-text receive the element instance el as the first parameter.

s-text (Plain string):
s-text (Computed with el argument):
s-html (Formatted markup):
spiki.data('textDemo', () => ({
    message: 'Reactive Text Content',
    richHtml: '<span class="text-emerald-500 font-bold">Safe HTML Content</span>',
    upperMessage(el) {
        return this.message.toUpperCase();
    }
}));

:[attr] & :class

Prefix any standard HTML or SVG attribute with : for dynamic binding. :class accepts string class names or objects with boolean values, fully safe for both HTML and SVG elements.

HTML Element Status:
SVG Element with reactive class binding
// HTML & SVG binding
<svg :class="svgClass" viewBox="0 0 24 24">...</svg>

spiki.data('attrDemo', () => ({
    isActive: false,
    toggleActive() { this.isActive = !this.isActive; },
    get activeClass() {
        return this.isActive ? 'bg-emerald-500 text-white' : 'bg-slate-200 text-slate-700';
    },
    get svgClass() {
        return this.isActive ? 'stroke-indigo-500 text-indigo-500' : 'stroke-slate-400 text-slate-400';
    }
}));

s-click & s-[event] Event Handling

Attach standard DOM event listeners using s-click, s-input, s-submit, s-keyup, etc. Event handlers receive the native Event object e without inline arguments.

Nested Handlers & e.stopPropagation()

When an inner element with s-click sits inside a container that also has s-click, calling e.stopPropagation() in the inner method cleanly prevents the outer container's listener from executing.

Last Event Log:
spiki.data('eventDemo', () => ({
    lastLog: 'None',
    handleClick(e) {
        this.lastLog = 'Clicked button at x: ' + e.clientX;
    },
    handleKeyup(e) {
        if (e.key === 'Enter') this.lastLog = 'Enter pressed!';
        else this.lastLog = 'Key pressed: ' + e.key;
    }
}));

s-model & s-value

s-model creates two-way bindings for inputs, textareas, checkboxes, and radios. s-value provides one-way value binding.

State: { name: "", isSubscribed: }
spiki.data('formDemo', () => ({
    name: 'Alice',
    isSubscribed: true,
    get displayName() { return this.name || 'Anonymous'; },
    get formattedName() { return this.name ? `Developer: ${this.name}` : ''; }
}));

s-if Conditional Mounting

s-if="condition" conditionally mounts or completely unmounts DOM nodes from the tree based on boolean evaluation. Supports negation using ! prefix.

✓ s-if="show" (Mounted in DOM tree)
✕ s-if="!show" (Negation: Mounted when false)
<!-- Mounted when show is true -->
<div s-if="show">Mounted when true</div>

<!-- Mounted when show is false (negation) -->
<div s-if="!show">Mounted when false</div>

:hidden Dynamic Visibility

Spiki uses native HTML attribute binding :hidden="!condition" as a high-performance hide-show. The element remains in the DOM tree while toggling display visibility. Fully supports ! negation.

:hidden="!isVisible" (Visible when isVisible is true)
:hidden="isVisible" (Visible when isVisible is false)
// Toggles native HTML 'hidden' attribute without DOM unmounting
<!-- Visible when isVisible is true -->
<div :hidden="!isVisible">Visible when true</div>

<!-- Visible when isVisible is false (negation) -->
<div :hidden="isVisible">Visible when false</div>
Pro Tip: Prevent Initial Render Glitch (FOUC)

Add the static hidden attribute to your HTML element if it should be hidden on initial page load (e.g. <div hidden :hidden="show">). This prevents element flickering before JavaScript loads.

s-for & s-key

Render lists by placing s-for="item in array" on a <template> element. Always supply a unique key with s-key="id" for optimal DOM reuse. Inside loop event handlers, Spiki automatically provides this.item.

<template s-for="(fruit, idx) in fruits" s-key="id">
    <li>
        <span s-text="fruit.name"></span>
        <!-- Notice: No arguments passed to removeItem! -->
        <button s-click="removeItem">Remove</button>
    </li>
</template>

spiki.data('listDemo', () => ({
    fruits: [ { id: 1, name: 'Apple' }, { id: 2, name: 'Banana' } ],
    removeItem() {
        // 'this.fruit' and 'this.idx' are automatically injected into 'this'!
        this.fruits.splice(this.idx, 1);
    }
}));
Automatic Loop Scope Injection (this.child, this.index)

Inside a loop like <template s-for="(child, index) in children">, event handlers like s-click="removeItem" do NOT require arguments. Spiki automatically injects this.child (current item) and this.index (current index) directly into this when the handler runs!

s-init & s-destroy

s-init="onMount" executes immediately when the component mounts. s-destroy="onUnmount" runs when the component is unmounted from DOM tree to prevent memory leaks. Both functions receive the DOM element el as parameter.

Mount Status:
<div s-data="userApp" s-init="onMount" s-destroy="onUnmount">
    <span s-text="status"></span>
</div>

spiki.data('userApp', () => ({
    status: 'Booting...',
    onMount(el) {
        console.log('Component mounted on element:', el);
        this.status = 'Ready';
    },
    onUnmount(el) {
        console.log('Cleaning up resources...');
    }
}));

s-effect Reactive Side Effects

s-effect="handler" registers an automatic reactive watcher function. Spiki automatically tracks all reactive state properties accessed inside the handler function and re-runs it whenever those properties mutate!

Synced Value:
// s-effect automatically re-executes whenever this.note mutates!
<div s-data="noteApp" s-effect="autoSave">
    <textarea s-model="note"></textarea>
</div>

spiki.data('noteApp', () => ({
    note: '',
    autoSave() {
        // Read reactive state (this.note) and save to external storage
        localStorage.setItem('user_note', this.note);
    }
}));
Warning: Avoid Infinite Loops in s-effect

Do NOT mutate component state properties (e.g. this.count++) inside an s-effect function! Modifying state read inside an effect re-triggers the watcher endlessly, causing an infinite loop. Use s-effect strictly for side effects like localStorage, DOM calls, or external network requests.

Instance API & DOM References

Inside any component method, this grants direct access to state properties as well as built-in Spiki instance helpers.

this.$refs

Dictionary of DOM elements marked with s-ref="key". Grants direct access to raw HTML elements without querySelector.

this.$root

Reference to the root element containing s-data. Useful for class toggles, container dimensions, or custom events.

this.$store

Shared global reactive store. Any property accessed via this.$store automatically triggers component updates when changed.

spiki.raw(proxy)

Unwraps a Spiki Proxy object back into a plain JavaScript object. Essential for JSON.stringify, logging, or API calls.

spiki.data(name, ...fns)

Registers a component and merges multiple modular factory functions into a single unified reactive scope while preserving getters and computed properties.

1 DOM References with s-ref

Use s-ref="name" to register direct DOM element references inside this.$refs. Ideal for focusing inputs, reading canvas contexts, controlling video elements, or integrating 3rd-party libraries.

Native Element Tag:
<input s-ref="textInput" type="text">
<button s-click="focusInput">Focus</button>

spiki.data('refDemo', () => ({
    focusInput() {
        // Access raw HTMLInputElement directly
        this.$refs.textInput.focus();
    },
    selectText() {
        this.$refs.textInput.select();
    }
}));

2 Root Element Reference with this.$root

this.$root points to the component's root container element (the HTML tag bearing s-data).

Root Tag:
spiki.data('rootDemo', () => ({
    highlightRoot() {
        // Manipulate root container element directly
        this.$root.classList.toggle('ring-2');
        this.$root.classList.toggle('ring-indigo-500');
    }
}));

3 Global Store spiki.store()

Register a shared global store using spiki.store('name', stateObject). Any component accessing this.$store.name automatically subscribes to store state updates.

Automatic init() Store Execution

If your store object defines an init() method, Spiki automatically executes store.init.call(store) immediately upon store registration! This is ideal for loading persisted state from localStorage or initializing listeners.

Store Theme:
Initialized Status:
// 1. Register store with automatic init()
spiki.store('settings', {
    theme: 'dark',
    initialized: false,
    init() {
        // Automatically runs when store is registered!
        this.theme = localStorage.getItem('theme') || 'dark';
        this.initialized = true;
    }
});
// 2. Read store inside any component
spiki.data('myComponent', () => ({
    get currentTheme() {
        return this.$store.settings.theme; // Reactive state access!
    }
}));

4 Unwrapping Reactive Proxies with spiki.raw()

Spiki wraps state objects in ES6 Proxies for reactive tracking. Use spiki.raw(proxy) to unwrap a proxy object back into a plain, non-reactive JavaScript object or array.

// Unwrap proxy before console.log, JSON.stringify, or API fetch
spiki.data('userForm', () => ({
    user: { name: 'Alice', email: 'alice@example.com' },

    saveUser() {
        // Extract raw un-proxied JS object
        const rawPayload = spiki.raw(this.user);

        console.log("Raw Object:", rawPayload);
        fetch('/api/user', {
            method: 'POST',
            body: JSON.stringify(rawPayload)
        });
    }
}));

5 Modular State Composition with spiki.data()

For large applications, organize code into smaller modular factory functions and pass them directly into spiki.data('name', ...fns). Spiki automatically merges them into a single reactive component while preserving getters and computed properties. All methods share access to the unified state via this.

Unified State: Count = | Mode =
// 1. Separate state slices / modular factories
const createCounterSlice = () => ({
    count: 0,
    increment() { this.count++; }
});

const createThemeSlice = () => ({
    darkMode: false,
    get themeMode() { return this.darkMode ? 'Dark' : 'Light'; },
    toggleTheme() { this.darkMode = !this.darkMode; }
});
// 2. Pass multiple factory functions directly into spiki.data()
spiki.data('modularDemo',
    createCounterSlice,
    createThemeSlice,
    () => ({
        init() {
            console.log('Composed component initialized!');
        }
    })
);