@zoijs/core 1.3.2 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +117 -103
- package/README.md +154 -154
- package/package.json +67 -63
- package/src/core/each.js +24 -24
- package/src/core/renderer.js +473 -470
- package/src/devtools.d.ts +56 -0
- package/src/index.d.ts +185 -185
- package/src/reactivity/core.js +7 -0
- package/src/reactivity/devtools.js +77 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,103 +1,117 @@
|
|
|
1
|
-
# Changelog
|
|
2
|
-
|
|
3
|
-
All notable changes to Zoijs are documented here. The format is based on
|
|
4
|
-
[Keep a Changelog](https://keepachangelog.com/), and Zoijs follows
|
|
5
|
-
[Semantic Versioning](https://semver.org/) (see `VERSIONING.md`).
|
|
6
|
-
|
|
7
|
-
## [1.
|
|
8
|
-
|
|
9
|
-
###
|
|
10
|
-
- **
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
-
|
|
83
|
-
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to Zoijs are documented here. The format is based on
|
|
4
|
+
[Keep a Changelog](https://keepachangelog.com/), and Zoijs follows
|
|
5
|
+
[Semantic Versioning](https://semver.org/) (see `VERSIONING.md`).
|
|
6
|
+
|
|
7
|
+
## [1.4.0] — 2026-06-26
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
- **Devtools inspection hook** (`@zoijs/core/devtools`). A new, dev-only, read-only
|
|
11
|
+
seam that lets an inspector — [`@zoijs/devtools`](https://www.npmjs.com/package/@zoijs/devtools)
|
|
12
|
+
or a browser extension — observe the reactive graph: states, computeds, effects,
|
|
13
|
+
the edges between them, and **which DOM node each binding updates**. It's reached
|
|
14
|
+
through a dedicated subpath (`import { attachInspector } from "@zoijs/core/devtools"`),
|
|
15
|
+
so the learnable **nine-function** main surface is unchanged. The hook is off by
|
|
16
|
+
default (a single null check until something attaches), never instruments the hot
|
|
17
|
+
read path (`.get()`), and is a no-op under `configure({ dev: false })` — so a
|
|
18
|
+
production app pays no measurable cost and exposes nothing. See
|
|
19
|
+
[RFC 0005](docs/rfcs/0005-devtools-hook.md).
|
|
20
|
+
|
|
21
|
+
## [1.3.2] — 2026-06-26
|
|
22
|
+
|
|
23
|
+
### Fixed
|
|
24
|
+
- **Focus is preserved across a keyed reorder.** The 1.3.1 minimal-move change can
|
|
25
|
+
move the subtree that holds the focused element, which blurs it in browsers.
|
|
26
|
+
`each` now captures focus + caret position before reordering and restores them
|
|
27
|
+
after, so reordering a list never steals focus or selection — whichever nodes
|
|
28
|
+
happen to move. Verified in Chromium, Firefox, and WebKit
|
|
29
|
+
(`browser-tests/regression.spec.js`).
|
|
30
|
+
|
|
31
|
+
## [1.3.1] — 2026-06-26
|
|
32
|
+
|
|
33
|
+
### Performance
|
|
34
|
+
- **Minimal DOM moves in `each`.** Keyed-list reconciliation now uses a
|
|
35
|
+
longest-increasing-subsequence pass, so a reorder moves the **fewest nodes
|
|
36
|
+
possible** — moving one item across a list is a single DOM move (it could be up
|
|
37
|
+
to N before). No API or behavior change; the final order is identical and reused
|
|
38
|
+
nodes keep their identity (focus, input values, and scroll survive reorders).
|
|
39
|
+
Proven by move-count tests (`tests/lis.test.js`); numbers in `bench/`.
|
|
40
|
+
|
|
41
|
+
## [1.3.0] — 2026-06-26
|
|
42
|
+
|
|
43
|
+
### Added
|
|
44
|
+
- **`boundary(child, fallback)`.** A render-time error boundary: it renders
|
|
45
|
+
`child`, and if `child` throws **synchronously while building its markup** (a
|
|
46
|
+
setup/render error that would otherwise break the whole `mount`), it disposes the
|
|
47
|
+
partial work — so an `effect` created before the throw can't leak — and renders
|
|
48
|
+
`fallback` (a value, or `(error) => value`) instead. Catches synchronous
|
|
49
|
+
setup/render throws only; errors in reactive *updates* are already contained per
|
|
50
|
+
binding, and *async* errors belong to `@zoijs/resource` / `@zoijs/action`'s
|
|
51
|
+
`error()` state. Logs in dev, silent in production. The public surface is now
|
|
52
|
+
**nine** functions (additive MINOR). See [RFC 0004](docs/rfcs/0004-error-boundary.md).
|
|
53
|
+
|
|
54
|
+
## [1.2.0] — 2026-06-26
|
|
55
|
+
|
|
56
|
+
### Added
|
|
57
|
+
- **`effect(fn)`.** A public reactive effect — runs a side effect immediately and
|
|
58
|
+
re-runs whenever a reactive value it reads changes (automatic dependency
|
|
59
|
+
tracking, microtask-batched). The function may return a cleanup that runs before
|
|
60
|
+
the next run and on dispose (same convention as a `ref`); `effect` auto-disposes
|
|
61
|
+
with its owner (component / list item) and returns `{ dispose }` for early
|
|
62
|
+
teardown. This is the public completion of the reactive trio (`createState` /
|
|
63
|
+
`computed` / `effect`) — the engine already used it internally for bindings. Use
|
|
64
|
+
it for side effects *outside* the view (persist on change, sync `document.title`,
|
|
65
|
+
drive a non-Zoijs widget); for on-screen content, keep using a binding
|
|
66
|
+
(`${() => …}`). The public surface is now **eight** functions (additive MINOR per
|
|
67
|
+
`VERSIONING.md`). See [RFC 0003](docs/rfcs/0003-effect-and-svg.md).
|
|
68
|
+
|
|
69
|
+
### Notes
|
|
70
|
+
- The **`svg`** helper considered alongside `effect` was **deferred**: templates
|
|
71
|
+
rooted at `<svg>` already render correctly, and only dynamic-SVG *composition* is
|
|
72
|
+
affected — a minority need. See [RFC 0003](docs/rfcs/0003-effect-and-svg.md) §6.
|
|
73
|
+
|
|
74
|
+
## [1.1.0] — 2026-06-25
|
|
75
|
+
|
|
76
|
+
### Added
|
|
77
|
+
- **Element refs.** A new `ref` binding gives you the rendered DOM element:
|
|
78
|
+
`html\`<input ref=${(el) => el.focus()} />\``. The callback runs once, just after
|
|
79
|
+
the element is inserted (so `focus`/`scroll`/`measure`/`canvas` work), is not
|
|
80
|
+
reactive, and may return a cleanup function that runs on unmount or list-item
|
|
81
|
+
removal. Works inside keyed `each` lists. Non-function values are ignored with a
|
|
82
|
+
dev-mode warning and never become a DOM attribute. No new export — `ref` is a
|
|
83
|
+
binding semantic, so the seven-function public surface is unchanged (additive
|
|
84
|
+
MINOR per `VERSIONING.md`). See [Element refs](docs/concepts/refs.md) and
|
|
85
|
+
[RFC 0001](docs/rfcs/0001-element-refs.md).
|
|
86
|
+
|
|
87
|
+
## [1.0.0] — 2026-06-24
|
|
88
|
+
|
|
89
|
+
First stable release. The public API is frozen at seven functions.
|
|
90
|
+
|
|
91
|
+
### Public API
|
|
92
|
+
- `html` — tagged-template renderer (no JSX, no build step).
|
|
93
|
+
- `mount(component, target)` → `unmount()`.
|
|
94
|
+
- `createState(value)` → `{ get, set, peek }`.
|
|
95
|
+
- `computed(fn)` → `{ get, peek }` — lazy, cached, value-gated.
|
|
96
|
+
- `each(items, keyFn, renderFn)` — keyed list reconciliation.
|
|
97
|
+
- `configure({ dev })` — development/production mode.
|
|
98
|
+
- `onCleanup(fn)` — teardown for components and list items.
|
|
99
|
+
|
|
100
|
+
### Features
|
|
101
|
+
- Fine-grained, direct DOM updates (no Virtual DOM); setup runs once.
|
|
102
|
+
- Push-pull reactive core with automatic dependency tracking and microtask batching.
|
|
103
|
+
- Owner-scoped cleanup; deterministic teardown on unmount and list-item removal.
|
|
104
|
+
- Context-aware template parser (quoted/unquoted/partial/multi-hole attributes,
|
|
105
|
+
boolean/URL/aria/data attributes, SVG, nested templates and lists).
|
|
106
|
+
- Secure by default: inert text, URL-scheme allowlist (control-char resistant),
|
|
107
|
+
`data:` raster-image rules, `on*`/`srcdoc` blocked, function-only handlers,
|
|
108
|
+
no `eval`, CSP- and Trusted-Types-friendly.
|
|
109
|
+
- TypeScript definitions with generics for state/computed/lists.
|
|
110
|
+
|
|
111
|
+
### Tooling
|
|
112
|
+
- 100+ unit/DOM tests (jsdom), real-browser tests on Chromium/Firefox/WebKit
|
|
113
|
+
(Playwright), and TypeScript type tests.
|
|
114
|
+
- No build step required at any point.
|
|
115
|
+
|
|
116
|
+
[1.1.0]: https://github.com/Zoijs/zoijs/releases/tag/core-v1.1.0
|
|
117
|
+
[1.0.0]: https://github.com/Zoijs/zoijs/releases/tag/core-v1.0.0
|
package/README.md
CHANGED
|
@@ -1,154 +1,154 @@
|
|
|
1
|
-
# Zoijs
|
|
2
|
-
|
|
3
|
-
A lightweight frontend framework you don't have to learn before you use it — **plain HTML, CSS, and JavaScript**, no JSX, no build step, no Virtual DOM.
|
|
4
|
-
|
|
5
|
-
**[Documentation](https://zoijs.dev)** · **[GitHub](https://github.com/Zoijs)** · **[npm](https://www.npmjs.com/package/@zoijs/core)**
|
|
6
|
-
|
|
7
|
-
```bash
|
|
8
|
-
npm install @zoijs/core
|
|
9
|
-
```
|
|
10
|
-
|
|
11
|
-
```js
|
|
12
|
-
import { html, mount, createState } from "@zoijs/core";
|
|
13
|
-
|
|
14
|
-
function Counter() {
|
|
15
|
-
const count = createState(0);
|
|
16
|
-
return html`<button onclick=${() => count.set(count.get() + 1)}>${() => count.get()}</button>`;
|
|
17
|
-
}
|
|
18
|
-
mount(Counter, "#app");
|
|
19
|
-
```
|
|
20
|
-
|
|
21
|
-
## 📚 Documentation
|
|
22
|
-
|
|
23
|
-
**New here? Start at [zoijs.dev](https://zoijs.dev) (or the [docs folder](docs/README.md)) — designed to get you productive in under 30 minutes.**
|
|
24
|
-
|
|
25
|
-
- [Installation](docs/installation.md) · [Your First App](docs/first-app.md) · [Core Concepts](docs/concepts/core-concepts.md)
|
|
26
|
-
- [Tutorials](docs/README.md#tutorials-build-something) · [API Reference](docs/api-reference.md) · [Examples](docs/examples.md)
|
|
27
|
-
- [Troubleshooting](docs/troubleshooting.md) · [FAQ](docs/faq.md) · [Migrating from React/Vue/Solid/Lit/vanilla](docs/README.md#coming-from-another-framework)
|
|
28
|
-
|
|
29
|
-
---
|
|
30
|
-
|
|
31
|
-
## Mission
|
|
32
|
-
|
|
33
|
-
Make building modern web applications feel as approachable as writing plain HTML, CSS, and JavaScript — so that any developer, on day one, can ship real software without first learning a framework.
|
|
34
|
-
|
|
35
|
-
The framework should disappear into the skills developers already have. The whole mental model is three verbs: **write a function that returns `html`, put `createState` values in it, `mount` it.**
|
|
36
|
-
|
|
37
|
-
## Goals
|
|
38
|
-
|
|
39
|
-
- **Beginner-friendly** — concepts you already know from vanilla JS/HTML/CSS.
|
|
40
|
-
- **No build step** — runs from a single `<script type="module">`.
|
|
41
|
-
- **No Virtual DOM** — fine-grained, direct DOM updates; only what changed is touched.
|
|
42
|
-
- **Minimal runtime** — the browser does the heavy lifting (native `<template>`, `cloneNode`, events).
|
|
43
|
-
- **Secure by default** — inert text rendering, URL-scheme guards, handler references (never strings), no `eval`.
|
|
44
|
-
- **Small & readable** — a junior developer can read the source.
|
|
45
|
-
|
|
46
|
-
See [`docs/Phase-1-MVP-Spec.md`](docs/Phase-1-MVP-Spec.md) for the full specification.
|
|
47
|
-
|
|
48
|
-
## Setup
|
|
49
|
-
|
|
50
|
-
No build step is required — the framework is plain ES modules.
|
|
51
|
-
|
|
52
|
-
```bash
|
|
53
|
-
# from the framework/ directory
|
|
54
|
-
|
|
55
|
-
# run the counter example (serves the project root over http so ES modules resolve)
|
|
56
|
-
npm run dev
|
|
57
|
-
# then open: http://localhost:7310/examples/counter/ (keep the trailing slash)
|
|
58
|
-
|
|
59
|
-
# run the tests (DOM tests run automatically via jsdom)
|
|
60
|
-
npm test
|
|
61
|
-
```
|
|
62
|
-
|
|
63
|
-
> Tips:
|
|
64
|
-
> - ES module imports need to be served over `http://`, not opened as a `file://` path. `npm run dev` handles that.
|
|
65
|
-
> - Use the **trailing slash** on the example URL (`/examples/counter/`). Without it, some static servers resolve `./app.js` against the wrong directory and the app won't load.
|
|
66
|
-
|
|
67
|
-
## Testing
|
|
68
|
-
|
|
69
|
-
| Command | What it runs |
|
|
70
|
-
|---|---|
|
|
71
|
-
| `npm test` | Unit + DOM tests via jsdom (fast, no browser) |
|
|
72
|
-
| `npm run test:unit` | Pure-logic tests only (no DOM) |
|
|
73
|
-
| `npm run test:types` | TypeScript type-checks (`tsc --noEmit`) |
|
|
74
|
-
| `npm run test:browser` | Real-browser tests in Chromium, Firefox, WebKit (Playwright) |
|
|
75
|
-
|
|
76
|
-
Browser tests live in `browser-tests/` and run the example apps plus framework regressions against real engines. First-time setup downloads the browsers:
|
|
77
|
-
|
|
78
|
-
```bash
|
|
79
|
-
npm install
|
|
80
|
-
npx playwright install chromium firefox webkit
|
|
81
|
-
npm run test:browser
|
|
82
|
-
```
|
|
83
|
-
|
|
84
|
-
Playwright starts a static server automatically (`npx serve` on port 7310) — still no build step.
|
|
85
|
-
|
|
86
|
-
## Browser support
|
|
87
|
-
|
|
88
|
-
Modern evergreen browsers. Verified automatically (Playwright) in:
|
|
89
|
-
|
|
90
|
-
| Browser | Engine | Status |
|
|
91
|
-
|---|---|---|
|
|
92
|
-
| Chrome / Edge | Chromium | ✅ tested |
|
|
93
|
-
| Firefox | Gecko | ✅ tested |
|
|
94
|
-
| Safari | WebKit | ✅ tested |
|
|
95
|
-
|
|
96
|
-
Relies on baseline-modern platform APIs: ES modules, `<template>`, `TreeWalker`, `Proxy`, `queueMicrotask`, `replaceChildren`, `addEventListener`, `setAttributeNS`. No IE support, no transpilation, no polyfills.
|
|
97
|
-
|
|
98
|
-
## Public API
|
|
99
|
-
|
|
100
|
-
```js
|
|
101
|
-
import { html, mount, createState, computed, each, configure, onCleanup } from "@zoijs/core";
|
|
102
|
-
```
|
|
103
|
-
|
|
104
|
-
- `html` — tagged template; parsed once, cached.
|
|
105
|
-
- `mount(component, target)` — render a component; returns `unmount()`.
|
|
106
|
-
- `createState(value)` — a reactive value (`get` / `set` / `peek`).
|
|
107
|
-
- `computed(fn)` — a lazy, cached, **value-gated** derived value (`get` / `peek`).
|
|
108
|
-
- `each(itemsFn, keyFn, renderFn)` — keyed list rendering (reuse / move / remove nodes).
|
|
109
|
-
- `configure({ dev })` — toggle development warnings (default `dev: true`).
|
|
110
|
-
- `onCleanup(fn)` — register teardown for a component or list item (timers, subscriptions).
|
|
111
|
-
|
|
112
|
-
See the [Documentation site](docs/README.md) for the full guide, tutorials, and API reference.
|
|
113
|
-
|
|
114
|
-
**TypeScript:** ships type definitions ([`src/index.d.ts`](src/index.d.ts)) for autocomplete and optional type-checking — JS-first, no build step required. `createState<T>`, `computed<T>`, and `each<T>` are generic. Type-check with `npm run test:types`.
|
|
115
|
-
|
|
116
|
-
## What's built
|
|
117
|
-
|
|
118
|
-
- Fine-grained text/attribute bindings — `${() => state.get()}` updates one node in place; setup runs once (no re-render).
|
|
119
|
-
- Native events, secure-by-default rendering (inert text, URL-scheme guards, no `eval`).
|
|
120
|
-
- `computed()` derived values — lazy, cached, nestable, and **value-gated** (unchanged results don't wake downstream).
|
|
121
|
-
- `each()` keyed list reconciliation — preserves focus / input / scroll on reorder.
|
|
122
|
-
- Microtask batching, push-pull dependency tracking, **owner-scoped cleanup** (unmount and removed items dispose their subscriptions).
|
|
123
|
-
- Production mode via `configure({ dev: false })` — no build step.
|
|
124
|
-
- Safety: self-triggering effects are warned + stopped; a throwing binding doesn't break others.
|
|
125
|
-
|
|
126
|
-
**Out of scope (by design):** router, CLI, plugins, SSR, global store, TypeScript-first setup, Virtual DOM.
|
|
127
|
-
|
|
128
|
-
## Project Structure
|
|
129
|
-
|
|
130
|
-
```
|
|
131
|
-
framework/
|
|
132
|
-
package.json
|
|
133
|
-
README.md
|
|
134
|
-
src/
|
|
135
|
-
core/
|
|
136
|
-
html.js # html() — parse template into a cached blueprint
|
|
137
|
-
mount.js # mount() — entry point; instantiate + attach + cleanup
|
|
138
|
-
renderer.js # internal: bind dynamic slots, apply fine-grained updates
|
|
139
|
-
reactivity/
|
|
140
|
-
state.js # createState() + internal dependency tracking
|
|
141
|
-
utils/
|
|
142
|
-
dom.js # small native-DOM helpers
|
|
143
|
-
security.js # escaping, URL-scheme allowlist, attribute-name guards
|
|
144
|
-
index.js # public entry — re-exports html, mount, createState
|
|
145
|
-
examples/
|
|
146
|
-
counter/ # the first working app
|
|
147
|
-
tests/ # basic unit tests (node --test)
|
|
148
|
-
docs/
|
|
149
|
-
Phase-1-MVP-Spec.md
|
|
150
|
-
```
|
|
151
|
-
|
|
152
|
-
## License
|
|
153
|
-
|
|
154
|
-
MIT
|
|
1
|
+
# Zoijs
|
|
2
|
+
|
|
3
|
+
A lightweight frontend framework you don't have to learn before you use it — **plain HTML, CSS, and JavaScript**, no JSX, no build step, no Virtual DOM.
|
|
4
|
+
|
|
5
|
+
**[Documentation](https://zoijs.dev)** · **[GitHub](https://github.com/Zoijs)** · **[npm](https://www.npmjs.com/package/@zoijs/core)**
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @zoijs/core
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
```js
|
|
12
|
+
import { html, mount, createState } from "@zoijs/core";
|
|
13
|
+
|
|
14
|
+
function Counter() {
|
|
15
|
+
const count = createState(0);
|
|
16
|
+
return html`<button onclick=${() => count.set(count.get() + 1)}>${() => count.get()}</button>`;
|
|
17
|
+
}
|
|
18
|
+
mount(Counter, "#app");
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## 📚 Documentation
|
|
22
|
+
|
|
23
|
+
**New here? Start at [zoijs.dev](https://zoijs.dev) (or the [docs folder](docs/README.md)) — designed to get you productive in under 30 minutes.**
|
|
24
|
+
|
|
25
|
+
- [Installation](docs/installation.md) · [Your First App](docs/first-app.md) · [Core Concepts](docs/concepts/core-concepts.md)
|
|
26
|
+
- [Tutorials](docs/README.md#tutorials-build-something) · [API Reference](docs/api-reference.md) · [Examples](docs/examples.md)
|
|
27
|
+
- [Troubleshooting](docs/troubleshooting.md) · [FAQ](docs/faq.md) · [Migrating from React/Vue/Solid/Lit/vanilla](docs/README.md#coming-from-another-framework)
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## Mission
|
|
32
|
+
|
|
33
|
+
Make building modern web applications feel as approachable as writing plain HTML, CSS, and JavaScript — so that any developer, on day one, can ship real software without first learning a framework.
|
|
34
|
+
|
|
35
|
+
The framework should disappear into the skills developers already have. The whole mental model is three verbs: **write a function that returns `html`, put `createState` values in it, `mount` it.**
|
|
36
|
+
|
|
37
|
+
## Goals
|
|
38
|
+
|
|
39
|
+
- **Beginner-friendly** — concepts you already know from vanilla JS/HTML/CSS.
|
|
40
|
+
- **No build step** — runs from a single `<script type="module">`.
|
|
41
|
+
- **No Virtual DOM** — fine-grained, direct DOM updates; only what changed is touched.
|
|
42
|
+
- **Minimal runtime** — the browser does the heavy lifting (native `<template>`, `cloneNode`, events).
|
|
43
|
+
- **Secure by default** — inert text rendering, URL-scheme guards, handler references (never strings), no `eval`.
|
|
44
|
+
- **Small & readable** — a junior developer can read the source.
|
|
45
|
+
|
|
46
|
+
See [`docs/Phase-1-MVP-Spec.md`](docs/Phase-1-MVP-Spec.md) for the full specification.
|
|
47
|
+
|
|
48
|
+
## Setup
|
|
49
|
+
|
|
50
|
+
No build step is required — the framework is plain ES modules.
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
# from the framework/ directory
|
|
54
|
+
|
|
55
|
+
# run the counter example (serves the project root over http so ES modules resolve)
|
|
56
|
+
npm run dev
|
|
57
|
+
# then open: http://localhost:7310/examples/counter/ (keep the trailing slash)
|
|
58
|
+
|
|
59
|
+
# run the tests (DOM tests run automatically via jsdom)
|
|
60
|
+
npm test
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
> Tips:
|
|
64
|
+
> - ES module imports need to be served over `http://`, not opened as a `file://` path. `npm run dev` handles that.
|
|
65
|
+
> - Use the **trailing slash** on the example URL (`/examples/counter/`). Without it, some static servers resolve `./app.js` against the wrong directory and the app won't load.
|
|
66
|
+
|
|
67
|
+
## Testing
|
|
68
|
+
|
|
69
|
+
| Command | What it runs |
|
|
70
|
+
|---|---|
|
|
71
|
+
| `npm test` | Unit + DOM tests via jsdom (fast, no browser) |
|
|
72
|
+
| `npm run test:unit` | Pure-logic tests only (no DOM) |
|
|
73
|
+
| `npm run test:types` | TypeScript type-checks (`tsc --noEmit`) |
|
|
74
|
+
| `npm run test:browser` | Real-browser tests in Chromium, Firefox, WebKit (Playwright) |
|
|
75
|
+
|
|
76
|
+
Browser tests live in `browser-tests/` and run the example apps plus framework regressions against real engines. First-time setup downloads the browsers:
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
npm install
|
|
80
|
+
npx playwright install chromium firefox webkit
|
|
81
|
+
npm run test:browser
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Playwright starts a static server automatically (`npx serve` on port 7310) — still no build step.
|
|
85
|
+
|
|
86
|
+
## Browser support
|
|
87
|
+
|
|
88
|
+
Modern evergreen browsers. Verified automatically (Playwright) in:
|
|
89
|
+
|
|
90
|
+
| Browser | Engine | Status |
|
|
91
|
+
|---|---|---|
|
|
92
|
+
| Chrome / Edge | Chromium | ✅ tested |
|
|
93
|
+
| Firefox | Gecko | ✅ tested |
|
|
94
|
+
| Safari | WebKit | ✅ tested |
|
|
95
|
+
|
|
96
|
+
Relies on baseline-modern platform APIs: ES modules, `<template>`, `TreeWalker`, `Proxy`, `queueMicrotask`, `replaceChildren`, `addEventListener`, `setAttributeNS`. No IE support, no transpilation, no polyfills.
|
|
97
|
+
|
|
98
|
+
## Public API
|
|
99
|
+
|
|
100
|
+
```js
|
|
101
|
+
import { html, mount, createState, computed, each, configure, onCleanup } from "@zoijs/core";
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
- `html` — tagged template; parsed once, cached.
|
|
105
|
+
- `mount(component, target)` — render a component; returns `unmount()`.
|
|
106
|
+
- `createState(value)` — a reactive value (`get` / `set` / `peek`).
|
|
107
|
+
- `computed(fn)` — a lazy, cached, **value-gated** derived value (`get` / `peek`).
|
|
108
|
+
- `each(itemsFn, keyFn, renderFn)` — keyed list rendering (reuse / move / remove nodes).
|
|
109
|
+
- `configure({ dev })` — toggle development warnings (default `dev: true`).
|
|
110
|
+
- `onCleanup(fn)` — register teardown for a component or list item (timers, subscriptions).
|
|
111
|
+
|
|
112
|
+
See the [Documentation site](docs/README.md) for the full guide, tutorials, and API reference.
|
|
113
|
+
|
|
114
|
+
**TypeScript:** ships type definitions ([`src/index.d.ts`](src/index.d.ts)) for autocomplete and optional type-checking — JS-first, no build step required. `createState<T>`, `computed<T>`, and `each<T>` are generic. Type-check with `npm run test:types`.
|
|
115
|
+
|
|
116
|
+
## What's built
|
|
117
|
+
|
|
118
|
+
- Fine-grained text/attribute bindings — `${() => state.get()}` updates one node in place; setup runs once (no re-render).
|
|
119
|
+
- Native events, secure-by-default rendering (inert text, URL-scheme guards, no `eval`).
|
|
120
|
+
- `computed()` derived values — lazy, cached, nestable, and **value-gated** (unchanged results don't wake downstream).
|
|
121
|
+
- `each()` keyed list reconciliation — preserves focus / input / scroll on reorder.
|
|
122
|
+
- Microtask batching, push-pull dependency tracking, **owner-scoped cleanup** (unmount and removed items dispose their subscriptions).
|
|
123
|
+
- Production mode via `configure({ dev: false })` — no build step.
|
|
124
|
+
- Safety: self-triggering effects are warned + stopped; a throwing binding doesn't break others.
|
|
125
|
+
|
|
126
|
+
**Out of scope (by design):** router, CLI, plugins, SSR, global store, TypeScript-first setup, Virtual DOM.
|
|
127
|
+
|
|
128
|
+
## Project Structure
|
|
129
|
+
|
|
130
|
+
```
|
|
131
|
+
framework/
|
|
132
|
+
package.json
|
|
133
|
+
README.md
|
|
134
|
+
src/
|
|
135
|
+
core/
|
|
136
|
+
html.js # html() — parse template into a cached blueprint
|
|
137
|
+
mount.js # mount() — entry point; instantiate + attach + cleanup
|
|
138
|
+
renderer.js # internal: bind dynamic slots, apply fine-grained updates
|
|
139
|
+
reactivity/
|
|
140
|
+
state.js # createState() + internal dependency tracking
|
|
141
|
+
utils/
|
|
142
|
+
dom.js # small native-DOM helpers
|
|
143
|
+
security.js # escaping, URL-scheme allowlist, attribute-name guards
|
|
144
|
+
index.js # public entry — re-exports html, mount, createState
|
|
145
|
+
examples/
|
|
146
|
+
counter/ # the first working app
|
|
147
|
+
tests/ # basic unit tests (node --test)
|
|
148
|
+
docs/
|
|
149
|
+
Phase-1-MVP-Spec.md
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
## License
|
|
153
|
+
|
|
154
|
+
MIT
|