Core Concepts
How an RJS instance (src/model/RJS.ts) is created and re-rendered, and why each render is a single, non-reactive pass.
Instance Lifecycle
"#selector".RJS(body) looks up the target element via body.id, clones it as the pristine template (#model), and stores data/event/when on the instance. If the selector resolves to null, the constructor returns early and no instance is initialized.
graph TB
Entry["#selector.RJS(body)"] --> Ctor["constructor: locate node, cloneNode(true)"]
Ctor --> Init["#init()"]
Init --> Before["when.before_render()"]
Before -->|"returns false"| Cancel["render cancelled"]
Before -->|"continue"| SetDOM["#SET_DOM(body, data) — recursive walk"]
SetDOM --> After["when.rendered()"]
app.renew(body) restores the DOM from the cloned #model (replaceChildren(...this.#model.children)), merges any provided data/event/when keys into the existing instance state (unmentioned keys keep their prior value), and re-runs #init(). This is the only way an RJS instance re-renders — there is no automatic reactivity or dirty-checking.
before_render returning false cancels the pass, so rendered never runs and the template text stays as written:
<div id="app"><p>{{ msg }}</p></div>
<script>
"#app".RJS({
data: { msg: "rendered" },
when: {
before_render: () => {
console.log("before_render");
return false;
},
rendered: () => console.log("rendered")
}
});
console.log("#app".$.textContent);
</script>
Non-Reactivity Is Deliberate
Every commented-out block in RJS.ts (#dataListener, the Object.defineProperty getter/setter pairs) is a removed reactive-observer mechanism. The shipped design renders once per #init() call and requires an explicit renew() to update — this trade-off is what the README describes as removing "the overhead of automatic reactive watching."
Related Topics
- Directive Parsing: the order
#SET_DOMapplies directives in, and what each directive does - Text Interpolation: how
{{ }}resolves values and helpers