@llui/dom 0.1.0 → 0.2.1
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/dist/binding-registry.d.ts +10 -0
- package/dist/binding-registry.d.ts.map +1 -0
- package/dist/binding-registry.js +96 -0
- package/dist/binding-registry.js.map +1 -0
- package/dist/binding.d.ts +22 -0
- package/dist/binding.d.ts.map +1 -1
- package/dist/binding.js +56 -5
- package/dist/binding.js.map +1 -1
- package/dist/build-flags.d.ts +25 -0
- package/dist/build-flags.d.ts.map +1 -0
- package/dist/build-flags.js +18 -0
- package/dist/build-flags.js.map +1 -0
- package/dist/compose.d.ts +27 -21
- package/dist/compose.d.ts.map +1 -1
- package/dist/compose.js +19 -13
- package/dist/compose.js.map +1 -1
- package/dist/el-split.d.ts.map +1 -1
- package/dist/el-split.js +8 -4
- package/dist/el-split.js.map +1 -1
- package/dist/el-template.d.ts.map +1 -1
- package/dist/el-template.js +15 -18
- package/dist/el-template.js.map +1 -1
- package/dist/elements.d.ts.map +1 -1
- package/dist/elements.js +12 -4
- package/dist/elements.js.map +1 -1
- package/dist/hmr.js +9 -4
- package/dist/hmr.js.map +1 -1
- package/dist/index.d.ts +4 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/internal/test-component-builder.d.ts +21 -0
- package/dist/internal/test-component-builder.d.ts.map +1 -0
- package/dist/internal/test-component-builder.js +65 -0
- package/dist/internal/test-component-builder.js.map +1 -0
- package/dist/internal.d.ts +1 -0
- package/dist/internal.d.ts.map +1 -1
- package/dist/internal.js +1 -0
- package/dist/internal.js.map +1 -1
- package/dist/lifetime.d.ts.map +1 -1
- package/dist/lifetime.js +16 -3
- package/dist/lifetime.js.map +1 -1
- package/dist/mount.d.ts +1 -0
- package/dist/mount.d.ts.map +1 -1
- package/dist/mount.js +32 -22
- package/dist/mount.js.map +1 -1
- package/dist/primitives/branch.d.ts.map +1 -1
- package/dist/primitives/branch.js +29 -16
- package/dist/primitives/branch.js.map +1 -1
- package/dist/primitives/client-only.d.ts +1 -1
- package/dist/primitives/client-only.d.ts.map +1 -1
- package/dist/primitives/client-only.js +3 -3
- package/dist/primitives/client-only.js.map +1 -1
- package/dist/primitives/context.d.ts.map +1 -1
- package/dist/primitives/context.js +6 -3
- package/dist/primitives/context.js.map +1 -1
- package/dist/primitives/each.d.ts.map +1 -1
- package/dist/primitives/each.js +121 -74
- package/dist/primitives/each.js.map +1 -1
- package/dist/primitives/foreign.js +2 -1
- package/dist/primitives/foreign.js.map +1 -1
- package/dist/primitives/lazy.d.ts +1 -1
- package/dist/primitives/lazy.d.ts.map +1 -1
- package/dist/primitives/lazy.js +11 -5
- package/dist/primitives/lazy.js.map +1 -1
- package/dist/primitives/portal.js +2 -1
- package/dist/primitives/portal.js.map +1 -1
- package/dist/primitives/sample.d.ts +2 -2
- package/dist/primitives/sample.d.ts.map +1 -1
- package/dist/primitives/sample.js +24 -18
- package/dist/primitives/sample.js.map +1 -1
- package/dist/primitives/track.d.ts +54 -0
- package/dist/primitives/track.d.ts.map +1 -0
- package/dist/primitives/track.js +54 -0
- package/dist/primitives/track.js.map +1 -0
- package/dist/primitives/virtual-each.js +4 -2
- package/dist/primitives/virtual-each.js.map +1 -1
- package/dist/render-context.d.ts +27 -1
- package/dist/render-context.d.ts.map +1 -1
- package/dist/render-context.js +82 -33
- package/dist/render-context.js.map +1 -1
- package/dist/ssr.d.ts.map +1 -1
- package/dist/ssr.js +5 -3
- package/dist/ssr.js.map +1 -1
- package/dist/types.d.ts +8 -13
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/update-loop.d.ts +1 -0
- package/dist/update-loop.d.ts.map +1 -1
- package/dist/update-loop.js +115 -50
- package/dist/update-loop.js.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sample.js","sourceRoot":"","sources":["../../src/primitives/sample.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAA;AAExE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0DG;AACH,MAAM,UAAU,MAAM,CAAO,QAAqB;IAChD,MAAM,QAAQ,GAAG,eAAe,EAAE,CAAA;IAClC,IAAI,QAAQ,KAAK,IAAI,EAAE,CAAC;QACtB,MAAM,IAAI,KAAK,CACb,kDAAkD,QAAQ,IAAI;
|
|
1
|
+
{"version":3,"file":"sample.js","sourceRoot":"","sources":["../../src/primitives/sample.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAA;AAExE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0DG;AACH,MAAM,UAAU,MAAM,CAAO,QAAqB;IAChD,MAAM,QAAQ,GAAG,eAAe,EAAE,CAAA;IAClC,IAAI,QAAQ,KAAK,IAAI,EAAE,CAAC;QACtB,+DAA+D;QAC/D,iEAAiE;QACjE,2CAA2C;QAC3C,IAAI,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,CAAC;YACzB,MAAM,IAAI,KAAK,CACb,kDAAkD,QAAQ,IAAI;gBAC5D,uEAAuE;gBACvE,yEAAyE;gBACzE,yCAAyC;gBACzC,uEAAuE;gBACvE,mEAAmE;gBACnE,gBAAgB;gBAChB,YAAY;gBACZ,6BAA6B;gBAC7B,0DAA0D;gBAC1D,QAAQ;gBACR,gBAAgB;gBAChB,YAAY;gBACZ,+DAA+D;gBAC/D,8CAA8C;gBAC9C,MAAM,CACT,CAAA;QACH,CAAC;QACD,MAAM,IAAI,KAAK,CAAC,sCAAsC,QAAQ,EAAE,CAAC,CAAA;IACnE,CAAC;IACD,MAAM,GAAG,GAAG,gBAAgB,CAAC,QAAQ,CAAC,CAAA;IACtC,OAAO,QAAQ,CAAC,GAAG,CAAC,KAAU,CAAC,CAAA;AACjC,CAAC","sourcesContent":["import { getRenderContext, currentAccessor } from '../render-context.js'\n\n/**\n * Read current state inside a render context and return the result of\n * `selector(state)`. No binding is created, no mask is assigned — this\n * is a one-shot imperative read at view-construction time.\n *\n * **Don't use for variable-length lists.** Wrapping a list-render in\n * `sample` looks idiomatic but silently breaks reactivity: the\n * `.map(...)` runs once at construction, captures the row objects in\n * closure, and never re-runs when state updates in place. The cells\n * inside the captured rows show stale data; only a full structural\n * rebuild (e.g. a parent `branch` swapping arms) will refresh them.\n * Use `each` + `ItemAccessor` instead — see the \"List of editable\n * rows\" recipe in the cookbook.\n *\n * **Don't use inside an accessor** (`each().key`, `each().items`,\n * `branch().on`, `show().when`, `scope().on`, `foreign().props`, or a\n * binding accessor like `text(s => …)`).\n * Accessors must be pure functions of their parameter — the compiler's\n * mask analysis only sees reads of the parameter, so a `sample()` read\n * is invisible. The result is a hidden dependency that breaks\n * reconciliation: structural blocks gate out updates that should fire,\n * binding accessors miss state changes, and `key` callbacks create\n * keys that don't track outer state correctly.\n *\n * To depend on outer state inside an accessor, **lift it into the\n * accessor's parameter**. For `each().key` reading a sibling field\n * `rev`:\n *\n * ```ts\n * // ❌ wrong — sample() in key is invisible to mask gating\n * each({\n * items: (s) => s.items,\n * key: (it) => `${it.id}|${sample(s => s.rev)}`,\n * render: …,\n * })\n *\n * // ✅ right — bake outer state into items, key is pure of T\n * each({\n * items: (s) => s.items.map((it) => ({ it, rev: s.rev })),\n * key: (r) => `${r.it.id}|${r.rev}`,\n * render: …,\n * })\n * ```\n *\n * **Use for** passing a state snapshot to an imperative renderer\n * (foreign libraries, third-party canvas/svg builders), reading a\n * value to compute a static piece of structure that doesn't need to\n * react, or any case where a reactive binding would be semantically\n * wrong (e.g. capturing a value at *this exact moment* for a\n * one-shot side effect).\n *\n * Also exposed as `h.sample` on the View bag for destructure-from-`h`\n * ergonomics. The top-level import form works everywhere a render\n * context is live — including `each.render`, whose bag intentionally\n * does not carry View methods.\n *\n * Throws if called outside a render context, or from inside an\n * accessor.\n */\nexport function sample<S, R>(selector: (s: S) => R): R {\n const accessor = currentAccessor()\n if (accessor !== null) {\n // Long-form guidance ships only in dev — production apps still\n // throw to abort the broken update, but the diff-cleanup example\n // (~400 source bytes) is dev-only DX help.\n if (import.meta.env?.DEV) {\n throw new Error(\n `[LLui] sample() must not be called from inside ${accessor}. ` +\n `Accessors must be pure functions of their parameter — sample() reads ` +\n `state outside the parameter, which is invisible to the compiler's mask ` +\n `analysis and breaks reconciliation.\\n\\n` +\n `To depend on outer state, lift it into the accessor's parameter. For ` +\n `each().key reading a sibling field, bake it into the items map:\\n` +\n ` // ❌ wrong\\n` +\n ` each({\\n` +\n ` items: (s) => s.rows,\\n` +\n ` key: (it) => \\`\\${it.id}|\\${sample(s => s.rev)}\\`,\\n` +\n ` })\\n` +\n ` // ✅ right\\n` +\n ` each({\\n` +\n ` items: (s) => s.rows.map((it) => ({ it, rev: s.rev })),\\n` +\n ` key: (r) => \\`\\${r.it.id}|\\${r.rev}\\`,\\n` +\n ` })`,\n )\n }\n throw new Error(`[LLui] sample() called from inside ${accessor}`)\n }\n const ctx = getRenderContext('sample')\n return selector(ctx.state as S)\n}\n"]}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `track()` — explicit reactivity declaration for paths the compiler
|
|
3
|
+
* cannot statically infer.
|
|
4
|
+
*
|
|
5
|
+
* ```ts
|
|
6
|
+
* track({ deps: (s) => [s.pluginRegistry, s.activePluginName] })
|
|
7
|
+
* ```
|
|
8
|
+
*
|
|
9
|
+
* At compile time `@llui/compiler` reads the `deps` accessor, folds its
|
|
10
|
+
* paths into the host component's `__prefixes` table, and **strips the
|
|
11
|
+
* entire call expression from the emitted output** (and removes the
|
|
12
|
+
* `track` import). A `track()` call in source produces zero bytes in the
|
|
13
|
+
* bundle and zero work at runtime.
|
|
14
|
+
*
|
|
15
|
+
* The runtime export below is the FULL_MASK fallback. If a
|
|
16
|
+
* `ComponentDef` somehow bypasses the compiler (hand-rolled, build that
|
|
17
|
+
* skipped the plugin), `track()` throws `LluiCompilerSkippedError` on
|
|
18
|
+
* first evaluation — pointing at the specific call site the user needs
|
|
19
|
+
* to either compile or remove. This is the alternative to silently
|
|
20
|
+
* degrading to FULL_MASK; explicit failure beats silent slowness.
|
|
21
|
+
*
|
|
22
|
+
* Use `track()` only when:
|
|
23
|
+
* - Plugin registries: `pluginRegistry[name].render(h, send)` where
|
|
24
|
+
* `name` is state.
|
|
25
|
+
* - Helpers returned by `useContext` chains where the provider lives
|
|
26
|
+
* two-plus files away.
|
|
27
|
+
* - Helpers stored in arrays and dispatched by index.
|
|
28
|
+
*
|
|
29
|
+
* For statically-typed view helpers in other files, the cross-file
|
|
30
|
+
* walker (v2b) resolves their reads directly — `track()` is unnecessary
|
|
31
|
+
* and the `llui/prefer-static-deps` lint rule will flag it. A clean
|
|
32
|
+
* codebase has zero `track()` calls.
|
|
33
|
+
*
|
|
34
|
+
* See `docs/proposals/v2-compiler/v2b.md` §3.
|
|
35
|
+
*/
|
|
36
|
+
export declare class LluiCompilerSkippedError extends Error {
|
|
37
|
+
constructor(message: string);
|
|
38
|
+
}
|
|
39
|
+
export interface TrackOptions<S> {
|
|
40
|
+
/**
|
|
41
|
+
* Declare the paths this component depends on. Called at most once at
|
|
42
|
+
* compile time; the compiler folds the returned paths into
|
|
43
|
+
* `__prefixes`. The runtime stub never invokes this function — if you
|
|
44
|
+
* see it running, the compiler skipped the file.
|
|
45
|
+
*/
|
|
46
|
+
deps: (s: S) => unknown[];
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Runtime stub. Throws on call to make it explicit when the compiler
|
|
50
|
+
* has been skipped — the alternative (silent FULL_MASK degradation) is
|
|
51
|
+
* a §0.5 wrong-by-default outcome.
|
|
52
|
+
*/
|
|
53
|
+
export declare function track<S>(_opts: TrackOptions<S>): void;
|
|
54
|
+
//# sourceMappingURL=track.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"track.d.ts","sourceRoot":"","sources":["../../src/primitives/track.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AAEH,qBAAa,wBAAyB,SAAQ,KAAK;gBACrC,OAAO,EAAE,MAAM;CAI5B;AAED,MAAM,WAAW,YAAY,CAAC,CAAC;IAC7B;;;;;OAKG;IACH,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC,KAAK,OAAO,EAAE,CAAA;CAC1B;AAED;;;;GAIG;AACH,wBAAgB,KAAK,CAAC,CAAC,EAAE,KAAK,EAAE,YAAY,CAAC,CAAC,CAAC,GAAG,IAAI,CAQrD"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `track()` — explicit reactivity declaration for paths the compiler
|
|
3
|
+
* cannot statically infer.
|
|
4
|
+
*
|
|
5
|
+
* ```ts
|
|
6
|
+
* track({ deps: (s) => [s.pluginRegistry, s.activePluginName] })
|
|
7
|
+
* ```
|
|
8
|
+
*
|
|
9
|
+
* At compile time `@llui/compiler` reads the `deps` accessor, folds its
|
|
10
|
+
* paths into the host component's `__prefixes` table, and **strips the
|
|
11
|
+
* entire call expression from the emitted output** (and removes the
|
|
12
|
+
* `track` import). A `track()` call in source produces zero bytes in the
|
|
13
|
+
* bundle and zero work at runtime.
|
|
14
|
+
*
|
|
15
|
+
* The runtime export below is the FULL_MASK fallback. If a
|
|
16
|
+
* `ComponentDef` somehow bypasses the compiler (hand-rolled, build that
|
|
17
|
+
* skipped the plugin), `track()` throws `LluiCompilerSkippedError` on
|
|
18
|
+
* first evaluation — pointing at the specific call site the user needs
|
|
19
|
+
* to either compile or remove. This is the alternative to silently
|
|
20
|
+
* degrading to FULL_MASK; explicit failure beats silent slowness.
|
|
21
|
+
*
|
|
22
|
+
* Use `track()` only when:
|
|
23
|
+
* - Plugin registries: `pluginRegistry[name].render(h, send)` where
|
|
24
|
+
* `name` is state.
|
|
25
|
+
* - Helpers returned by `useContext` chains where the provider lives
|
|
26
|
+
* two-plus files away.
|
|
27
|
+
* - Helpers stored in arrays and dispatched by index.
|
|
28
|
+
*
|
|
29
|
+
* For statically-typed view helpers in other files, the cross-file
|
|
30
|
+
* walker (v2b) resolves their reads directly — `track()` is unnecessary
|
|
31
|
+
* and the `llui/prefer-static-deps` lint rule will flag it. A clean
|
|
32
|
+
* codebase has zero `track()` calls.
|
|
33
|
+
*
|
|
34
|
+
* See `docs/proposals/v2-compiler/v2b.md` §3.
|
|
35
|
+
*/
|
|
36
|
+
export class LluiCompilerSkippedError extends Error {
|
|
37
|
+
constructor(message) {
|
|
38
|
+
super(message);
|
|
39
|
+
this.name = 'LluiCompilerSkippedError';
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Runtime stub. Throws on call to make it explicit when the compiler
|
|
44
|
+
* has been skipped — the alternative (silent FULL_MASK degradation) is
|
|
45
|
+
* a §0.5 wrong-by-default outcome.
|
|
46
|
+
*/
|
|
47
|
+
export function track(_opts) {
|
|
48
|
+
throw new LluiCompilerSkippedError('[llui] track() was reached at runtime — this means the @llui/compiler ' +
|
|
49
|
+
'transform did not run against this file. Compiled track() calls are ' +
|
|
50
|
+
'erased from the bundle. Check that vite.config.ts registers the LLui ' +
|
|
51
|
+
'plugin and that the file extension is .ts / .tsx. See ' +
|
|
52
|
+
'docs/proposals/v2-compiler/v2b.md §3.');
|
|
53
|
+
}
|
|
54
|
+
//# sourceMappingURL=track.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"track.js","sourceRoot":"","sources":["../../src/primitives/track.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AAEH,MAAM,OAAO,wBAAyB,SAAQ,KAAK;IACjD,YAAY,OAAe;QACzB,KAAK,CAAC,OAAO,CAAC,CAAA;QACd,IAAI,CAAC,IAAI,GAAG,0BAA0B,CAAA;IACxC,CAAC;CACF;AAYD;;;;GAIG;AACH,MAAM,UAAU,KAAK,CAAI,KAAsB;IAC7C,MAAM,IAAI,wBAAwB,CAChC,wEAAwE;QACtE,sEAAsE;QACtE,uEAAuE;QACvE,wDAAwD;QACxD,uCAAuC,CAC1C,CAAA;AACH,CAAC","sourcesContent":["/**\n * `track()` — explicit reactivity declaration for paths the compiler\n * cannot statically infer.\n *\n * ```ts\n * track({ deps: (s) => [s.pluginRegistry, s.activePluginName] })\n * ```\n *\n * At compile time `@llui/compiler` reads the `deps` accessor, folds its\n * paths into the host component's `__prefixes` table, and **strips the\n * entire call expression from the emitted output** (and removes the\n * `track` import). A `track()` call in source produces zero bytes in the\n * bundle and zero work at runtime.\n *\n * The runtime export below is the FULL_MASK fallback. If a\n * `ComponentDef` somehow bypasses the compiler (hand-rolled, build that\n * skipped the plugin), `track()` throws `LluiCompilerSkippedError` on\n * first evaluation — pointing at the specific call site the user needs\n * to either compile or remove. This is the alternative to silently\n * degrading to FULL_MASK; explicit failure beats silent slowness.\n *\n * Use `track()` only when:\n * - Plugin registries: `pluginRegistry[name].render(h, send)` where\n * `name` is state.\n * - Helpers returned by `useContext` chains where the provider lives\n * two-plus files away.\n * - Helpers stored in arrays and dispatched by index.\n *\n * For statically-typed view helpers in other files, the cross-file\n * walker (v2b) resolves their reads directly — `track()` is unnecessary\n * and the `llui/prefer-static-deps` lint rule will flag it. A clean\n * codebase has zero `track()` calls.\n *\n * See `docs/proposals/v2-compiler/v2b.md` §3.\n */\n\nexport class LluiCompilerSkippedError extends Error {\n constructor(message: string) {\n super(message)\n this.name = 'LluiCompilerSkippedError'\n }\n}\n\nexport interface TrackOptions<S> {\n /**\n * Declare the paths this component depends on. Called at most once at\n * compile time; the compiler folds the returned paths into\n * `__prefixes`. The runtime stub never invokes this function — if you\n * see it running, the compiler skipped the file.\n */\n deps: (s: S) => unknown[]\n}\n\n/**\n * Runtime stub. Throws on call to make it explicit when the compiler\n * has been skipped — the alternative (silent FULL_MASK degradation) is\n * a §0.5 wrong-by-default outcome.\n */\nexport function track<S>(_opts: TrackOptions<S>): void {\n throw new LluiCompilerSkippedError(\n '[llui] track() was reached at runtime — this means the @llui/compiler ' +\n 'transform did not run against this file. Compiled track() calls are ' +\n 'erased from the bundle. Check that vite.config.ts registers the LLui ' +\n 'plugin and that the file extension is .ts / .tsx. See ' +\n 'docs/proposals/v2-compiler/v2b.md §3.',\n )\n}\n"]}
|
|
@@ -163,7 +163,8 @@ export function virtualEach(opts) {
|
|
|
163
163
|
if (items.length === 0) {
|
|
164
164
|
// Dispose all entries
|
|
165
165
|
for (const entry of entries.values()) {
|
|
166
|
-
|
|
166
|
+
if (import.meta.env?.DEV)
|
|
167
|
+
entry.scope.disposalCause = 'each-remove';
|
|
167
168
|
disposeLifetime(entry.scope);
|
|
168
169
|
if (entry.wrapper.parentNode)
|
|
169
170
|
entry.wrapper.parentNode.removeChild(entry.wrapper);
|
|
@@ -181,7 +182,8 @@ export function virtualEach(opts) {
|
|
|
181
182
|
// Dispose entries no longer visible
|
|
182
183
|
for (const [key, entry] of entries) {
|
|
183
184
|
if (!visibleKeys.has(key)) {
|
|
184
|
-
|
|
185
|
+
if (import.meta.env?.DEV)
|
|
186
|
+
entry.scope.disposalCause = 'each-remove';
|
|
185
187
|
disposeLifetime(entry.scope);
|
|
186
188
|
if (entry.wrapper.parentNode)
|
|
187
189
|
entry.wrapper.parentNode.removeChild(entry.wrapper);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"virtual-each.js","sourceRoot":"","sources":["../../src/primitives/virtual-each.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ItemAccessor, Lifetime, Send } from '../types.js'\nimport {\n getRenderContext,\n setRenderContext,\n clearRenderContext,\n enterAccessor,\n exitAccessor,\n type RenderContext,\n} from '../render-context.js'\nimport { createLifetime, disposeLifetime, addDisposer } from '../lifetime.js'\nimport { getFlatBindings, setFlatBindings } from '../binding.js'\nimport { FULL_MASK } from '../update-loop.js'\nimport type { StructuralBlock } from '../structural.js'\n\nexport interface VirtualEachOptions<S, T, M = unknown> {\n items: (s: S) => T[]\n key: (item: T) => string | number\n /** Fixed pixel height per item. Required — dynamic heights are not supported yet. */\n itemHeight: number\n /** Scrollable container height in pixels. */\n containerHeight: number\n /** Extra rows to render above/below the viewport for smooth scrolling. Default: 3. */\n overscan?: number\n /** Optional class for the scroll container. */\n class?: string\n render: (opts: {\n send: Send<M>\n item: ItemAccessor<T>\n acc: <R>(selector: (t: T) => R) => () => R\n index: () => number\n }) => Node[]\n}\n\ninterface VirtualEntry<T> {\n key: string | number\n current: T\n index: number\n scope: Lifetime\n wrapper: HTMLElement\n}\n\nconst buildCtx: RenderContext = {\n rootLifetime: null as unknown as Lifetime,\n state: null,\n allBindings: [],\n structuralBlocks: [],\n dom: null as unknown as import('../dom-env.js').DomEnv,\n}\n\n/**\n * Virtualized list — renders only the rows visible in the scroll viewport.\n * Use for lists with 1k+ items where a regular `each()` would be too slow.\n *\n * Current limitations:\n * - Fixed row height (`itemHeight`) — dynamic heights not supported\n * - No transitions / animations\n * - No cross-container reuse (items outside view are fully disposed)\n *\n * ```ts\n * view: ({ text }) => [\n * ...virtualEach({\n * items: (s) => s.rows,\n * key: (r) => r.id,\n * itemHeight: 40,\n * containerHeight: 600,\n * render: ({ item }) => [\n * div({ class: 'row' }, [text(item.label)]),\n * ],\n * }),\n * ]\n * ```\n */\nexport function virtualEach<S, T, M = unknown>(opts: VirtualEachOptions<S, T, M>): Node[] {\n const ctx = getRenderContext('virtualEach')\n const parentLifetime = ctx.rootLifetime\n const blocks = ctx.structuralBlocks\n const send = ctx.send as (msg: M) => void\n\n const overscan = opts.overscan ?? 3\n\n // Scroll container\n const scroll = ctx.dom.createElement('div') as HTMLDivElement\n scroll.style.overflow = 'auto'\n scroll.style.position = 'relative'\n scroll.style.height = `${opts.containerHeight}px`\n scroll.dataset.virtualContainer = ''\n if (opts.class) scroll.className = opts.class\n\n // Inner spacer sized to full content height\n const spacer = ctx.dom.createElement('div') as HTMLDivElement\n spacer.style.position = 'relative'\n spacer.style.width = '100%'\n spacer.dataset.virtualSpacer = ''\n scroll.appendChild(spacer)\n\n // Map of key → entry\n const entries = new Map<string | number, VirtualEntry<T>>()\n let lastItems: T[] = []\n let scrollTop = 0\n\n const computeRange = (length: number): [number, number] => {\n if (length === 0) return [0, 0]\n const start = Math.max(0, Math.floor(scrollTop / opts.itemHeight) - overscan)\n const end = Math.min(\n length,\n Math.ceil((scrollTop + opts.containerHeight) / opts.itemHeight) + overscan,\n )\n return [start, end]\n }\n\n // Wrap accessor invocations to make sample()/h.sample() inside throw a\n // targeted error. Mirrors the wrappers in each.ts.\n const callItems = (state: S): T[] => {\n enterAccessor('virtualEach().items')\n try {\n return opts.items(state)\n } finally {\n exitAccessor()\n }\n }\n const callKey = (item: T): string | number => {\n enterAccessor('virtualEach().key')\n try {\n return opts.key(item)\n } finally {\n exitAccessor()\n }\n }\n\n const buildEntry = (item: T, index: number, state: S): VirtualEntry<T> => {\n const key = callKey(item)\n const scope = createLifetime(parentLifetime)\n\n const wrapper = ctx.dom.createElement('div') as HTMLDivElement\n wrapper.style.position = 'absolute'\n wrapper.style.top = `${index * opts.itemHeight}px`\n wrapper.style.left = '0'\n wrapper.style.right = '0'\n wrapper.style.height = `${opts.itemHeight}px`\n wrapper.dataset.virtualItem = ''\n wrapper.dataset.virtualKey = String(key)\n\n const entry: VirtualEntry<T> = { key, current: item, index, scope, wrapper }\n\n // Item accessor: item(selector) and item.field\n const itemFn = <R>(selector: (t: T) => R): (() => R) => {\n const accessor = (): R => selector(entry.current)\n ;(accessor as unknown as { __perItem: true }).__perItem = true\n return accessor\n }\n\n let itemProxy: ItemAccessor<T> | null = null\n const getItemProxy = (): ItemAccessor<T> => {\n if (itemProxy) return itemProxy\n const fieldCache = new Map<string, () => unknown>()\n itemProxy = new Proxy(itemFn as object, {\n get(target, prop) {\n if (typeof prop === 'symbol' || prop === 'then' || prop === 'prototype') {\n return Reflect.get(target, prop)\n }\n const k = prop as string\n const cached = fieldCache.get(k)\n if (cached) return cached\n const accessor = (): unknown => (entry.current as Record<string, unknown>)[k]\n ;(accessor as unknown as { __perItem: true }).__perItem = true\n fieldCache.set(k, accessor)\n return accessor\n },\n }) as ItemAccessor<T>\n return itemProxy\n }\n\n const indexAccessor = (): number => entry.index\n\n // Every non-rootLifetime/non-state field is copied from the\n // surrounding context — including `send` and `container`. The\n // historical motivation: missing `send` broke row-internal message\n // dispatch (originally surfaced via the removed `child({ onMsg })`\n // primitive; equivalent today is a row-internal event handler\n // calling the parent's `send`); missing `container` would point\n // `onMount` at `document.body`.\n buildCtx.rootLifetime = scope\n buildCtx.state = state\n buildCtx.allBindings = ctx.allBindings\n buildCtx.structuralBlocks = ctx.structuralBlocks\n buildCtx.dom = ctx.dom\n buildCtx.instance = ctx.instance\n buildCtx.send = ctx.send\n buildCtx.container = ctx.container\n const prevFlat = getFlatBindings()\n setFlatBindings(ctx.allBindings)\n setRenderContext(buildCtx)\n\n const nodes = opts.render({\n send,\n item: getItemProxy(),\n acc: itemFn,\n index: indexAccessor,\n })\n\n clearRenderContext()\n setFlatBindings(prevFlat)\n setRenderContext(ctx)\n\n for (const node of nodes) wrapper.appendChild(node)\n return entry\n }\n\n const reconcile = (state: S): void => {\n const items = callItems(state)\n lastItems = items\n\n // Update spacer total height\n spacer.style.height = `${items.length * opts.itemHeight}px`\n\n if (items.length === 0) {\n // Dispose all entries\n for (const entry of entries.values()) {\n entry.scope.disposalCause = 'each-remove'\n disposeLifetime(entry.scope)\n if (entry.wrapper.parentNode) entry.wrapper.parentNode.removeChild(entry.wrapper)\n }\n entries.clear()\n return\n }\n\n const [start, end] = computeRange(items.length)\n\n // Build a map of key → {item, index} for the visible range\n const visibleKeys = new Map<string | number, { item: T; index: number }>()\n for (let i = start; i < end; i++) {\n const item = items[i]!\n visibleKeys.set(callKey(item), { item, index: i })\n }\n\n // Dispose entries no longer visible\n for (const [key, entry] of entries) {\n if (!visibleKeys.has(key)) {\n entry.scope.disposalCause = 'each-remove'\n disposeLifetime(entry.scope)\n if (entry.wrapper.parentNode) entry.wrapper.parentNode.removeChild(entry.wrapper)\n entries.delete(key)\n }\n }\n\n // Create new entries + update existing\n for (const [key, { item, index }] of visibleKeys) {\n const existing = entries.get(key)\n if (existing) {\n existing.current = item\n if (existing.index !== index) {\n existing.index = index\n existing.wrapper.style.top = `${index * opts.itemHeight}px`\n }\n } else {\n const entry = buildEntry(item, index, state)\n entries.set(key, entry)\n spacer.appendChild(entry.wrapper)\n }\n }\n }\n\n // Scroll handler — reconcile visible range without touching component state\n const onScroll = (): void => {\n scrollTop = scroll.scrollTop\n reconcile(ctx.state as S)\n }\n scroll.addEventListener('scroll', onScroll, { passive: true })\n\n // Register as a structural block BEFORE initial render so this block\n // precedes any nested blocks its rows register. See branch.ts for the\n // full rationale (Phase 1 iteration safety).\n const block: StructuralBlock = {\n mask: FULL_MASK,\n maskHi: 0,\n reconcile(state: unknown) {\n const newItems = callItems(state as S)\n if (newItems === lastItems) return\n reconcile(state as S)\n },\n }\n blocks.push(block)\n\n // Initial render\n reconcile(ctx.state as S)\n\n // Cleanup on parent disposal\n addDisposer(parentLifetime, () => {\n scroll.removeEventListener('scroll', onScroll)\n for (const entry of entries.values()) {\n disposeLifetime(entry.scope)\n }\n entries.clear()\n })\n\n return [scroll]\n}\n"]}
|
|
1
|
+
{"version":3,"file":"virtual-each.js","sourceRoot":"","sources":["../../src/primitives/virtual-each.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ItemAccessor, Lifetime, Send } from '../types.js'\nimport {\n getRenderContext,\n setRenderContext,\n clearRenderContext,\n enterAccessor,\n exitAccessor,\n type RenderContext,\n} from '../render-context.js'\nimport { createLifetime, disposeLifetime, addDisposer } from '../lifetime.js'\nimport { getFlatBindings, setFlatBindings } from '../binding.js'\nimport { FULL_MASK } from '../update-loop.js'\nimport type { StructuralBlock } from '../structural.js'\n\nexport interface VirtualEachOptions<S, T, M = unknown> {\n items: (s: S) => T[]\n key: (item: T) => string | number\n /** Fixed pixel height per item. Required — dynamic heights are not supported yet. */\n itemHeight: number\n /** Scrollable container height in pixels. */\n containerHeight: number\n /** Extra rows to render above/below the viewport for smooth scrolling. Default: 3. */\n overscan?: number\n /** Optional class for the scroll container. */\n class?: string\n render: (opts: {\n send: Send<M>\n item: ItemAccessor<T>\n acc: <R>(selector: (t: T) => R) => () => R\n index: () => number\n }) => Node[]\n}\n\ninterface VirtualEntry<T> {\n key: string | number\n current: T\n index: number\n scope: Lifetime\n wrapper: HTMLElement\n}\n\nconst buildCtx: RenderContext = {\n rootLifetime: null as unknown as Lifetime,\n state: null,\n allBindings: [],\n structuralBlocks: [],\n dom: null as unknown as import('../dom-env.js').DomEnv,\n}\n\n/**\n * Virtualized list — renders only the rows visible in the scroll viewport.\n * Use for lists with 1k+ items where a regular `each()` would be too slow.\n *\n * Current limitations:\n * - Fixed row height (`itemHeight`) — dynamic heights not supported\n * - No transitions / animations\n * - No cross-container reuse (items outside view are fully disposed)\n *\n * ```ts\n * view: ({ text }) => [\n * ...virtualEach({\n * items: (s) => s.rows,\n * key: (r) => r.id,\n * itemHeight: 40,\n * containerHeight: 600,\n * render: ({ item }) => [\n * div({ class: 'row' }, [text(item.label)]),\n * ],\n * }),\n * ]\n * ```\n */\nexport function virtualEach<S, T, M = unknown>(opts: VirtualEachOptions<S, T, M>): Node[] {\n const ctx = getRenderContext('virtualEach')\n const parentLifetime = ctx.rootLifetime\n const blocks = ctx.structuralBlocks\n const send = ctx.send as (msg: M) => void\n\n const overscan = opts.overscan ?? 3\n\n // Scroll container\n const scroll = ctx.dom.createElement('div') as HTMLDivElement\n scroll.style.overflow = 'auto'\n scroll.style.position = 'relative'\n scroll.style.height = `${opts.containerHeight}px`\n scroll.dataset.virtualContainer = ''\n if (opts.class) scroll.className = opts.class\n\n // Inner spacer sized to full content height\n const spacer = ctx.dom.createElement('div') as HTMLDivElement\n spacer.style.position = 'relative'\n spacer.style.width = '100%'\n spacer.dataset.virtualSpacer = ''\n scroll.appendChild(spacer)\n\n // Map of key → entry\n const entries = new Map<string | number, VirtualEntry<T>>()\n let lastItems: T[] = []\n let scrollTop = 0\n\n const computeRange = (length: number): [number, number] => {\n if (length === 0) return [0, 0]\n const start = Math.max(0, Math.floor(scrollTop / opts.itemHeight) - overscan)\n const end = Math.min(\n length,\n Math.ceil((scrollTop + opts.containerHeight) / opts.itemHeight) + overscan,\n )\n return [start, end]\n }\n\n // Wrap accessor invocations to make sample()/h.sample() inside throw a\n // targeted error. Mirrors the wrappers in each.ts.\n const callItems = (state: S): T[] => {\n enterAccessor('virtualEach().items')\n try {\n return opts.items(state)\n } finally {\n exitAccessor()\n }\n }\n const callKey = (item: T): string | number => {\n enterAccessor('virtualEach().key')\n try {\n return opts.key(item)\n } finally {\n exitAccessor()\n }\n }\n\n const buildEntry = (item: T, index: number, state: S): VirtualEntry<T> => {\n const key = callKey(item)\n const scope = createLifetime(parentLifetime)\n\n const wrapper = ctx.dom.createElement('div') as HTMLDivElement\n wrapper.style.position = 'absolute'\n wrapper.style.top = `${index * opts.itemHeight}px`\n wrapper.style.left = '0'\n wrapper.style.right = '0'\n wrapper.style.height = `${opts.itemHeight}px`\n wrapper.dataset.virtualItem = ''\n wrapper.dataset.virtualKey = String(key)\n\n const entry: VirtualEntry<T> = { key, current: item, index, scope, wrapper }\n\n // Item accessor: item(selector) and item.field\n const itemFn = <R>(selector: (t: T) => R): (() => R) => {\n const accessor = (): R => selector(entry.current)\n ;(accessor as unknown as { __perItem: true }).__perItem = true\n return accessor\n }\n\n let itemProxy: ItemAccessor<T> | null = null\n const getItemProxy = (): ItemAccessor<T> => {\n if (itemProxy) return itemProxy\n const fieldCache = new Map<string, () => unknown>()\n itemProxy = new Proxy(itemFn as object, {\n get(target, prop) {\n if (typeof prop === 'symbol' || prop === 'then' || prop === 'prototype') {\n return Reflect.get(target, prop)\n }\n const k = prop as string\n const cached = fieldCache.get(k)\n if (cached) return cached\n const accessor = (): unknown => (entry.current as Record<string, unknown>)[k]\n ;(accessor as unknown as { __perItem: true }).__perItem = true\n fieldCache.set(k, accessor)\n return accessor\n },\n }) as ItemAccessor<T>\n return itemProxy\n }\n\n const indexAccessor = (): number => entry.index\n\n // Every non-rootLifetime/non-state field is copied from the\n // surrounding context — including `send` and `container`. The\n // historical motivation: missing `send` broke row-internal message\n // dispatch (originally surfaced via the removed `child({ onMsg })`\n // primitive; equivalent today is a row-internal event handler\n // calling the parent's `send`); missing `container` would point\n // `onMount` at `document.body`.\n buildCtx.rootLifetime = scope\n buildCtx.state = state\n buildCtx.allBindings = ctx.allBindings\n buildCtx.structuralBlocks = ctx.structuralBlocks\n buildCtx.dom = ctx.dom\n buildCtx.instance = ctx.instance\n buildCtx.send = ctx.send\n buildCtx.container = ctx.container\n const prevFlat = getFlatBindings()\n setFlatBindings(ctx.allBindings)\n setRenderContext(buildCtx)\n\n const nodes = opts.render({\n send,\n item: getItemProxy(),\n acc: itemFn,\n index: indexAccessor,\n })\n\n clearRenderContext()\n setFlatBindings(prevFlat)\n setRenderContext(ctx)\n\n for (const node of nodes) wrapper.appendChild(node)\n return entry\n }\n\n const reconcile = (state: S): void => {\n const items = callItems(state)\n lastItems = items\n\n // Update spacer total height\n spacer.style.height = `${items.length * opts.itemHeight}px`\n\n if (items.length === 0) {\n // Dispose all entries\n for (const entry of entries.values()) {\n if (import.meta.env?.DEV) entry.scope.disposalCause = 'each-remove'\n disposeLifetime(entry.scope)\n if (entry.wrapper.parentNode) entry.wrapper.parentNode.removeChild(entry.wrapper)\n }\n entries.clear()\n return\n }\n\n const [start, end] = computeRange(items.length)\n\n // Build a map of key → {item, index} for the visible range\n const visibleKeys = new Map<string | number, { item: T; index: number }>()\n for (let i = start; i < end; i++) {\n const item = items[i]!\n visibleKeys.set(callKey(item), { item, index: i })\n }\n\n // Dispose entries no longer visible\n for (const [key, entry] of entries) {\n if (!visibleKeys.has(key)) {\n if (import.meta.env?.DEV) entry.scope.disposalCause = 'each-remove'\n disposeLifetime(entry.scope)\n if (entry.wrapper.parentNode) entry.wrapper.parentNode.removeChild(entry.wrapper)\n entries.delete(key)\n }\n }\n\n // Create new entries + update existing\n for (const [key, { item, index }] of visibleKeys) {\n const existing = entries.get(key)\n if (existing) {\n existing.current = item\n if (existing.index !== index) {\n existing.index = index\n existing.wrapper.style.top = `${index * opts.itemHeight}px`\n }\n } else {\n const entry = buildEntry(item, index, state)\n entries.set(key, entry)\n spacer.appendChild(entry.wrapper)\n }\n }\n }\n\n // Scroll handler — reconcile visible range without touching component state\n const onScroll = (): void => {\n scrollTop = scroll.scrollTop\n reconcile(ctx.state as S)\n }\n scroll.addEventListener('scroll', onScroll, { passive: true })\n\n // Register as a structural block BEFORE initial render so this block\n // precedes any nested blocks its rows register. See branch.ts for the\n // full rationale (Phase 1 iteration safety).\n const block: StructuralBlock = {\n mask: FULL_MASK,\n maskHi: 0,\n reconcile(state: unknown) {\n const newItems = callItems(state as S)\n if (newItems === lastItems) return\n reconcile(state as S)\n },\n }\n blocks.push(block)\n\n // Initial render\n reconcile(ctx.state as S)\n\n // Cleanup on parent disposal\n addDisposer(parentLifetime, () => {\n scroll.removeEventListener('scroll', onScroll)\n for (const entry of entries.values()) {\n disposeLifetime(entry.scope)\n }\n entries.clear()\n })\n\n return [scroll]\n}\n"]}
|
package/dist/render-context.d.ts
CHANGED
|
@@ -1,6 +1,15 @@
|
|
|
1
|
-
import type { Lifetime, Binding } from './types.js';
|
|
1
|
+
import type { Lifetime, Binding, Send } from './types.js';
|
|
2
2
|
import type { StructuralBlock } from './structural.js';
|
|
3
|
+
import type { ComponentInstance } from './update-loop.js';
|
|
3
4
|
import type { DomEnv } from './dom-env.js';
|
|
5
|
+
declare global {
|
|
6
|
+
interface ImportMeta {
|
|
7
|
+
env?: {
|
|
8
|
+
DEV?: boolean;
|
|
9
|
+
MODE?: string;
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
}
|
|
4
13
|
export interface RenderContext {
|
|
5
14
|
rootLifetime: Lifetime;
|
|
6
15
|
state: unknown;
|
|
@@ -22,5 +31,22 @@ export declare function clearRenderContext(): void;
|
|
|
22
31
|
export declare function enterAccessor(label: string): void;
|
|
23
32
|
export declare function exitAccessor(): void;
|
|
24
33
|
export declare function currentAccessor(): string | null;
|
|
34
|
+
/**
|
|
35
|
+
* Return the view bag for `inst`, caching it on the instance so the
|
|
36
|
+
* per-row allocation that each.render / branch arms / lazy fallback /
|
|
37
|
+
* client-only render / hmr replay / ssr render used to incur on every
|
|
38
|
+
* call collapses to a single object literal per instance. Hot path on
|
|
39
|
+
* jfb's Select benchmark — `pnpm bench` measured +31% without this
|
|
40
|
+
* cache (each row's mount called `def.__view(send)` afresh).
|
|
41
|
+
*
|
|
42
|
+
* Cache key is the instance: `send` is identity-stable per instance,
|
|
43
|
+
* so the bag's bound `send` is correct for every call site that
|
|
44
|
+
* receives it. The cache invalidates implicitly on instance disposal.
|
|
45
|
+
* In test mode (no compiler-emitted `__view`) we fall through to
|
|
46
|
+
* `createView` per call — tests don't measure perf, and the
|
|
47
|
+
* createView reference is gone from production via the Vite-time MODE
|
|
48
|
+
* fold (see mount.ts buildViewBag for the same pattern).
|
|
49
|
+
*/
|
|
50
|
+
export declare function getInstanceViewBag<S, M>(inst: ComponentInstance | undefined, send: Send<M>): unknown;
|
|
25
51
|
export declare function getRenderContext(primitiveName?: string): RenderContext;
|
|
26
52
|
//# sourceMappingURL=render-context.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"render-context.d.ts","sourceRoot":"","sources":["../src/render-context.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,YAAY,CAAA;
|
|
1
|
+
{"version":3,"file":"render-context.d.ts","sourceRoot":"","sources":["../src/render-context.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,YAAY,CAAA;AACzD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAA;AACtD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAA;AACzD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAA;AAG1C,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,UAAU;QAClB,GAAG,CAAC,EAAE;YAAE,GAAG,CAAC,EAAE,OAAO,CAAC;YAAC,IAAI,CAAC,EAAE,MAAM,CAAA;SAAE,CAAA;KACvC;CACF;AAED,MAAM,WAAW,aAAa;IAC5B,YAAY,EAAE,QAAQ,CAAA;IACtB,KAAK,EAAE,OAAO,CAAA;IACd,WAAW,EAAE,OAAO,EAAE,CAAA;IACtB,gBAAgB,EAAE,eAAe,EAAE,CAAA;IACnC,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,IAAI,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,IAAI,CAAA;IAC7B;;;;;;OAMG;IACH,GAAG,EAAE,MAAM,CAAA;CAMZ;AAID,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,aAAa,GAAG,IAAI,CAEzD;AAED,wBAAgB,kBAAkB,IAAI,IAAI,CAEzC;AAaD,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAEjD;AAED,wBAAgB,YAAY,IAAI,IAAI,CAEnC;AAED,wBAAgB,eAAe,IAAI,MAAM,GAAG,IAAI,CAG/C;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,kBAAkB,CAAC,CAAC,EAAE,CAAC,EACrC,IAAI,EAAE,iBAAiB,GAAG,SAAS,EACnC,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC,GACZ,OAAO,CAoBT;AAED,wBAAgB,gBAAgB,CAAC,aAAa,CAAC,EAAE,MAAM,GAAG,aAAa,CAgDtE"}
|
package/dist/render-context.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { createView } from './view-helpers.js';
|
|
1
2
|
let currentContext = null;
|
|
2
3
|
export function setRenderContext(ctx) {
|
|
3
4
|
currentContext = ctx;
|
|
@@ -25,42 +26,90 @@ export function currentAccessor() {
|
|
|
25
26
|
const len = accessorStack.length;
|
|
26
27
|
return len > 0 ? accessorStack[len - 1] : null;
|
|
27
28
|
}
|
|
29
|
+
/**
|
|
30
|
+
* Return the view bag for `inst`, caching it on the instance so the
|
|
31
|
+
* per-row allocation that each.render / branch arms / lazy fallback /
|
|
32
|
+
* client-only render / hmr replay / ssr render used to incur on every
|
|
33
|
+
* call collapses to a single object literal per instance. Hot path on
|
|
34
|
+
* jfb's Select benchmark — `pnpm bench` measured +31% without this
|
|
35
|
+
* cache (each row's mount called `def.__view(send)` afresh).
|
|
36
|
+
*
|
|
37
|
+
* Cache key is the instance: `send` is identity-stable per instance,
|
|
38
|
+
* so the bag's bound `send` is correct for every call site that
|
|
39
|
+
* receives it. The cache invalidates implicitly on instance disposal.
|
|
40
|
+
* In test mode (no compiler-emitted `__view`) we fall through to
|
|
41
|
+
* `createView` per call — tests don't measure perf, and the
|
|
42
|
+
* createView reference is gone from production via the Vite-time MODE
|
|
43
|
+
* fold (see mount.ts buildViewBag for the same pattern).
|
|
44
|
+
*/
|
|
45
|
+
export function getInstanceViewBag(inst, send) {
|
|
46
|
+
// No instance → only happens in test-mode fixtures that mount through
|
|
47
|
+
// a stub render context. The createView reference below is dead in
|
|
48
|
+
// production builds (Vite folds the MODE check to a constant).
|
|
49
|
+
if (!inst) {
|
|
50
|
+
if (import.meta.env?.MODE !== 'production')
|
|
51
|
+
return createView(send);
|
|
52
|
+
return { send };
|
|
53
|
+
}
|
|
54
|
+
if (inst._viewBag !== undefined)
|
|
55
|
+
return inst._viewBag;
|
|
56
|
+
const factory = inst.def.__view;
|
|
57
|
+
let bag;
|
|
58
|
+
if (factory) {
|
|
59
|
+
bag = factory(send);
|
|
60
|
+
}
|
|
61
|
+
else if (import.meta.env?.MODE !== 'production') {
|
|
62
|
+
bag = createView(send);
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
bag = { send };
|
|
66
|
+
}
|
|
67
|
+
inst._viewBag = bag;
|
|
68
|
+
return bag;
|
|
69
|
+
}
|
|
28
70
|
export function getRenderContext(primitiveName) {
|
|
29
71
|
if (!currentContext) {
|
|
30
72
|
const name = primitiveName ? `${primitiveName}()` : 'primitives';
|
|
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
|
-
|
|
73
|
+
// Long-form guidance is dev-only — these are programming errors
|
|
74
|
+
// (calling a primitive outside a view callback or inside an
|
|
75
|
+
// accessor). Production apps that hit this still throw a brief
|
|
76
|
+
// identifying error; the diagnostic prose adds ~600 source bytes
|
|
77
|
+
// / ~250 bytes gz that the bundler keeps only when DEV is true.
|
|
78
|
+
if (import.meta.env?.DEV) {
|
|
79
|
+
// `sample()` is specifically the one users reach for from adapter
|
|
80
|
+
// send wrappers / event handlers / async callbacks expecting it
|
|
81
|
+
// to be "imperative and safe." It isn't — point at the sanctioned
|
|
82
|
+
// escape hatch (`AppHandle.getState()`) so the caller doesn't
|
|
83
|
+
// have to dig.
|
|
84
|
+
const sampleGuidance = primitiveName === 'sample'
|
|
85
|
+
? '\n\nFor the "read state inside a callback / handler" case: use ' +
|
|
86
|
+
'AppHandle.getState() instead. It is safe to call from anywhere ' +
|
|
87
|
+
'(event handlers, adapter send wrappers, async callbacks, timers).\n' +
|
|
88
|
+
'Example:\n' +
|
|
89
|
+
' const handle = mountApp(root, App)\n' +
|
|
90
|
+
" el.addEventListener('click', () => {\n" +
|
|
91
|
+
' const { count } = handle.getState() as AppState\n' +
|
|
92
|
+
" if (count > 0) handle.send({ type: 'tick' })\n" +
|
|
93
|
+
' })'
|
|
94
|
+
: '';
|
|
95
|
+
throw new Error(`[LLui] ${name} can only be called inside a component's view() function. ` +
|
|
96
|
+
`It was called outside a render context. Common causes:\n` +
|
|
97
|
+
` 1. Calling a primitive at module scope instead of inside view().\n` +
|
|
98
|
+
` 2. Calling an overlay helper (dialog.overlay, popover.overlay, …) at ` +
|
|
99
|
+
`module scope — these internally use show()/branch() and must be invoked ` +
|
|
100
|
+
`from inside the component's view callback so their result can be spread ` +
|
|
101
|
+
`into the returned node tree.\n` +
|
|
102
|
+
` 3. Calling a primitive from a setTimeout / Promise / event handler — ` +
|
|
103
|
+
`the render context only persists during the synchronous view() call.\n` +
|
|
104
|
+
` 4. Calling a primitive from a structural accessor (each().key, ` +
|
|
105
|
+
`each().items, branch().on, show().when, scope().on, …) or a ` +
|
|
106
|
+
`binding accessor (text(s => …), el({attr: s => …})) during reconcile — ` +
|
|
107
|
+
`accessors run during the update phase with no render context. They must ` +
|
|
108
|
+
`be pure functions of their parameter; reads outside the parameter break ` +
|
|
109
|
+
`mask gating.` +
|
|
110
|
+
sampleGuidance);
|
|
111
|
+
}
|
|
112
|
+
throw new Error(`[LLui] ${name} called outside a render context`);
|
|
64
113
|
}
|
|
65
114
|
return currentContext;
|
|
66
115
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"render-context.js","sourceRoot":"","sources":["../src/render-context.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"render-context.js","sourceRoot":"","sources":["../src/render-context.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Lifetime, Binding, Send } from './types.js'\nimport type { StructuralBlock } from './structural.js'\nimport type { ComponentInstance } from './update-loop.js'\nimport type { DomEnv } from './dom-env.js'\nimport { createView } from './view-helpers.js'\n\ndeclare global {\n interface ImportMeta {\n env?: { DEV?: boolean; MODE?: string }\n }\n}\n\nexport interface RenderContext {\n rootLifetime: Lifetime\n state: unknown\n allBindings: Binding[]\n structuralBlocks: StructuralBlock[]\n container?: Element\n send?: (msg: unknown) => void\n /**\n * The DOM implementation backing this render pass. Seeded by\n * `mountApp` / `hydrateApp` / `renderToString` and threaded through\n * every nested primitive via context spread. Primitives construct\n * DOM through `ctx.dom.createElement(...)` etc. instead of reaching\n * for `globalThis.document`.\n */\n dom: DomEnv\n /** @internal dev-only — the owning ComponentInstance. Set by mount /\n * hydrate / child to let primitives (currently `each`) emit tracker\n * data to `inst._eachDiffLog`. Nested contexts pass through via\n * spread (e.g. `{ ...ctx, rootLifetime }`). Undefined outside dev. */\n instance?: ComponentInstance\n}\n\nlet currentContext: RenderContext | null = null\n\nexport function setRenderContext(ctx: RenderContext): void {\n currentContext = ctx\n}\n\nexport function clearRenderContext(): void {\n currentContext = null\n}\n\n// Accessor stack — tracks which structural-primitive or binding accessor is\n// currently executing. `sample()` reads the top of this stack to detect calls\n// from inside an accessor (forbidden — accessors must be pure functions of\n// their parameter, since their reads drive mask gating).\n//\n// Implemented as an array (rather than a counter) so the targeted error can\n// name the innermost accessor: \"inside each().key\" rather than just \"inside\n// an accessor\". Nested primitives push/pop in LIFO order; the top is the\n// site that called sample().\nconst accessorStack: string[] = []\n\nexport function enterAccessor(label: string): void {\n accessorStack.push(label)\n}\n\nexport function exitAccessor(): void {\n accessorStack.pop()\n}\n\nexport function currentAccessor(): string | null {\n const len = accessorStack.length\n return len > 0 ? accessorStack[len - 1]! : null\n}\n\n/**\n * Return the view bag for `inst`, caching it on the instance so the\n * per-row allocation that each.render / branch arms / lazy fallback /\n * client-only render / hmr replay / ssr render used to incur on every\n * call collapses to a single object literal per instance. Hot path on\n * jfb's Select benchmark — `pnpm bench` measured +31% without this\n * cache (each row's mount called `def.__view(send)` afresh).\n *\n * Cache key is the instance: `send` is identity-stable per instance,\n * so the bag's bound `send` is correct for every call site that\n * receives it. The cache invalidates implicitly on instance disposal.\n * In test mode (no compiler-emitted `__view`) we fall through to\n * `createView` per call — tests don't measure perf, and the\n * createView reference is gone from production via the Vite-time MODE\n * fold (see mount.ts buildViewBag for the same pattern).\n */\nexport function getInstanceViewBag<S, M>(\n inst: ComponentInstance | undefined,\n send: Send<M>,\n): unknown {\n // No instance → only happens in test-mode fixtures that mount through\n // a stub render context. The createView reference below is dead in\n // production builds (Vite folds the MODE check to a constant).\n if (!inst) {\n if (import.meta.env?.MODE !== 'production') return createView<S, M>(send)\n return { send }\n }\n if (inst._viewBag !== undefined) return inst._viewBag\n const factory = (inst.def as unknown as { __view?: (s: Send<M>) => unknown }).__view\n let bag: unknown\n if (factory) {\n bag = factory(send)\n } else if (import.meta.env?.MODE !== 'production') {\n bag = createView<S, M>(send)\n } else {\n bag = { send }\n }\n inst._viewBag = bag\n return bag\n}\n\nexport function getRenderContext(primitiveName?: string): RenderContext {\n if (!currentContext) {\n const name = primitiveName ? `${primitiveName}()` : 'primitives'\n // Long-form guidance is dev-only — these are programming errors\n // (calling a primitive outside a view callback or inside an\n // accessor). Production apps that hit this still throw a brief\n // identifying error; the diagnostic prose adds ~600 source bytes\n // / ~250 bytes gz that the bundler keeps only when DEV is true.\n if (import.meta.env?.DEV) {\n // `sample()` is specifically the one users reach for from adapter\n // send wrappers / event handlers / async callbacks expecting it\n // to be \"imperative and safe.\" It isn't — point at the sanctioned\n // escape hatch (`AppHandle.getState()`) so the caller doesn't\n // have to dig.\n const sampleGuidance =\n primitiveName === 'sample'\n ? '\\n\\nFor the \"read state inside a callback / handler\" case: use ' +\n 'AppHandle.getState() instead. It is safe to call from anywhere ' +\n '(event handlers, adapter send wrappers, async callbacks, timers).\\n' +\n 'Example:\\n' +\n ' const handle = mountApp(root, App)\\n' +\n \" el.addEventListener('click', () => {\\n\" +\n ' const { count } = handle.getState() as AppState\\n' +\n \" if (count > 0) handle.send({ type: 'tick' })\\n\" +\n ' })'\n : ''\n throw new Error(\n `[LLui] ${name} can only be called inside a component's view() function. ` +\n `It was called outside a render context. Common causes:\\n` +\n ` 1. Calling a primitive at module scope instead of inside view().\\n` +\n ` 2. Calling an overlay helper (dialog.overlay, popover.overlay, …) at ` +\n `module scope — these internally use show()/branch() and must be invoked ` +\n `from inside the component's view callback so their result can be spread ` +\n `into the returned node tree.\\n` +\n ` 3. Calling a primitive from a setTimeout / Promise / event handler — ` +\n `the render context only persists during the synchronous view() call.\\n` +\n ` 4. Calling a primitive from a structural accessor (each().key, ` +\n `each().items, branch().on, show().when, scope().on, …) or a ` +\n `binding accessor (text(s => …), el({attr: s => …})) during reconcile — ` +\n `accessors run during the update phase with no render context. They must ` +\n `be pure functions of their parameter; reads outside the parameter break ` +\n `mask gating.` +\n sampleGuidance,\n )\n }\n throw new Error(`[LLui] ${name} called outside a render context`)\n }\n return currentContext\n}\n"]}
|
package/dist/ssr.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ssr.d.ts","sourceRoot":"","sources":["../src/ssr.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,YAAY,CAAA;AACjE,OAAO,EAA2B,KAAK,iBAAiB,EAAE,MAAM,kBAAkB,CAAA;AAIlF,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAA;AAE1C;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,WAAW,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,GAAG,IAAI,EAC3C,GAAG,EAAE,YAAY,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,EAC7B,YAAY,EAAE,CAAC,GAAG,SAAS,EAC3B,GAAG,EAAE,MAAM,EACX,cAAc,CAAC,EAAE,QAAQ,GACxB;IAAE,KAAK,EAAE,IAAI,EAAE,CAAC;IAAC,IAAI,EAAE,iBAAiB,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAA;CAAE,
|
|
1
|
+
{"version":3,"file":"ssr.d.ts","sourceRoot":"","sources":["../src/ssr.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,YAAY,CAAA;AACjE,OAAO,EAA2B,KAAK,iBAAiB,EAAE,MAAM,kBAAkB,CAAA;AAIlF,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAA;AAE1C;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,WAAW,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,GAAG,IAAI,EAC3C,GAAG,EAAE,YAAY,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,EAC7B,YAAY,EAAE,CAAC,GAAG,SAAS,EAC3B,GAAG,EAAE,MAAM,EACX,cAAc,CAAC,EAAE,QAAQ,GACxB;IAAE,KAAK,EAAE,IAAI,EAAE,CAAC;IAAC,IAAI,EAAE,iBAAiB,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAA;CAAE,CAoBrD;AAED;;;;;;;;GAQG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE,QAAQ,EAAE,OAAO,EAAE,GAAG,MAAM,CAezE;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,cAAc,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,GAAG,IAAI,EAC9C,GAAG,EAAE,YAAY,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,EAC7B,YAAY,EAAE,CAAC,GAAG,SAAS,EAC3B,GAAG,EAAE,MAAM,GACV,MAAM,CAGR"}
|
package/dist/ssr.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { createComponentInstance } from './update-loop.js';
|
|
2
|
-
import { setRenderContext, clearRenderContext } from './render-context.js';
|
|
2
|
+
import { setRenderContext, clearRenderContext, getInstanceViewBag } from './render-context.js';
|
|
3
3
|
import { setFlatBindings } from './binding.js';
|
|
4
|
-
import { createView } from './view-helpers.js';
|
|
5
4
|
/**
|
|
6
5
|
* Render a component to DOM nodes for SSR, returning both the produced
|
|
7
6
|
* nodes and the component instance (so callers can compose trees before
|
|
@@ -29,7 +28,10 @@ export function renderNodes(def, initialState, env, parentLifetime) {
|
|
|
29
28
|
send: inst.send,
|
|
30
29
|
instance: inst,
|
|
31
30
|
});
|
|
32
|
-
|
|
31
|
+
// v0.4 Tier 1.2 + cache: bag memoised on the SSR instance for the
|
|
32
|
+
// single render pass.
|
|
33
|
+
const bag = getInstanceViewBag(inst, inst.send);
|
|
34
|
+
const nodes = def.view(bag);
|
|
33
35
|
clearRenderContext();
|
|
34
36
|
setFlatBindings(null);
|
|
35
37
|
return { nodes, inst };
|
package/dist/ssr.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ssr.js","sourceRoot":"","sources":["../src/ssr.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,uBAAuB,EAA0B,MAAM,kBAAkB,CAAA;AAClF,OAAO,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAA;
|
|
1
|
+
{"version":3,"file":"ssr.js","sourceRoot":"","sources":["../src/ssr.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ComponentDef, Lifetime, Binding } from './types.js'\nimport { createComponentInstance, type ComponentInstance } from './update-loop.js'\nimport { setRenderContext, clearRenderContext, getInstanceViewBag } from './render-context.js'\nimport { setFlatBindings } from './binding.js'\nimport type { View } from './view-helpers.js'\nimport type { DomEnv } from './dom-env.js'\n\n/**\n * Render a component to DOM nodes for SSR, returning both the produced\n * nodes and the component instance (so callers can compose trees before\n * serializing — e.g. `@llui/vike` stitches layout + page nodes at the\n * `pageSlot()` marker position before one final serialization pass).\n *\n * `env` is the `DomEnv` backing this render pass — obtain one from\n * `@llui/dom/ssr/jsdom` (`jsdomEnv()`) or `@llui/dom/ssr/linkedom`\n * (`linkedomEnv()`). Passing the env per call keeps SSR concurrent-safe\n * and lets a single process host multiple DOM implementations.\n *\n * Accepts an optional `parentLifetime` so the rendered instance's rootLifetime\n * becomes a child of an existing scope tree — used by persistent layouts\n * so contexts provided by an outer layout are reachable from an inner\n * page via `useContext`.\n */\nexport function renderNodes<S, M, E, D = void>(\n def: ComponentDef<S, M, E, D>,\n initialState: S | undefined,\n env: DomEnv,\n parentLifetime?: Lifetime,\n): { nodes: Node[]; inst: ComponentInstance<S, M, E> } {\n const inst = createComponentInstance(def, undefined, parentLifetime ?? null, env)\n if (initialState !== undefined) {\n inst.state = initialState\n }\n\n setFlatBindings(inst.allBindings)\n setRenderContext({\n ...inst,\n send: inst.send as (msg: unknown) => void,\n instance: inst as ComponentInstance,\n })\n // v0.4 Tier 1.2 + cache: bag memoised on the SSR instance for the\n // single render pass.\n const bag = getInstanceViewBag<S, M>(inst as ComponentInstance, inst.send) as View<S, M>\n const nodes = def.view(bag)\n clearRenderContext()\n setFlatBindings(null)\n\n return { nodes, inst }\n}\n\n/**\n * Serialize an array of DOM nodes to an HTML string, adding\n * `data-llui-hydrate` markers on elements that own reactive bindings.\n *\n * Accepts a flat binding list so compositions of multiple instances\n * (layout + page, for persistent-layout SSR) produce correct markers\n * across the whole tree. Pass the union of every composed instance's\n * `allBindings`.\n */\nexport function serializeNodes(nodes: Node[], bindings: Binding[]): string {\n const hydrateElements = new Set<Node>()\n for (const binding of bindings) {\n const node = binding.node\n if (node.nodeType === 1) {\n hydrateElements.add(node)\n } else if (node.parentNode && node.parentNode.nodeType === 1) {\n hydrateElements.add(node.parentNode)\n }\n }\n let html = ''\n for (const node of nodes) {\n html += nodeToStringWithMarkers(node, hydrateElements)\n }\n return html\n}\n\n/**\n * Render a component to an HTML string for SSR.\n * Evaluates view() against the initial state (or provided data),\n * serializes the DOM to HTML, and adds data-llui-hydrate markers\n * on nodes with reactive bindings.\n *\n * `env` is the `DomEnv` backing this render pass — obtain one from\n * `@llui/dom/ssr/jsdom` or `@llui/dom/ssr/linkedom`.\n *\n * For persistent layouts, use `renderNodes` + `serializeNodes` directly\n * so layout and page nodes can be composed before serialization.\n */\nexport function renderToString<S, M, E, D = void>(\n def: ComponentDef<S, M, E, D>,\n initialState: S | undefined,\n env: DomEnv,\n): string {\n const { nodes, inst } = renderNodes(def, initialState, env)\n return serializeNodes(nodes, inst.allBindings)\n}\n\nfunction nodeToStringWithMarkers(node: Node, bindingNodes: Set<Node>): string {\n if (node.nodeType === 3) {\n return escapeHtml(node.textContent ?? '')\n }\n if (node.nodeType === 8) {\n return `<!--${node.textContent ?? ''}-->`\n }\n if (node.nodeType === 1) {\n const el = node as Element\n return elementToString(el, bindingNodes)\n }\n return ''\n}\n\nfunction elementToString(el: Element, bindingNodes: Set<Node>): string {\n const tag = el.tagName.toLowerCase()\n let attrs = ''\n\n for (let i = 0; i < el.attributes.length; i++) {\n const attr = el.attributes[i]!\n // Skip event handler attributes\n if (attr.name.startsWith('on')) continue\n attrs += ` ${attr.name}=\"${escapeAttr(attr.value)}\"`\n }\n\n // Add hydrate marker if this element or any of its text children have bindings\n if (bindingNodes.has(el)) {\n attrs += ' data-llui-hydrate'\n }\n\n // Void elements\n if (isVoidElement(tag)) {\n return `<${tag}${attrs} />`\n }\n\n let children = ''\n for (let i = 0; i < el.childNodes.length; i++) {\n children += nodeToStringWithMarkers(el.childNodes[i]!, bindingNodes)\n }\n\n return `<${tag}${attrs}>${children}</${tag}>`\n}\n\nfunction escapeHtml(s: string): string {\n return s.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>')\n}\n\nfunction escapeAttr(s: string): string {\n return s.replace(/&/g, '&').replace(/\"/g, '"')\n}\n\nconst VOID_ELEMENTS = new Set([\n 'area',\n 'base',\n 'br',\n 'col',\n 'embed',\n 'hr',\n 'img',\n 'input',\n 'link',\n 'meta',\n 'param',\n 'source',\n 'track',\n 'wbr',\n])\n\nfunction isVoidElement(tag: string): boolean {\n return VOID_ELEMENTS.has(tag)\n}\n"]}
|