</> Ultima

Getting started

There's no npm package or official CDN yet — Ultima is used by including the source files directly, as native ES modules. That's how the project itself is used internally.

1. Get the core files

Copy (or clone the repository and reference) the two files that make up the framework's core:

src/component_base.js
src/reactive_component.js

No other file is required — that's the entire framework.

2. Create the component's template and class

Every component has two files: an .html file (the template) and a .js file (the class, which extends ReactiveComponent when it needs data binding, or ComponentBase when it doesn't).

my-counter.html
<div>
  Total: <span data-bind='{"textContent":"total"}'></span>
  <button id="increment">+1</button>
</div>
my-counter.js
import { ReactiveComponent } from './reactive_component.js';

export class MyCounter extends ReactiveComponent {

    constructor() {
        super(
            { templateUrl: './my-counter.html', shadowDom: true },
            import.meta.url
        );

        this.addEventListener('component-loaded', (event) => {
            // composedPath()[0], not event.target — see the Guide for why
            if (event.composedPath()[0] !== this) return;

            this.state = { total: 0 };

            this.rootNode.querySelector('#increment')
                .addEventListener('click', () => {
                    this.state = { total: this.state.total + 1 };
                });
        });
    }
}

customElements.define('my-counter', MyCounter);

3. Use the component

<script type="module" src="./my-counter.js"></script>

<my-counter></my-counter>
No build step. Serve the files with any static server (python3 -m http.server, for example) and open it in the browser. Paths in templateUrl are resolved relative to the component's own .js file (via import.meta.url), not relative to the page — so components are independent of wherever they're used.