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
-
Create
src/components/<name>.jswith aclass <Name> extends Component. -
Document its props with a JSDoc
@typedefso call sites get autocomplete. -
Add its
<script>tag toindex.htmlbeforemain.js.
Constraints to know
-
Every top-level
class/function/constis 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.