SonnetJS v0.1.0

Documentation

It's plain JavaScript with a few opinions, and this page covers all of them.

Getting started

Clone the boilerplate and open index.html in a browser. There is no install step. It works straight from the filesystem over file://.

git clone https://github.com/sonnetjs/sonnet

Structure

index.html            entry page; loads scripts in dependency order
jsconfig.json         type checking + autocomplete in VS Code
src/
  styles.css          global styles (design tokens + page styles)
  core.js             a Component base class and a mount() helper
  components/         one file per component
  main.js             entry point: mounts App into #app

These are classic scripts, with no modules and no imports, so load order in index.html is the dependency graph: core.js first, then component files, then main.js last.

Components

A component is a class with two things: a static template (the markup) and a script(root) method (the behavior). root is the cloned template content; from there it's ordinary DOM code: querySelector, textContent, addEventListener.

class Counter extends Component {
    static template = `<button>count is 0</button>`

    script(root) {
        let count = 0
        const button = root.querySelector('button')
        button.addEventListener('click', () => {
            button.textContent = `count is ${++count}`
        })
    }
}

mount(element, component) puts it on the page:

mount(document.getElementById('app'), new Counter())

Templates are static, trusted HTML only. Never interpolate data into them; dynamic values are set in script via textContent.

Passing data

There is no built-in props mechanism. A component that needs data defines its own constructor and stores what it's given; it's just a class. And there is no reactivity either: when data changes, you update the DOM yourself in an event handler, exactly like the counter above does.

class Greeting extends Component {
    static template = `<p data-ref="text"></p>`

    constructor(props = {}) {
        super()
        this.props = props
    }

    script(root) {
        root.querySelector('[data-ref="text"]').textContent =
            `Hello, ${this.props.name ?? 'world'}`
    }
}

mount(document.getElementById('app'), new Greeting({ name: 'Sonnet' }))

Composition

Put placeholder elements in the template and mount children into them from script:

script(root) {
    mount(root.querySelector('[data-ref="counter"]'), new Counter())
}

Adding a component

  1. Create src/components/<name>.js with a class <Name> extends Component.
  2. Document its props with a JSDoc @typedef so call sites get autocomplete.
  3. Add its <script> tag to index.html before main.js.

Constraints to know

  • Every top-level class / function / const is a global shared across all scripts, so keep names unique.
  • Templates are parsed once per class and cached; they cannot interpolate per-instance values, so set dynamic content in script.
  • There is no re-render machinery. Components render once; after that, update the DOM directly in event handlers.