@llui/compiler 0.3.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/LICENSE +21 -0
- package/dist/accessor-resolver.d.ts +58 -0
- package/dist/accessor-resolver.d.ts.map +1 -0
- package/dist/accessor-resolver.js +119 -0
- package/dist/accessor-resolver.js.map +1 -0
- package/dist/binding-descriptors.d.ts +105 -0
- package/dist/binding-descriptors.d.ts.map +1 -0
- package/dist/binding-descriptors.js +340 -0
- package/dist/binding-descriptors.js.map +1 -0
- package/dist/collect-deps.d.ts +49 -0
- package/dist/collect-deps.d.ts.map +1 -0
- package/dist/collect-deps.js +444 -0
- package/dist/collect-deps.js.map +1 -0
- package/dist/compiler-cache.d.ts +20 -0
- package/dist/compiler-cache.d.ts.map +1 -0
- package/dist/compiler-cache.js +20 -0
- package/dist/compiler-cache.js.map +1 -0
- package/dist/cross-file-resolver.d.ts +109 -0
- package/dist/cross-file-resolver.d.ts.map +1 -0
- package/dist/cross-file-resolver.js +530 -0
- package/dist/cross-file-resolver.js.map +1 -0
- package/dist/cross-file-walker.d.ts +63 -0
- package/dist/cross-file-walker.d.ts.map +1 -0
- package/dist/cross-file-walker.js +516 -0
- package/dist/cross-file-walker.js.map +1 -0
- package/dist/diagnostic.d.ts +76 -0
- package/dist/diagnostic.d.ts.map +1 -0
- package/dist/diagnostic.js +59 -0
- package/dist/diagnostic.js.map +1 -0
- package/dist/index.d.ts +27 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +37 -0
- package/dist/index.js.map +1 -0
- package/dist/introspection-factory.d.ts +54 -0
- package/dist/introspection-factory.d.ts.map +1 -0
- package/dist/introspection-factory.js +46 -0
- package/dist/introspection-factory.js.map +1 -0
- package/dist/manifest.d.ts +144 -0
- package/dist/manifest.d.ts.map +1 -0
- package/dist/manifest.js +209 -0
- package/dist/manifest.js.map +1 -0
- package/dist/module.d.ts +222 -0
- package/dist/module.d.ts.map +1 -0
- package/dist/module.js +256 -0
- package/dist/module.js.map +1 -0
- package/dist/modules/_element-helpers.d.ts +4 -0
- package/dist/modules/_element-helpers.d.ts.map +1 -0
- package/dist/modules/_element-helpers.js +138 -0
- package/dist/modules/_element-helpers.js.map +1 -0
- package/dist/modules/_msg-variants.d.ts +10 -0
- package/dist/modules/_msg-variants.d.ts.map +1 -0
- package/dist/modules/_msg-variants.js +97 -0
- package/dist/modules/_msg-variants.js.map +1 -0
- package/dist/modules/_shared.d.ts +16 -0
- package/dist/modules/_shared.d.ts.map +1 -0
- package/dist/modules/_shared.js +30 -0
- package/dist/modules/_shared.js.map +1 -0
- package/dist/modules/accessibility.d.ts +3 -0
- package/dist/modules/accessibility.d.ts.map +1 -0
- package/dist/modules/accessibility.js +82 -0
- package/dist/modules/accessibility.js.map +1 -0
- package/dist/modules/accessor-side-effect.d.ts +3 -0
- package/dist/modules/accessor-side-effect.d.ts.map +1 -0
- package/dist/modules/accessor-side-effect.js +113 -0
- package/dist/modules/accessor-side-effect.js.map +1 -0
- package/dist/modules/agent-emits-drift.d.ts +3 -0
- package/dist/modules/agent-emits-drift.d.ts.map +1 -0
- package/dist/modules/agent-emits-drift.js +158 -0
- package/dist/modules/agent-emits-drift.js.map +1 -0
- package/dist/modules/agent-example-on-payload.d.ts +3 -0
- package/dist/modules/agent-example-on-payload.d.ts.map +1 -0
- package/dist/modules/agent-example-on-payload.js +53 -0
- package/dist/modules/agent-example-on-payload.js.map +1 -0
- package/dist/modules/agent-exclusive-annotations.d.ts +3 -0
- package/dist/modules/agent-exclusive-annotations.d.ts.map +1 -0
- package/dist/modules/agent-exclusive-annotations.js +68 -0
- package/dist/modules/agent-exclusive-annotations.js.map +1 -0
- package/dist/modules/agent-missing-intent.d.ts +3 -0
- package/dist/modules/agent-missing-intent.d.ts.map +1 -0
- package/dist/modules/agent-missing-intent.js +47 -0
- package/dist/modules/agent-missing-intent.js.map +1 -0
- package/dist/modules/agent-msg-resolvable.d.ts +3 -0
- package/dist/modules/agent-msg-resolvable.d.ts.map +1 -0
- package/dist/modules/agent-msg-resolvable.js +161 -0
- package/dist/modules/agent-msg-resolvable.js.map +1 -0
- package/dist/modules/agent-nonextractable-handler.d.ts +3 -0
- package/dist/modules/agent-nonextractable-handler.d.ts.map +1 -0
- package/dist/modules/agent-nonextractable-handler.js +127 -0
- package/dist/modules/agent-nonextractable-handler.js.map +1 -0
- package/dist/modules/agent-optional-field-undocumented.d.ts +3 -0
- package/dist/modules/agent-optional-field-undocumented.d.ts.map +1 -0
- package/dist/modules/agent-optional-field-undocumented.js +67 -0
- package/dist/modules/agent-optional-field-undocumented.js.map +1 -0
- package/dist/modules/agent-tagsend-translator-missing.d.ts +3 -0
- package/dist/modules/agent-tagsend-translator-missing.d.ts.map +1 -0
- package/dist/modules/agent-tagsend-translator-missing.js +58 -0
- package/dist/modules/agent-tagsend-translator-missing.js.map +1 -0
- package/dist/modules/agent-warning-on-confirm.d.ts +3 -0
- package/dist/modules/agent-warning-on-confirm.d.ts.map +1 -0
- package/dist/modules/agent-warning-on-confirm.js +46 -0
- package/dist/modules/agent-warning-on-confirm.js.map +1 -0
- package/dist/modules/async-update.d.ts +3 -0
- package/dist/modules/async-update.d.ts.map +1 -0
- package/dist/modules/async-update.js +86 -0
- package/dist/modules/async-update.js.map +1 -0
- package/dist/modules/binding-descriptors.d.ts +4 -0
- package/dist/modules/binding-descriptors.d.ts.map +1 -0
- package/dist/modules/binding-descriptors.js +48 -0
- package/dist/modules/binding-descriptors.js.map +1 -0
- package/dist/modules/bitmask-overflow.d.ts +3 -0
- package/dist/modules/bitmask-overflow.d.ts.map +1 -0
- package/dist/modules/bitmask-overflow.js +152 -0
- package/dist/modules/bitmask-overflow.js.map +1 -0
- package/dist/modules/compiler-stamp.d.ts +3 -0
- package/dist/modules/compiler-stamp.d.ts.map +1 -0
- package/dist/modules/compiler-stamp.js +44 -0
- package/dist/modules/compiler-stamp.js.map +1 -0
- package/dist/modules/component-meta.d.ts +3 -0
- package/dist/modules/component-meta.d.ts.map +1 -0
- package/dist/modules/component-meta.js +44 -0
- package/dist/modules/component-meta.js.map +1 -0
- package/dist/modules/controlled-input.d.ts +3 -0
- package/dist/modules/controlled-input.d.ts.map +1 -0
- package/dist/modules/controlled-input.js +68 -0
- package/dist/modules/controlled-input.js.map +1 -0
- package/dist/modules/core-synthesis.d.ts +18 -0
- package/dist/modules/core-synthesis.d.ts.map +1 -0
- package/dist/modules/core-synthesis.js +748 -0
- package/dist/modules/core-synthesis.js.map +1 -0
- package/dist/modules/direct-state-in-view.d.ts +3 -0
- package/dist/modules/direct-state-in-view.d.ts.map +1 -0
- package/dist/modules/direct-state-in-view.js +103 -0
- package/dist/modules/direct-state-in-view.js.map +1 -0
- package/dist/modules/each-closure-violation.d.ts +3 -0
- package/dist/modules/each-closure-violation.d.ts.map +1 -0
- package/dist/modules/each-closure-violation.js +255 -0
- package/dist/modules/each-closure-violation.js.map +1 -0
- package/dist/modules/each-memo.d.ts +15 -0
- package/dist/modules/each-memo.d.ts.map +1 -0
- package/dist/modules/each-memo.js +115 -0
- package/dist/modules/each-memo.js.map +1 -0
- package/dist/modules/effect-without-handler.d.ts +3 -0
- package/dist/modules/effect-without-handler.d.ts.map +1 -0
- package/dist/modules/effect-without-handler.js +92 -0
- package/dist/modules/effect-without-handler.js.map +1 -0
- package/dist/modules/element-rewrite.d.ts +22 -0
- package/dist/modules/element-rewrite.d.ts.map +1 -0
- package/dist/modules/element-rewrite.js +1017 -0
- package/dist/modules/element-rewrite.js.map +1 -0
- package/dist/modules/empty-props.d.ts +3 -0
- package/dist/modules/empty-props.d.ts.map +1 -0
- package/dist/modules/empty-props.js +50 -0
- package/dist/modules/empty-props.js.map +1 -0
- package/dist/modules/exhaustive-effect-handling.d.ts +3 -0
- package/dist/modules/exhaustive-effect-handling.d.ts.map +1 -0
- package/dist/modules/exhaustive-effect-handling.js +61 -0
- package/dist/modules/exhaustive-effect-handling.js.map +1 -0
- package/dist/modules/exhaustive-update.d.ts +3 -0
- package/dist/modules/exhaustive-update.d.ts.map +1 -0
- package/dist/modules/exhaustive-update.js +146 -0
- package/dist/modules/exhaustive-update.js.map +1 -0
- package/dist/modules/forgotten-spread.d.ts +3 -0
- package/dist/modules/forgotten-spread.d.ts.map +1 -0
- package/dist/modules/forgotten-spread.js +51 -0
- package/dist/modules/forgotten-spread.js.map +1 -0
- package/dist/modules/form-boilerplate.d.ts +3 -0
- package/dist/modules/form-boilerplate.d.ts.map +1 -0
- package/dist/modules/form-boilerplate.js +101 -0
- package/dist/modules/form-boilerplate.js.map +1 -0
- package/dist/modules/imperative-dom-in-view.d.ts +3 -0
- package/dist/modules/imperative-dom-in-view.d.ts.map +1 -0
- package/dist/modules/imperative-dom-in-view.js +123 -0
- package/dist/modules/imperative-dom-in-view.js.map +1 -0
- package/dist/modules/item-dedup.d.ts +7 -0
- package/dist/modules/item-dedup.d.ts.map +1 -0
- package/dist/modules/item-dedup.js +204 -0
- package/dist/modules/item-dedup.js.map +1 -0
- package/dist/modules/map-on-state-array.d.ts +3 -0
- package/dist/modules/map-on-state-array.d.ts.map +1 -0
- package/dist/modules/map-on-state-array.js +84 -0
- package/dist/modules/map-on-state-array.js.map +1 -0
- package/dist/modules/mask-legend.d.ts +10 -0
- package/dist/modules/mask-legend.d.ts.map +1 -0
- package/dist/modules/mask-legend.js +50 -0
- package/dist/modules/mask-legend.js.map +1 -0
- package/dist/modules/missing-memo.d.ts +3 -0
- package/dist/modules/missing-memo.d.ts.map +1 -0
- package/dist/modules/missing-memo.js +114 -0
- package/dist/modules/missing-memo.js.map +1 -0
- package/dist/modules/msg-annotations.d.ts +9 -0
- package/dist/modules/msg-annotations.d.ts.map +1 -0
- package/dist/modules/msg-annotations.js +54 -0
- package/dist/modules/msg-annotations.js.map +1 -0
- package/dist/modules/msg-schema.d.ts +10 -0
- package/dist/modules/msg-schema.d.ts.map +1 -0
- package/dist/modules/msg-schema.js +70 -0
- package/dist/modules/msg-schema.js.map +1 -0
- package/dist/modules/namespace-import.d.ts +3 -0
- package/dist/modules/namespace-import.d.ts.map +1 -0
- package/dist/modules/namespace-import.js +80 -0
- package/dist/modules/namespace-import.js.map +1 -0
- package/dist/modules/nested-send-in-update.d.ts +3 -0
- package/dist/modules/nested-send-in-update.d.ts.map +1 -0
- package/dist/modules/nested-send-in-update.js +77 -0
- package/dist/modules/nested-send-in-update.js.map +1 -0
- package/dist/modules/no-barrel-import-when-subpath-exists.d.ts +3 -0
- package/dist/modules/no-barrel-import-when-subpath-exists.d.ts.map +1 -0
- package/dist/modules/no-barrel-import-when-subpath-exists.js +100 -0
- package/dist/modules/no-barrel-import-when-subpath-exists.js.map +1 -0
- package/dist/modules/no-eager-item-accessor.d.ts +3 -0
- package/dist/modules/no-eager-item-accessor.d.ts.map +1 -0
- package/dist/modules/no-eager-item-accessor.js +74 -0
- package/dist/modules/no-eager-item-accessor.js.map +1 -0
- package/dist/modules/no-let-reactive-accessor.d.ts +3 -0
- package/dist/modules/no-let-reactive-accessor.d.ts.map +1 -0
- package/dist/modules/no-let-reactive-accessor.js +227 -0
- package/dist/modules/no-let-reactive-accessor.js.map +1 -0
- package/dist/modules/no-list-render-in-sample.d.ts +3 -0
- package/dist/modules/no-list-render-in-sample.d.ts.map +1 -0
- package/dist/modules/no-list-render-in-sample.js +89 -0
- package/dist/modules/no-list-render-in-sample.js.map +1 -0
- package/dist/modules/no-sample-in-accessor.d.ts +3 -0
- package/dist/modules/no-sample-in-accessor.d.ts.map +1 -0
- package/dist/modules/no-sample-in-accessor.js +141 -0
- package/dist/modules/no-sample-in-accessor.js.map +1 -0
- package/dist/modules/no-sample-in-reactive-position.d.ts +3 -0
- package/dist/modules/no-sample-in-reactive-position.d.ts.map +1 -0
- package/dist/modules/no-sample-in-reactive-position.js +72 -0
- package/dist/modules/no-sample-in-reactive-position.js.map +1 -0
- package/dist/modules/pure-update-function.d.ts +3 -0
- package/dist/modules/pure-update-function.d.ts.map +1 -0
- package/dist/modules/pure-update-function.js +127 -0
- package/dist/modules/pure-update-function.js.map +1 -0
- package/dist/modules/reactive-paths.d.ts +3 -0
- package/dist/modules/reactive-paths.d.ts.map +1 -0
- package/dist/modules/reactive-paths.js +77 -0
- package/dist/modules/reactive-paths.js.map +1 -0
- package/dist/modules/row-factory.d.ts +12 -0
- package/dist/modules/row-factory.d.ts.map +1 -0
- package/dist/modules/row-factory.js +385 -0
- package/dist/modules/row-factory.js.map +1 -0
- package/dist/modules/schema-hash.d.ts +15 -0
- package/dist/modules/schema-hash.d.ts.map +1 -0
- package/dist/modules/schema-hash.js +70 -0
- package/dist/modules/schema-hash.js.map +1 -0
- package/dist/modules/spread-in-children.d.ts +3 -0
- package/dist/modules/spread-in-children.d.ts.map +1 -0
- package/dist/modules/spread-in-children.js +144 -0
- package/dist/modules/spread-in-children.js.map +1 -0
- package/dist/modules/state-mutation.d.ts +3 -0
- package/dist/modules/state-mutation.d.ts.map +1 -0
- package/dist/modules/state-mutation.js +138 -0
- package/dist/modules/state-mutation.js.map +1 -0
- package/dist/modules/state-schema.d.ts +8 -0
- package/dist/modules/state-schema.d.ts.map +1 -0
- package/dist/modules/state-schema.js +55 -0
- package/dist/modules/state-schema.js.map +1 -0
- package/dist/modules/static-items.d.ts +3 -0
- package/dist/modules/static-items.d.ts.map +1 -0
- package/dist/modules/static-items.js +125 -0
- package/dist/modules/static-items.js.map +1 -0
- package/dist/modules/static-on.d.ts +3 -0
- package/dist/modules/static-on.d.ts.map +1 -0
- package/dist/modules/static-on.js +100 -0
- package/dist/modules/static-on.js.map +1 -0
- package/dist/modules/string-effect-callback.d.ts +3 -0
- package/dist/modules/string-effect-callback.d.ts.map +1 -0
- package/dist/modules/string-effect-callback.js +50 -0
- package/dist/modules/string-effect-callback.js.map +1 -0
- package/dist/modules/structural-mask.d.ts +8 -0
- package/dist/modules/structural-mask.d.ts.map +1 -0
- package/dist/modules/structural-mask.js +76 -0
- package/dist/modules/structural-mask.js.map +1 -0
- package/dist/modules/subapp-requires-reason.d.ts +3 -0
- package/dist/modules/subapp-requires-reason.d.ts.map +1 -0
- package/dist/modules/subapp-requires-reason.js +129 -0
- package/dist/modules/subapp-requires-reason.js.map +1 -0
- package/dist/modules/text-mask.d.ts +12 -0
- package/dist/modules/text-mask.d.ts.map +1 -0
- package/dist/modules/text-mask.js +63 -0
- package/dist/modules/text-mask.js.map +1 -0
- package/dist/modules/view-bag-import.d.ts +3 -0
- package/dist/modules/view-bag-import.d.ts.map +1 -0
- package/dist/modules/view-bag-import.js +80 -0
- package/dist/modules/view-bag-import.js.map +1 -0
- package/dist/msg-annotations.d.ts +104 -0
- package/dist/msg-annotations.d.ts.map +1 -0
- package/dist/msg-annotations.js +242 -0
- package/dist/msg-annotations.js.map +1 -0
- package/dist/msg-schema.d.ts +130 -0
- package/dist/msg-schema.d.ts.map +1 -0
- package/dist/msg-schema.js +770 -0
- package/dist/msg-schema.js.map +1 -0
- package/dist/schema-hash.d.ts +16 -0
- package/dist/schema-hash.d.ts.map +1 -0
- package/dist/schema-hash.js +31 -0
- package/dist/schema-hash.js.map +1 -0
- package/dist/state-schema.d.ts +41 -0
- package/dist/state-schema.d.ts.map +1 -0
- package/dist/state-schema.js +156 -0
- package/dist/state-schema.js.map +1 -0
- package/dist/transform.d.ts +109 -0
- package/dist/transform.d.ts.map +1 -0
- package/dist/transform.js +1390 -0
- package/dist/transform.js.map +1 -0
- package/dist/version.d.ts +11 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +11 -0
- package/dist/version.js.map +1 -0
- package/package.json +47 -0
|
@@ -0,0 +1,1017 @@
|
|
|
1
|
+
// `element-rewrite` — transforms `div(...)` / `button(...)` / etc.
|
|
2
|
+
// (every helper in `ELEMENT_HELPERS`) into `elSplit(...)` /
|
|
3
|
+
// `elTemplate(...)` / `__cloneStaticTemplate(...)` calls.
|
|
4
|
+
//
|
|
5
|
+
// The transform classifies each prop of the element call:
|
|
6
|
+
// - Static literals → emit as a one-time `__e.prop = X` setter
|
|
7
|
+
// - Event handlers (`onClick`, `onInput`, ...) → emit as
|
|
8
|
+
// `[eventName, handler]` tuples
|
|
9
|
+
// - Reactive accessors (arrows / `memo(arrow)` / identifier-bound
|
|
10
|
+
// forms) → emit as `[mask, kind, key, accessor]` binding tuples
|
|
11
|
+
// - Per-item shapes (`item.field`, `item((r) => r.x)`) inside
|
|
12
|
+
// each() render callbacks → emit with `FULL_MASK` since the
|
|
13
|
+
// accessor closes over a per-row argument the runtime supplies
|
|
14
|
+
//
|
|
15
|
+
// When the element has only static + event content, the rewrite
|
|
16
|
+
// further specializes into `__cloneStaticTemplate(...)` (prerendered
|
|
17
|
+
// HTML clone). When the element's subtree has multiple nested
|
|
18
|
+
// elements, `analyzeSubtree` + `emitSubtreeTemplate` collapse the
|
|
19
|
+
// whole subtree into a single `elTemplate(...)` call with a
|
|
20
|
+
// patch-by-walk function.
|
|
21
|
+
//
|
|
22
|
+
// Owned by this module since v2c/decomp-22. The thin-wrapper
|
|
23
|
+
// migration (decomp-17) put the registry on the call path; the
|
|
24
|
+
// helpers (and `tryTransformElementCall` itself) moved here in this
|
|
25
|
+
// commit so the module is self-contained.
|
|
26
|
+
//
|
|
27
|
+
// Fires top-down (`transformCallEnter`). Sets module-level state
|
|
28
|
+
// via `ELEMENT_REWRITE_SLOT` for the umbrella's `cleanupImports`
|
|
29
|
+
// to decide whether `elSplit` / `elTemplate` / `__cloneStaticTemplate`
|
|
30
|
+
// need their runtime imports.
|
|
31
|
+
import ts from 'typescript';
|
|
32
|
+
import { resolveLocalConstInitializer, isMemoCallWithArrowArg } from '../accessor-resolver.js';
|
|
33
|
+
import { computeAccessorMask, createMaskLiteral } from '../transform.js';
|
|
34
|
+
export const ELEMENT_REWRITE_SLOT = 'element-rewrite:state';
|
|
35
|
+
export function elementRewriteModule(options) {
|
|
36
|
+
const { importedHelpers, fieldBits, fieldBitsHi } = options;
|
|
37
|
+
return {
|
|
38
|
+
name: 'element-rewrite',
|
|
39
|
+
compilerVersion: '^0.3.0',
|
|
40
|
+
diagnostics: [],
|
|
41
|
+
visitors: {},
|
|
42
|
+
transformCallEnter(ctx, node) {
|
|
43
|
+
const slot = ctx.analysis.perModule.get(ELEMENT_REWRITE_SLOT);
|
|
44
|
+
const state = slot ?? {
|
|
45
|
+
compiled: new Set(),
|
|
46
|
+
bailed: new Set(),
|
|
47
|
+
usesElSplit: false,
|
|
48
|
+
usesElTemplate: false,
|
|
49
|
+
usesCloneStaticTemplate: false,
|
|
50
|
+
};
|
|
51
|
+
if (!slot)
|
|
52
|
+
ctx.analysis.perModule.set(ELEMENT_REWRITE_SLOT, state);
|
|
53
|
+
const transformed = tryTransformElementCall(node, importedHelpers, fieldBits, state.compiled, state.bailed, ctx.factory, fieldBitsHi);
|
|
54
|
+
if (!transformed)
|
|
55
|
+
return null;
|
|
56
|
+
if (ts.isIdentifier(transformed.expression)) {
|
|
57
|
+
if (transformed.expression.text === 'elTemplate')
|
|
58
|
+
state.usesElTemplate = true;
|
|
59
|
+
else if (transformed.expression.text === 'elSplit')
|
|
60
|
+
state.usesElSplit = true;
|
|
61
|
+
else if (transformed.expression.text === '__cloneStaticTemplate')
|
|
62
|
+
state.usesCloneStaticTemplate = true;
|
|
63
|
+
}
|
|
64
|
+
return transformed;
|
|
65
|
+
},
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
// ─── Rewrite implementation (moved verbatim from transform.ts) ─────
|
|
69
|
+
const PROP_KEYS = new Set([
|
|
70
|
+
'value',
|
|
71
|
+
'checked',
|
|
72
|
+
'selected',
|
|
73
|
+
'disabled',
|
|
74
|
+
'readOnly',
|
|
75
|
+
'multiple',
|
|
76
|
+
'indeterminate',
|
|
77
|
+
'defaultValue',
|
|
78
|
+
'defaultChecked',
|
|
79
|
+
'innerHTML',
|
|
80
|
+
'textContent',
|
|
81
|
+
]);
|
|
82
|
+
function isStaticPrimitiveLiteral(expr) {
|
|
83
|
+
return (ts.isStringLiteral(expr) ||
|
|
84
|
+
ts.isNumericLiteral(expr) ||
|
|
85
|
+
ts.isNoSubstitutionTemplateLiteral(expr) ||
|
|
86
|
+
expr.kind === ts.SyntaxKind.TrueKeyword ||
|
|
87
|
+
expr.kind === ts.SyntaxKind.FalseKeyword ||
|
|
88
|
+
expr.kind === ts.SyntaxKind.NullKeyword);
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Classify a reactive-prop value. See `ResolvedReactiveValue` for the
|
|
92
|
+
* contract. Returns `null` only when the value is none of the recognized
|
|
93
|
+
* shapes (caller can fall back to its own branches — currently only
|
|
94
|
+
* `tryTransformElementCall` does this for `isPerItemFieldAccess` /
|
|
95
|
+
* `isHoistedPerItem`).
|
|
96
|
+
*/
|
|
97
|
+
function classifyReactiveValue(value) {
|
|
98
|
+
// Inline arrow / function expression at the call site
|
|
99
|
+
if (ts.isArrowFunction(value) || ts.isFunctionExpression(value)) {
|
|
100
|
+
return { kind: 'arrow', accessor: value, valueForBinding: value };
|
|
101
|
+
}
|
|
102
|
+
// Inline `memo(arrow)` at the call site
|
|
103
|
+
if (isMemoCallWithArrowArg(value)) {
|
|
104
|
+
return {
|
|
105
|
+
kind: 'memo-call',
|
|
106
|
+
accessor: value.arguments[0],
|
|
107
|
+
valueForBinding: value,
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
// Identifier — resolve and classify the resolved declaration
|
|
111
|
+
if (ts.isIdentifier(value)) {
|
|
112
|
+
const resolved = resolveLocalConstInitializer(value);
|
|
113
|
+
if (!resolved) {
|
|
114
|
+
// Imported / parameter / unbound — can't prove it's a primitive,
|
|
115
|
+
// can't prove it's a function. Caller must bail to runtime.
|
|
116
|
+
return { kind: 'bail' };
|
|
117
|
+
}
|
|
118
|
+
if (ts.isArrowFunction(resolved) || ts.isFunctionExpression(resolved)) {
|
|
119
|
+
return { kind: 'arrow', accessor: resolved, valueForBinding: value };
|
|
120
|
+
}
|
|
121
|
+
if (ts.isFunctionDeclaration(resolved)) {
|
|
122
|
+
return { kind: 'fn-decl', accessor: resolved, valueForBinding: value };
|
|
123
|
+
}
|
|
124
|
+
if (isMemoCallWithArrowArg(resolved)) {
|
|
125
|
+
return {
|
|
126
|
+
kind: 'memo-call',
|
|
127
|
+
accessor: resolved.arguments[0],
|
|
128
|
+
valueForBinding: value,
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
if (isStaticPrimitiveLiteral(resolved)) {
|
|
132
|
+
return { kind: 'static-literal' };
|
|
133
|
+
}
|
|
134
|
+
// Resolved to something else (object/array/expression) — conservative
|
|
135
|
+
// bail. We don't know if the runtime value is a function; the runtime
|
|
136
|
+
// element helper handles both cases correctly.
|
|
137
|
+
return { kind: 'bail' };
|
|
138
|
+
}
|
|
139
|
+
// Static literals at the call site
|
|
140
|
+
if (isStaticPrimitiveLiteral(value)) {
|
|
141
|
+
return { kind: 'static-literal' };
|
|
142
|
+
}
|
|
143
|
+
// CallExpression — caller decides (per-item, etc.)
|
|
144
|
+
return null;
|
|
145
|
+
}
|
|
146
|
+
function classifyKind(key) {
|
|
147
|
+
if (key === 'class' || key === 'className')
|
|
148
|
+
return 'class';
|
|
149
|
+
if (key.startsWith('style.'))
|
|
150
|
+
return 'style';
|
|
151
|
+
if (PROP_KEYS.has(key))
|
|
152
|
+
return 'prop';
|
|
153
|
+
return 'attr';
|
|
154
|
+
}
|
|
155
|
+
function resolveKey(key, kind) {
|
|
156
|
+
if (kind === 'class')
|
|
157
|
+
return 'class';
|
|
158
|
+
if (kind === 'style')
|
|
159
|
+
return key.slice(6);
|
|
160
|
+
if (kind === 'prop')
|
|
161
|
+
return key;
|
|
162
|
+
if (key === 'className')
|
|
163
|
+
return 'class';
|
|
164
|
+
return key;
|
|
165
|
+
}
|
|
166
|
+
// ─── emitStaticProp + tryTransformElementCall ────────────────────
|
|
167
|
+
function emitStaticProp(staticProps, f, kind, resolvedKey, value) {
|
|
168
|
+
switch (kind) {
|
|
169
|
+
case 'class':
|
|
170
|
+
staticProps.push(f.createExpressionStatement(f.createBinaryExpression(f.createPropertyAccessExpression(f.createIdentifier('__e'), 'className'), ts.SyntaxKind.EqualsToken, value)));
|
|
171
|
+
break;
|
|
172
|
+
case 'prop':
|
|
173
|
+
staticProps.push(f.createExpressionStatement(f.createBinaryExpression(f.createPropertyAccessExpression(f.createIdentifier('__e'), resolvedKey), ts.SyntaxKind.EqualsToken, value)));
|
|
174
|
+
break;
|
|
175
|
+
case 'style':
|
|
176
|
+
staticProps.push(f.createExpressionStatement(f.createCallExpression(f.createPropertyAccessExpression(f.createPropertyAccessExpression(f.createIdentifier('__e'), 'style'), 'setProperty'), undefined, [f.createStringLiteral(resolvedKey), value])));
|
|
177
|
+
break;
|
|
178
|
+
default: // attr
|
|
179
|
+
staticProps.push(f.createExpressionStatement(f.createCallExpression(f.createPropertyAccessExpression(f.createIdentifier('__e'), 'setAttribute'), undefined, [f.createStringLiteral(resolvedKey), value])));
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
// ── Pass 1: Element → elSplit ────────────────────────────────────
|
|
183
|
+
function tryTransformElementCall(node, helpers, fieldBits, compiled, bailed, f, fieldBitsHi = new Map()) {
|
|
184
|
+
if (!ts.isIdentifier(node.expression))
|
|
185
|
+
return null;
|
|
186
|
+
const localName = node.expression.text;
|
|
187
|
+
const originalName = helpers.get(localName);
|
|
188
|
+
if (!originalName)
|
|
189
|
+
return null;
|
|
190
|
+
// Handle children-only overload: `div([...])` — first arg is the children array.
|
|
191
|
+
// Normalize to props=undefined, children=firstArg so downstream logic works.
|
|
192
|
+
const firstArg = node.arguments[0];
|
|
193
|
+
const usesChildrenOnlyOverload = firstArg && ts.isArrayLiteralExpression(firstArg);
|
|
194
|
+
const propsArg = usesChildrenOnlyOverload ? undefined : firstArg;
|
|
195
|
+
if (propsArg && !ts.isObjectLiteralExpression(propsArg)) {
|
|
196
|
+
bailed.add(localName);
|
|
197
|
+
return null;
|
|
198
|
+
}
|
|
199
|
+
// Bail on spread assignments (`...parts.root`) — the compiler cannot
|
|
200
|
+
// statically classify spread contents, and silently dropping them would
|
|
201
|
+
// break consumers (e.g. @llui/components parts spreading). Fall back to
|
|
202
|
+
// the runtime element helper so spreads are applied normally.
|
|
203
|
+
if (propsArg &&
|
|
204
|
+
ts.isObjectLiteralExpression(propsArg) &&
|
|
205
|
+
propsArg.properties.some((p) => ts.isSpreadAssignment(p))) {
|
|
206
|
+
bailed.add(localName);
|
|
207
|
+
return null;
|
|
208
|
+
}
|
|
209
|
+
const tag = f.createStringLiteral(originalName);
|
|
210
|
+
// Classify props
|
|
211
|
+
const staticProps = [];
|
|
212
|
+
const events = [];
|
|
213
|
+
const bindings = [];
|
|
214
|
+
if (propsArg && ts.isObjectLiteralExpression(propsArg)) {
|
|
215
|
+
for (const prop of propsArg.properties) {
|
|
216
|
+
// Handle both PropertyAssignment (key: value) and ShorthandPropertyAssignment ({ id })
|
|
217
|
+
let key;
|
|
218
|
+
let value;
|
|
219
|
+
if (ts.isPropertyAssignment(prop)) {
|
|
220
|
+
if (!ts.isIdentifier(prop.name) && !ts.isStringLiteral(prop.name))
|
|
221
|
+
continue;
|
|
222
|
+
key = ts.isIdentifier(prop.name) ? prop.name.text : prop.name.text;
|
|
223
|
+
value = prop.initializer;
|
|
224
|
+
}
|
|
225
|
+
else if (ts.isShorthandPropertyAssignment(prop)) {
|
|
226
|
+
key = prop.name.text;
|
|
227
|
+
value = prop.name; // The identifier itself is the value
|
|
228
|
+
}
|
|
229
|
+
else {
|
|
230
|
+
continue;
|
|
231
|
+
}
|
|
232
|
+
if (key === 'key')
|
|
233
|
+
continue;
|
|
234
|
+
// Event handler
|
|
235
|
+
if (/^on[A-Z]/.test(key)) {
|
|
236
|
+
const eventName = key.slice(2).toLowerCase();
|
|
237
|
+
events.push(f.createArrayLiteralExpression([f.createStringLiteral(eventName), value]));
|
|
238
|
+
continue;
|
|
239
|
+
}
|
|
240
|
+
// Per-item shapes — handled before the general classifier because
|
|
241
|
+
// they appear inside `each().render` callbacks where `item` is a
|
|
242
|
+
// closed-over per-row accessor (zero-arg). The resolver above can't
|
|
243
|
+
// see them; they're shape-matched syntactically.
|
|
244
|
+
if (isPerItemFieldAccess(value) || isHoistedPerItem(value)) {
|
|
245
|
+
const kind = classifyKind(key);
|
|
246
|
+
const resolvedKey = resolveKey(key, kind);
|
|
247
|
+
bindings.push(f.createArrayLiteralExpression([
|
|
248
|
+
createMaskLiteral(f, 0xffffffff | 0),
|
|
249
|
+
f.createStringLiteral(kind),
|
|
250
|
+
f.createStringLiteral(resolvedKey),
|
|
251
|
+
value,
|
|
252
|
+
]));
|
|
253
|
+
continue;
|
|
254
|
+
}
|
|
255
|
+
if (ts.isCallExpression(value) && isPerItemCall(value)) {
|
|
256
|
+
const kind = classifyKind(key);
|
|
257
|
+
const resolvedKey = resolveKey(key, kind);
|
|
258
|
+
bindings.push(f.createArrayLiteralExpression([
|
|
259
|
+
createMaskLiteral(f, 0xffffffff | 0),
|
|
260
|
+
f.createStringLiteral(kind),
|
|
261
|
+
f.createStringLiteral(resolvedKey),
|
|
262
|
+
value,
|
|
263
|
+
]));
|
|
264
|
+
continue;
|
|
265
|
+
}
|
|
266
|
+
// Classify the value at a reactive-prop position:
|
|
267
|
+
// - inline arrow / fn-expr at the call site
|
|
268
|
+
// - inline `memo(arrow)` at the call site
|
|
269
|
+
// - Identifier referencing a const-bound arrow/fn-expr in scope
|
|
270
|
+
// - Identifier referencing a hoisted function declaration in scope
|
|
271
|
+
// - Identifier referencing `const x = memo(arrow)` in scope
|
|
272
|
+
// - Identifier referencing a static primitive literal
|
|
273
|
+
// - Anything else (imports, parameters, opaque expressions) — bail
|
|
274
|
+
// to runtime; the runtime helper handles `typeof v === 'function'`
|
|
275
|
+
// correctly for both function and primitive values.
|
|
276
|
+
const classified = classifyReactiveValue(value);
|
|
277
|
+
if (classified === null) {
|
|
278
|
+
// Unknown shape (a CallExpression that isn't memo/per-item, etc.)
|
|
279
|
+
// — historically bailed to runtime. Preserve that.
|
|
280
|
+
bailed.add(localName);
|
|
281
|
+
return null;
|
|
282
|
+
}
|
|
283
|
+
if (classified.kind === 'bail') {
|
|
284
|
+
bailed.add(localName);
|
|
285
|
+
return null;
|
|
286
|
+
}
|
|
287
|
+
if (classified.kind === 'static-literal') {
|
|
288
|
+
// Fall through to emitStaticProp (`__e.disabled = X`). Safe because
|
|
289
|
+
// we proved X is a primitive.
|
|
290
|
+
const kind = classifyKind(key);
|
|
291
|
+
const resolvedKey = resolveKey(key, kind);
|
|
292
|
+
emitStaticProp(staticProps, f, kind, resolvedKey, value);
|
|
293
|
+
continue;
|
|
294
|
+
}
|
|
295
|
+
// 'arrow' | 'fn-decl' | 'memo-call' — emit as a binding tuple. Mask is
|
|
296
|
+
// analyzed from the resolved accessor body (or the inner arrow inside
|
|
297
|
+
// a memo() call); the value emitted into the binding tuple is what the
|
|
298
|
+
// runtime calls as `accessor(state)` — for inline arrows we keep the
|
|
299
|
+
// arrow itself (preserves the historical inlining behavior), for
|
|
300
|
+
// identifier-bound forms we keep the identifier so consumers see
|
|
301
|
+
// a single canonical reference (and `memo()` proxies aren't rebuilt
|
|
302
|
+
// per render).
|
|
303
|
+
{
|
|
304
|
+
const kind = classifyKind(key);
|
|
305
|
+
const resolvedKey = resolveKey(key, kind);
|
|
306
|
+
const { mask, maskHi, readsState } = computeAccessorMask(classified.accessor, fieldBits, undefined, fieldBitsHi);
|
|
307
|
+
// Zero-mask constant folding only applies to inline arrows whose body
|
|
308
|
+
// we can safely call at compile time. For identifier-bound forms
|
|
309
|
+
// (`accessor !== value`) we skip the fold — calling the identifier's
|
|
310
|
+
// declaration at compile time would be unsafe (different scope) and
|
|
311
|
+
// calling the identifier in the emitted output would defeat the point.
|
|
312
|
+
if (classified.kind === 'arrow' &&
|
|
313
|
+
classified.accessor === value &&
|
|
314
|
+
mask === 0 &&
|
|
315
|
+
maskHi === 0 &&
|
|
316
|
+
!readsState) {
|
|
317
|
+
emitStaticProp(staticProps, f, kind, resolvedKey, f.createCallExpression(classified.accessor, undefined, []));
|
|
318
|
+
continue;
|
|
319
|
+
}
|
|
320
|
+
const effectiveMask = mask === 0 && maskHi === 0 && readsState ? 0xffffffff | 0 : mask;
|
|
321
|
+
// Emit a 5-tuple only when the accessor reads a high-word
|
|
322
|
+
// prefix (positions 31..61). For the common ≤31-prefix case
|
|
323
|
+
// the emit stays byte-identical to the pre-multi-word baseline,
|
|
324
|
+
// and stale runtime bundles ignore the 5th slot.
|
|
325
|
+
const tupleEls = [
|
|
326
|
+
createMaskLiteral(f, effectiveMask),
|
|
327
|
+
f.createStringLiteral(kind),
|
|
328
|
+
f.createStringLiteral(resolvedKey),
|
|
329
|
+
classified.valueForBinding,
|
|
330
|
+
];
|
|
331
|
+
if (maskHi !== 0)
|
|
332
|
+
tupleEls.push(createMaskLiteral(f, maskHi));
|
|
333
|
+
bindings.push(f.createArrayLiteralExpression(tupleEls));
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
// Build elSplit args
|
|
338
|
+
const staticFn = staticProps.length > 0
|
|
339
|
+
? f.createArrowFunction(undefined, undefined, [f.createParameterDeclaration(undefined, undefined, '__e')], undefined, f.createToken(ts.SyntaxKind.EqualsGreaterThanToken), f.createBlock(staticProps, true))
|
|
340
|
+
: f.createNull();
|
|
341
|
+
const eventsArr = events.length > 0 ? f.createArrayLiteralExpression(events) : f.createNull();
|
|
342
|
+
const bindingsArr = bindings.length > 0 ? f.createArrayLiteralExpression(bindings) : f.createNull();
|
|
343
|
+
const children = usesChildrenOnlyOverload
|
|
344
|
+
? node.arguments[0]
|
|
345
|
+
: (node.arguments[1] ?? f.createNull());
|
|
346
|
+
compiled.add(localName);
|
|
347
|
+
// Subtree collapse: if children contain nested element helpers,
|
|
348
|
+
// collapse the entire tree into a single elTemplate() call
|
|
349
|
+
const analyzed = analyzeSubtree(node, helpers, fieldBits, [], fieldBitsHi);
|
|
350
|
+
if (analyzed && hasNestedElements(analyzed)) {
|
|
351
|
+
// Mark all descendant helpers as compiled for import cleanup
|
|
352
|
+
collectUsedHelpers(analyzed, compiled);
|
|
353
|
+
const templateCall = emitSubtreeTemplate(analyzed, fieldBits, f);
|
|
354
|
+
return templateCall;
|
|
355
|
+
}
|
|
356
|
+
// Static subtree prerendering: if no events, no bindings, and children
|
|
357
|
+
// are all static text, emit a <template> clone
|
|
358
|
+
if (events.length === 0 && bindings.length === 0 && isStaticChildren(children)) {
|
|
359
|
+
const html = buildStaticHTML(originalName, staticProps, children, f);
|
|
360
|
+
if (html) {
|
|
361
|
+
return emitTemplateClone(html, f);
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
const call = f.createCallExpression(f.createIdentifier('elSplit'), undefined, [
|
|
365
|
+
tag,
|
|
366
|
+
staticFn,
|
|
367
|
+
eventsArr,
|
|
368
|
+
bindingsArr,
|
|
369
|
+
children,
|
|
370
|
+
]);
|
|
371
|
+
ts.addSyntheticLeadingComment(call, ts.SyntaxKind.MultiLineCommentTrivia, '@__PURE__', false);
|
|
372
|
+
return call;
|
|
373
|
+
}
|
|
374
|
+
// ─── Analysis + per-item heuristics ───────────────────────────────
|
|
375
|
+
const VOID_ELEMENTS = new Set([
|
|
376
|
+
'area',
|
|
377
|
+
'base',
|
|
378
|
+
'br',
|
|
379
|
+
'col',
|
|
380
|
+
'embed',
|
|
381
|
+
'hr',
|
|
382
|
+
'img',
|
|
383
|
+
'input',
|
|
384
|
+
'link',
|
|
385
|
+
'meta',
|
|
386
|
+
'param',
|
|
387
|
+
'source',
|
|
388
|
+
'track',
|
|
389
|
+
'wbr',
|
|
390
|
+
]);
|
|
391
|
+
/**
|
|
392
|
+
* Try to analyze an element call and all its descendants as a collapsible subtree.
|
|
393
|
+
* Returns null if any part of the tree is not eligible for collapse.
|
|
394
|
+
*/
|
|
395
|
+
function analyzeSubtree(node, helpers, fieldBits, path, fieldBitsHi = new Map()) {
|
|
396
|
+
if (!ts.isIdentifier(node.expression))
|
|
397
|
+
return null;
|
|
398
|
+
const localName = node.expression.text;
|
|
399
|
+
const tag = helpers.get(localName);
|
|
400
|
+
if (!tag)
|
|
401
|
+
return null;
|
|
402
|
+
// Handle children-only overload: `div([...])` — first arg is the children array.
|
|
403
|
+
// In that case, treat it as no props + children=firstArg.
|
|
404
|
+
const firstArg = node.arguments[0];
|
|
405
|
+
const usesChildrenOnlyOverload = firstArg && ts.isArrayLiteralExpression(firstArg);
|
|
406
|
+
const propsArg = usesChildrenOnlyOverload ? undefined : firstArg;
|
|
407
|
+
const childrenArg = usesChildrenOnlyOverload ? firstArg : node.arguments[1];
|
|
408
|
+
if (propsArg && !ts.isObjectLiteralExpression(propsArg))
|
|
409
|
+
return null;
|
|
410
|
+
const staticAttrs = [];
|
|
411
|
+
const events = [];
|
|
412
|
+
const bindings = [];
|
|
413
|
+
if (propsArg && ts.isObjectLiteralExpression(propsArg)) {
|
|
414
|
+
for (const prop of propsArg.properties) {
|
|
415
|
+
let key;
|
|
416
|
+
let value;
|
|
417
|
+
if (ts.isPropertyAssignment(prop)) {
|
|
418
|
+
if (!ts.isIdentifier(prop.name) && !ts.isStringLiteral(prop.name))
|
|
419
|
+
return null;
|
|
420
|
+
key = ts.isIdentifier(prop.name) ? prop.name.text : prop.name.text;
|
|
421
|
+
value = prop.initializer;
|
|
422
|
+
}
|
|
423
|
+
else if (ts.isShorthandPropertyAssignment(prop)) {
|
|
424
|
+
key = prop.name.text;
|
|
425
|
+
value = prop.name;
|
|
426
|
+
}
|
|
427
|
+
else {
|
|
428
|
+
return null;
|
|
429
|
+
}
|
|
430
|
+
if (key === 'key')
|
|
431
|
+
continue;
|
|
432
|
+
// Event handler
|
|
433
|
+
if (/^on[A-Z]/.test(key)) {
|
|
434
|
+
events.push([key.slice(2).toLowerCase(), value]);
|
|
435
|
+
continue;
|
|
436
|
+
}
|
|
437
|
+
// Resolve identifier → local const arrow initializer (see elSplit
|
|
438
|
+
// path for the full rationale).
|
|
439
|
+
if (ts.isIdentifier(value)) {
|
|
440
|
+
const resolved = resolveLocalConstInitializer(value);
|
|
441
|
+
if (resolved && (ts.isArrowFunction(resolved) || ts.isFunctionExpression(resolved))) {
|
|
442
|
+
value = resolved;
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
// Reactive binding
|
|
446
|
+
if (ts.isArrowFunction(value) || ts.isFunctionExpression(value)) {
|
|
447
|
+
const kind = classifyKind(key);
|
|
448
|
+
const resolvedKey = resolveKey(key, kind);
|
|
449
|
+
const { mask, maskHi, readsState } = computeAccessorMask(value, fieldBits, undefined, fieldBitsHi);
|
|
450
|
+
if (mask === 0 && maskHi === 0 && !readsState) {
|
|
451
|
+
// Constant fold — treat as static if we can extract a string
|
|
452
|
+
const staticVal = tryExtractStaticString(value);
|
|
453
|
+
if (staticVal !== null) {
|
|
454
|
+
const attrKey = kind === 'class' ? 'class' : resolvedKey;
|
|
455
|
+
staticAttrs.push([attrKey, staticVal]);
|
|
456
|
+
continue;
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
const finalMask = mask === 0 && maskHi === 0 && readsState ? 0xffffffff | 0 : mask;
|
|
460
|
+
bindings.push([finalMask, maskHi, kind, resolvedKey, value]);
|
|
461
|
+
continue;
|
|
462
|
+
}
|
|
463
|
+
// Per-item accessor call
|
|
464
|
+
if (ts.isCallExpression(value) && isPerItemCall(value)) {
|
|
465
|
+
const kind = classifyKind(key);
|
|
466
|
+
const resolvedKey = resolveKey(key, kind);
|
|
467
|
+
bindings.push([0xffffffff | 0, 0, kind, resolvedKey, value]);
|
|
468
|
+
continue;
|
|
469
|
+
}
|
|
470
|
+
// Per-item property access: item.field (or hoisted __a0/__a1/…)
|
|
471
|
+
if (isPerItemFieldAccess(value) || isHoistedPerItem(value)) {
|
|
472
|
+
const kind = classifyKind(key);
|
|
473
|
+
const resolvedKey = resolveKey(key, kind);
|
|
474
|
+
bindings.push([0xffffffff | 0, 0, kind, resolvedKey, value]);
|
|
475
|
+
continue;
|
|
476
|
+
}
|
|
477
|
+
// Static literal prop
|
|
478
|
+
if (ts.isStringLiteral(value)) {
|
|
479
|
+
const kind = classifyKind(key);
|
|
480
|
+
const attrKey = kind === 'class' ? 'class' : resolveKey(key, kind);
|
|
481
|
+
staticAttrs.push([attrKey, value.text]);
|
|
482
|
+
continue;
|
|
483
|
+
}
|
|
484
|
+
if (ts.isNumericLiteral(value)) {
|
|
485
|
+
const kind = classifyKind(key);
|
|
486
|
+
const attrKey = kind === 'class' ? 'class' : resolveKey(key, kind);
|
|
487
|
+
staticAttrs.push([attrKey, value.text]);
|
|
488
|
+
continue;
|
|
489
|
+
}
|
|
490
|
+
if (value.kind === ts.SyntaxKind.TrueKeyword) {
|
|
491
|
+
const kind = classifyKind(key);
|
|
492
|
+
const attrKey = kind === 'class' ? 'class' : resolveKey(key, kind);
|
|
493
|
+
staticAttrs.push([attrKey, '']);
|
|
494
|
+
continue;
|
|
495
|
+
}
|
|
496
|
+
// Non-literal prop — can't collapse
|
|
497
|
+
return null;
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
// Analyze children
|
|
501
|
+
const children = [];
|
|
502
|
+
if (childrenArg && ts.isArrayLiteralExpression(childrenArg)) {
|
|
503
|
+
let childIdx = 0;
|
|
504
|
+
for (const child of childrenArg.elements) {
|
|
505
|
+
// String literal child — static text node
|
|
506
|
+
if (ts.isStringLiteral(child) || ts.isNoSubstitutionTemplateLiteral(child)) {
|
|
507
|
+
children.push({ type: 'staticText', value: child.text });
|
|
508
|
+
childIdx++;
|
|
509
|
+
continue;
|
|
510
|
+
}
|
|
511
|
+
// text('literal') — static text
|
|
512
|
+
if (ts.isCallExpression(child) &&
|
|
513
|
+
ts.isIdentifier(child.expression) &&
|
|
514
|
+
child.expression.text === 'text') {
|
|
515
|
+
if (child.arguments.length >= 1 && ts.isStringLiteral(child.arguments[0])) {
|
|
516
|
+
children.push({ type: 'staticText', value: child.arguments[0].text });
|
|
517
|
+
childIdx++; // static text creates a text node in the template DOM
|
|
518
|
+
continue;
|
|
519
|
+
}
|
|
520
|
+
// Reactive text — accessor is first arg
|
|
521
|
+
const accessor = child.arguments[0];
|
|
522
|
+
if (ts.isArrowFunction(accessor) || ts.isFunctionExpression(accessor)) {
|
|
523
|
+
const { mask, maskHi, readsState } = computeAccessorMask(accessor, fieldBits, undefined, fieldBitsHi);
|
|
524
|
+
children.push({
|
|
525
|
+
type: 'reactiveText',
|
|
526
|
+
accessor,
|
|
527
|
+
mask: mask === 0 && maskHi === 0 && readsState ? 0xffffffff | 0 : mask,
|
|
528
|
+
maskHi,
|
|
529
|
+
childIdx,
|
|
530
|
+
});
|
|
531
|
+
childIdx++; // placeholder text node in template
|
|
532
|
+
continue;
|
|
533
|
+
}
|
|
534
|
+
// Per-item text: text(item(t => t.label))
|
|
535
|
+
if (ts.isCallExpression(accessor) && isPerItemCall(accessor)) {
|
|
536
|
+
children.push({
|
|
537
|
+
type: 'reactiveText',
|
|
538
|
+
accessor,
|
|
539
|
+
mask: 0xffffffff | 0,
|
|
540
|
+
maskHi: 0,
|
|
541
|
+
childIdx,
|
|
542
|
+
});
|
|
543
|
+
childIdx++; // placeholder text node in template
|
|
544
|
+
continue;
|
|
545
|
+
}
|
|
546
|
+
// Per-item text via property access: text(item.label)
|
|
547
|
+
// Also matches hoisted __a0/__a1/… identifiers produced by dedup.
|
|
548
|
+
if (isPerItemFieldAccess(accessor) || isHoistedPerItem(accessor)) {
|
|
549
|
+
children.push({
|
|
550
|
+
type: 'reactiveText',
|
|
551
|
+
accessor,
|
|
552
|
+
mask: 0xffffffff | 0,
|
|
553
|
+
maskHi: 0,
|
|
554
|
+
childIdx,
|
|
555
|
+
});
|
|
556
|
+
childIdx++;
|
|
557
|
+
continue;
|
|
558
|
+
}
|
|
559
|
+
return null; // unsupported text() form
|
|
560
|
+
}
|
|
561
|
+
// Element helper call — recurse
|
|
562
|
+
if (ts.isCallExpression(child) &&
|
|
563
|
+
ts.isIdentifier(child.expression) &&
|
|
564
|
+
helpers.has(child.expression.text)) {
|
|
565
|
+
const childNode = analyzeSubtree(child, helpers, fieldBits, [...path, childIdx], fieldBitsHi);
|
|
566
|
+
if (!childNode)
|
|
567
|
+
return null;
|
|
568
|
+
children.push({ type: 'element', node: childNode });
|
|
569
|
+
childIdx++;
|
|
570
|
+
continue;
|
|
571
|
+
}
|
|
572
|
+
// Anything else (each, branch, show, arbitrary expressions) — bail
|
|
573
|
+
return null;
|
|
574
|
+
}
|
|
575
|
+
// Note: mixed static + reactive text in the same parent is now supported
|
|
576
|
+
// because reactive text uses <!--$--> comment placeholders that break
|
|
577
|
+
// text-node merging at parse time.
|
|
578
|
+
}
|
|
579
|
+
else if (childrenArg && childrenArg.kind !== ts.SyntaxKind.NullKeyword) {
|
|
580
|
+
// Non-array children (e.g., spread, variable) — bail
|
|
581
|
+
return null;
|
|
582
|
+
}
|
|
583
|
+
return { tag, localName, staticAttrs, events, bindings, children, path };
|
|
584
|
+
}
|
|
585
|
+
function tryExtractStaticString(accessor) {
|
|
586
|
+
const body = ts.isArrowFunction(accessor) ? accessor.body : null;
|
|
587
|
+
if (body && ts.isStringLiteral(body))
|
|
588
|
+
return body.text;
|
|
589
|
+
return null;
|
|
590
|
+
}
|
|
591
|
+
/**
|
|
592
|
+
* Check if a subtree has any nested element children (worth collapsing).
|
|
593
|
+
*/
|
|
594
|
+
function hasNestedElements(node) {
|
|
595
|
+
return node.children.some((c) => c.type === 'element');
|
|
596
|
+
}
|
|
597
|
+
/**
|
|
598
|
+
* Collect all local helper names used in the subtree for import cleanup.
|
|
599
|
+
*/
|
|
600
|
+
function collectUsedHelpers(node, out) {
|
|
601
|
+
out.add(node.localName);
|
|
602
|
+
for (const child of node.children) {
|
|
603
|
+
if (child.type === 'element')
|
|
604
|
+
collectUsedHelpers(child.node, out);
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
/**
|
|
608
|
+
* Build the static HTML string from an analyzed subtree.
|
|
609
|
+
*/
|
|
610
|
+
function buildTemplateHTML(node) {
|
|
611
|
+
let html = `<${node.tag}`;
|
|
612
|
+
for (const [key, value] of node.staticAttrs) {
|
|
613
|
+
html += ` ${key}="${escapeAttr(value)}"`;
|
|
614
|
+
}
|
|
615
|
+
html += '>';
|
|
616
|
+
if (VOID_ELEMENTS.has(node.tag))
|
|
617
|
+
return html;
|
|
618
|
+
for (let ci = 0; ci < node.children.length; ci++) {
|
|
619
|
+
const child = node.children[ci];
|
|
620
|
+
if (child.type === 'staticText') {
|
|
621
|
+
html += escapeHTML(child.value);
|
|
622
|
+
}
|
|
623
|
+
else if (child.type === 'element') {
|
|
624
|
+
html += buildTemplateHTML(child.node);
|
|
625
|
+
}
|
|
626
|
+
else if (child.type === 'reactiveText') {
|
|
627
|
+
// When the reactive text is not adjacent to another text-type child,
|
|
628
|
+
// we can use a literal text node placeholder instead of a comment.
|
|
629
|
+
// The cloned text node is reused in the patch function — no
|
|
630
|
+
// createTextNode + replaceChild needed. This saves 2 DOM operations
|
|
631
|
+
// per text binding per row.
|
|
632
|
+
//
|
|
633
|
+
// When adjacent text WOULD cause HTML-parser merging (two text nodes
|
|
634
|
+
// collapse into one), we fall back to the comment placeholder.
|
|
635
|
+
const prev = ci > 0 ? node.children[ci - 1] : null;
|
|
636
|
+
const next = ci < node.children.length - 1 ? node.children[ci + 1] : null;
|
|
637
|
+
const adjText = prev?.type === 'staticText' ||
|
|
638
|
+
prev?.type === 'reactiveText' ||
|
|
639
|
+
next?.type === 'staticText' ||
|
|
640
|
+
next?.type === 'reactiveText';
|
|
641
|
+
if (adjText) {
|
|
642
|
+
html += '<!--$-->';
|
|
643
|
+
}
|
|
644
|
+
else {
|
|
645
|
+
// Space character becomes a Text node in the cloned template.
|
|
646
|
+
// Mark the child so the patch codegen knows to skip replaceChild.
|
|
647
|
+
html += ' ';
|
|
648
|
+
child.inlineText = true;
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
html += `</${node.tag}>`;
|
|
653
|
+
return html;
|
|
654
|
+
}
|
|
655
|
+
/**
|
|
656
|
+
* Collect all patch operations from an analyzed subtree.
|
|
657
|
+
*/
|
|
658
|
+
function collectPatchOps(node, f, rootExpr, ops, counter) {
|
|
659
|
+
const hasDynamic = node.events.length > 0 ||
|
|
660
|
+
node.bindings.length > 0 ||
|
|
661
|
+
node.children.some((c) => c.type === 'reactiveText');
|
|
662
|
+
let nodeExpr = rootExpr;
|
|
663
|
+
if (hasDynamic) {
|
|
664
|
+
const varName = `__n${counter.n++}`;
|
|
665
|
+
// Build walk expression: root.childNodes[i].childNodes[j]...
|
|
666
|
+
nodeExpr = f.createIdentifier(varName);
|
|
667
|
+
ops.push({
|
|
668
|
+
varName,
|
|
669
|
+
walkExpr: buildWalkExpr(node.path, f),
|
|
670
|
+
events: node.events,
|
|
671
|
+
bindings: node.bindings,
|
|
672
|
+
reactiveTexts: node.children.filter((c) => c.type === 'reactiveText'),
|
|
673
|
+
});
|
|
674
|
+
}
|
|
675
|
+
// Recurse into element children
|
|
676
|
+
for (const child of node.children) {
|
|
677
|
+
if (child.type === 'element') {
|
|
678
|
+
collectPatchOps(child.node, f, nodeExpr, ops, counter);
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
function buildWalkExpr(path, f) {
|
|
683
|
+
let expr = f.createIdentifier('root');
|
|
684
|
+
for (const idx of path) {
|
|
685
|
+
// Use firstChild + nextSibling chain instead of childNodes[n]
|
|
686
|
+
// firstChild/nextSibling are direct pointer lookups, childNodes is a live NodeList
|
|
687
|
+
expr = f.createPropertyAccessExpression(expr, 'firstChild');
|
|
688
|
+
for (let i = 0; i < idx; i++) {
|
|
689
|
+
expr = f.createPropertyAccessExpression(expr, 'nextSibling');
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
return expr;
|
|
693
|
+
}
|
|
694
|
+
/**
|
|
695
|
+
* Emit elTemplate(htmlString, (root, __bind) => { ... }) call.
|
|
696
|
+
*/
|
|
697
|
+
function emitSubtreeTemplate(analyzed, fieldBits, f) {
|
|
698
|
+
const html = buildTemplateHTML(analyzed);
|
|
699
|
+
const ops = [];
|
|
700
|
+
const counter = { n: 0, t: 0 };
|
|
701
|
+
// Collect root-level patches
|
|
702
|
+
const rootHasDynamic = analyzed.events.length > 0 ||
|
|
703
|
+
analyzed.bindings.length > 0 ||
|
|
704
|
+
analyzed.children.some((c) => c.type === 'reactiveText');
|
|
705
|
+
if (rootHasDynamic) {
|
|
706
|
+
ops.push({
|
|
707
|
+
varName: '', // use 'root' directly
|
|
708
|
+
walkExpr: f.createIdentifier('root'),
|
|
709
|
+
events: analyzed.events,
|
|
710
|
+
bindings: analyzed.bindings,
|
|
711
|
+
reactiveTexts: analyzed.children.filter((c) => c.type === 'reactiveText'),
|
|
712
|
+
});
|
|
713
|
+
}
|
|
714
|
+
// Collect child patches
|
|
715
|
+
for (const child of analyzed.children) {
|
|
716
|
+
if (child.type === 'element') {
|
|
717
|
+
collectPatchOps(child.node, f, f.createIdentifier('root'), ops, counter);
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
// Collect delegatable events: group by event type across all ops
|
|
721
|
+
// Events on child nodes with the same type are delegated to the root
|
|
722
|
+
const delegatableEvents = new Map();
|
|
723
|
+
for (const op of ops) {
|
|
724
|
+
for (const [eventName, handler] of op.events) {
|
|
725
|
+
if (!op.varName) {
|
|
726
|
+
// Root-level events — can't delegate further up
|
|
727
|
+
continue;
|
|
728
|
+
}
|
|
729
|
+
const list = delegatableEvents.get(eventName);
|
|
730
|
+
if (list)
|
|
731
|
+
list.push({ nodeVar: op.varName, handler });
|
|
732
|
+
else
|
|
733
|
+
delegatableEvents.set(eventName, [{ nodeVar: op.varName, handler }]);
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
// Build patch function body
|
|
737
|
+
const stmts = [];
|
|
738
|
+
for (const op of ops) {
|
|
739
|
+
const nodeRef = op.varName ? f.createIdentifier(op.varName) : f.createIdentifier('root');
|
|
740
|
+
// Variable declaration for walking to node
|
|
741
|
+
if (op.varName) {
|
|
742
|
+
stmts.push(f.createVariableStatement(undefined, f.createVariableDeclarationList([f.createVariableDeclaration(op.varName, undefined, undefined, op.walkExpr)], ts.NodeFlags.Const)));
|
|
743
|
+
}
|
|
744
|
+
// Non-delegatable events (root-level or single-use event types)
|
|
745
|
+
for (const [eventName, handler] of op.events) {
|
|
746
|
+
const delegated = delegatableEvents.get(eventName);
|
|
747
|
+
if (op.varName && delegated && delegated.length >= 2)
|
|
748
|
+
continue; // handled below
|
|
749
|
+
stmts.push(f.createExpressionStatement(f.createCallExpression(f.createPropertyAccessExpression(nodeRef, 'addEventListener'), undefined, [f.createStringLiteral(eventName), handler])));
|
|
750
|
+
}
|
|
751
|
+
// Reactive text children — walk to placeholder, create text node, bind
|
|
752
|
+
for (const rt of op.reactiveTexts) {
|
|
753
|
+
const tVar = `__t${counter.t++}`;
|
|
754
|
+
const isInline = !!rt.inlineText;
|
|
755
|
+
if (isInline) {
|
|
756
|
+
// Inline text placeholder: the template HTML has a space character
|
|
757
|
+
// that cloneNode already created as a Text node. Walk to it and
|
|
758
|
+
// bind directly — no createTextNode, no replaceChild.
|
|
759
|
+
let walk = f.createPropertyAccessExpression(nodeRef, 'firstChild');
|
|
760
|
+
for (let i = 0; i < rt.childIdx; i++) {
|
|
761
|
+
walk = f.createPropertyAccessExpression(walk, 'nextSibling');
|
|
762
|
+
}
|
|
763
|
+
stmts.push(f.createVariableStatement(undefined, f.createVariableDeclarationList([f.createVariableDeclaration(tVar, undefined, undefined, walk)], ts.NodeFlags.Const)));
|
|
764
|
+
}
|
|
765
|
+
else {
|
|
766
|
+
// Comment placeholder: create a new text node and replace the comment.
|
|
767
|
+
const cVar = `__c${counter.t - 1}`;
|
|
768
|
+
let walk = f.createPropertyAccessExpression(nodeRef, 'firstChild');
|
|
769
|
+
for (let i = 0; i < rt.childIdx; i++) {
|
|
770
|
+
walk = f.createPropertyAccessExpression(walk, 'nextSibling');
|
|
771
|
+
}
|
|
772
|
+
stmts.push(f.createVariableStatement(undefined, f.createVariableDeclarationList([f.createVariableDeclaration(cVar, undefined, undefined, walk)], ts.NodeFlags.Const)));
|
|
773
|
+
stmts.push(f.createVariableStatement(undefined, f.createVariableDeclarationList([
|
|
774
|
+
f.createVariableDeclaration(tVar, undefined, undefined, f.createCallExpression(f.createPropertyAccessExpression(f.createIdentifier('__dom'), 'createTextNode'), undefined, [f.createStringLiteral('')])),
|
|
775
|
+
], ts.NodeFlags.Const)));
|
|
776
|
+
stmts.push(f.createExpressionStatement(f.createCallExpression(f.createPropertyAccessExpression(f.createPropertyAccessExpression(f.createIdentifier(cVar), 'parentNode'), 'replaceChild'), undefined, [f.createIdentifier(tVar), f.createIdentifier(cVar)])));
|
|
777
|
+
}
|
|
778
|
+
// __bind(__t0, mask, 'text', undefined, accessor, [maskHi])
|
|
779
|
+
const rtArgs = [
|
|
780
|
+
f.createIdentifier(tVar),
|
|
781
|
+
createMaskLiteral(f, rt.mask),
|
|
782
|
+
f.createStringLiteral('text'),
|
|
783
|
+
f.createIdentifier('undefined'),
|
|
784
|
+
rt.accessor,
|
|
785
|
+
];
|
|
786
|
+
// Only pass the 6th positional arg when the accessor reads a
|
|
787
|
+
// high-word prefix. Keeps the emit byte-identical to the
|
|
788
|
+
// pre-multi-word baseline for the common case.
|
|
789
|
+
if (rt.maskHi !== 0)
|
|
790
|
+
rtArgs.push(createMaskLiteral(f, rt.maskHi));
|
|
791
|
+
stmts.push(f.createExpressionStatement(f.createCallExpression(f.createIdentifier('__bind'), undefined, rtArgs)));
|
|
792
|
+
}
|
|
793
|
+
// Reactive bindings — __bind(node, mask, kind, key, accessor, [maskHi])
|
|
794
|
+
for (const [mask, maskHi, kind, key, accessor] of op.bindings) {
|
|
795
|
+
const args = [
|
|
796
|
+
nodeRef,
|
|
797
|
+
createMaskLiteral(f, mask),
|
|
798
|
+
f.createStringLiteral(kind),
|
|
799
|
+
key ? f.createStringLiteral(key) : f.createIdentifier('undefined'),
|
|
800
|
+
accessor,
|
|
801
|
+
];
|
|
802
|
+
if (maskHi !== 0)
|
|
803
|
+
args.push(createMaskLiteral(f, maskHi));
|
|
804
|
+
stmts.push(f.createExpressionStatement(f.createCallExpression(f.createIdentifier('__bind'), undefined, args)));
|
|
805
|
+
}
|
|
806
|
+
}
|
|
807
|
+
// Emit delegated event listeners on root
|
|
808
|
+
for (const [eventName, entries] of delegatableEvents) {
|
|
809
|
+
if (entries.length < 2)
|
|
810
|
+
continue;
|
|
811
|
+
// root.onclick = (e) => { if (n1.contains(e.target)) { h1(); return } if (n2.contains(e.target)) { h2(); return } }
|
|
812
|
+
const eParam = f.createIdentifier('__e');
|
|
813
|
+
const eTarget = f.createPropertyAccessExpression(eParam, 'target');
|
|
814
|
+
const ifStmts = [];
|
|
815
|
+
for (const { nodeVar, handler } of entries) {
|
|
816
|
+
// if (nodeVar.contains(e.target)) { handler(e); return }
|
|
817
|
+
ifStmts.push(f.createIfStatement(f.createCallExpression(f.createPropertyAccessExpression(f.createIdentifier(nodeVar), 'contains'), undefined, [eTarget]), f.createBlock([
|
|
818
|
+
f.createExpressionStatement(f.createCallExpression(handler, undefined, [eParam])),
|
|
819
|
+
f.createReturnStatement(),
|
|
820
|
+
], true)));
|
|
821
|
+
}
|
|
822
|
+
const delegateHandler = f.createArrowFunction(undefined, undefined, [f.createParameterDeclaration(undefined, undefined, '__e')], undefined, f.createToken(ts.SyntaxKind.EqualsGreaterThanToken), f.createBlock(ifStmts, true));
|
|
823
|
+
// root.addEventListener(eventName, handler)
|
|
824
|
+
stmts.push(f.createExpressionStatement(f.createCallExpression(f.createPropertyAccessExpression(f.createIdentifier('root'), 'addEventListener'), undefined, [f.createStringLiteral(eventName), delegateHandler])));
|
|
825
|
+
}
|
|
826
|
+
// (root, __bind, __dom) => { ... }
|
|
827
|
+
const patchFn = f.createArrowFunction(undefined, undefined, [
|
|
828
|
+
f.createParameterDeclaration(undefined, undefined, 'root'),
|
|
829
|
+
f.createParameterDeclaration(undefined, undefined, '__bind'),
|
|
830
|
+
f.createParameterDeclaration(undefined, undefined, '__dom'),
|
|
831
|
+
], undefined, f.createToken(ts.SyntaxKind.EqualsGreaterThanToken), f.createBlock(stmts, true));
|
|
832
|
+
const call = f.createCallExpression(f.createIdentifier('elTemplate'), undefined, [
|
|
833
|
+
f.createStringLiteral(html),
|
|
834
|
+
patchFn,
|
|
835
|
+
]);
|
|
836
|
+
return call;
|
|
837
|
+
}
|
|
838
|
+
// ── Static subtree detection ─────────────────────────────────────
|
|
839
|
+
function isStaticChildren(children) {
|
|
840
|
+
if (children.kind === ts.SyntaxKind.NullKeyword)
|
|
841
|
+
return true;
|
|
842
|
+
if (!ts.isArrayLiteralExpression(children))
|
|
843
|
+
return false;
|
|
844
|
+
return children.elements.every((child) => {
|
|
845
|
+
// text('literal') — static text
|
|
846
|
+
if (ts.isCallExpression(child) &&
|
|
847
|
+
ts.isIdentifier(child.expression) &&
|
|
848
|
+
child.expression.text === 'text') {
|
|
849
|
+
return child.arguments.length === 1 && ts.isStringLiteral(child.arguments[0]);
|
|
850
|
+
}
|
|
851
|
+
// Another elSplit or element helper that was already determined static
|
|
852
|
+
// For now, only handle text() children
|
|
853
|
+
return false;
|
|
854
|
+
});
|
|
855
|
+
}
|
|
856
|
+
function buildStaticHTML(tag, staticProps, children, _f) {
|
|
857
|
+
// Extract static attributes from staticFn statements
|
|
858
|
+
let attrs = '';
|
|
859
|
+
for (const stmt of staticProps) {
|
|
860
|
+
if (!ts.isExpressionStatement(stmt))
|
|
861
|
+
return null;
|
|
862
|
+
const expr = stmt.expression;
|
|
863
|
+
// __e.className = 'value'
|
|
864
|
+
if (ts.isBinaryExpression(expr) && ts.isPropertyAccessExpression(expr.left)) {
|
|
865
|
+
const prop = expr.left.name.text;
|
|
866
|
+
if (prop === 'className' && ts.isStringLiteral(expr.right)) {
|
|
867
|
+
attrs += ` class="${escapeAttr(expr.right.text)}"`;
|
|
868
|
+
}
|
|
869
|
+
}
|
|
870
|
+
// __e.setAttribute('key', 'value')
|
|
871
|
+
if (ts.isCallExpression(expr) && ts.isPropertyAccessExpression(expr.expression)) {
|
|
872
|
+
if (expr.expression.name.text === 'setAttribute' && expr.arguments.length === 2) {
|
|
873
|
+
const key = expr.arguments[0];
|
|
874
|
+
const val = expr.arguments[1];
|
|
875
|
+
if (key && val && ts.isStringLiteral(key) && ts.isStringLiteral(val)) {
|
|
876
|
+
attrs += ` ${key.text}="${escapeAttr(val.text)}"`;
|
|
877
|
+
}
|
|
878
|
+
else {
|
|
879
|
+
return null; // non-literal attribute
|
|
880
|
+
}
|
|
881
|
+
}
|
|
882
|
+
}
|
|
883
|
+
}
|
|
884
|
+
// Extract text children
|
|
885
|
+
let inner = '';
|
|
886
|
+
if (ts.isArrayLiteralExpression(children)) {
|
|
887
|
+
for (const child of children.elements) {
|
|
888
|
+
if (ts.isCallExpression(child) &&
|
|
889
|
+
ts.isIdentifier(child.expression) &&
|
|
890
|
+
child.expression.text === 'text') {
|
|
891
|
+
if (ts.isStringLiteral(child.arguments[0])) {
|
|
892
|
+
inner += escapeHTML(child.arguments[0].text);
|
|
893
|
+
}
|
|
894
|
+
else {
|
|
895
|
+
return null;
|
|
896
|
+
}
|
|
897
|
+
}
|
|
898
|
+
else {
|
|
899
|
+
return null;
|
|
900
|
+
}
|
|
901
|
+
}
|
|
902
|
+
}
|
|
903
|
+
return `<${tag}${attrs}>${inner}</${tag}>`;
|
|
904
|
+
}
|
|
905
|
+
function escapeHTML(s) {
|
|
906
|
+
return s.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
|
907
|
+
}
|
|
908
|
+
function escapeAttr(s) {
|
|
909
|
+
return s.replace(/&/g, '&').replace(/"/g, '"');
|
|
910
|
+
}
|
|
911
|
+
function emitTemplateClone(html, f) {
|
|
912
|
+
// Emits: __cloneStaticTemplate("<html>")
|
|
913
|
+
//
|
|
914
|
+
// The helper lives in `@llui/dom` and threads through `ctx.dom` so SSR
|
|
915
|
+
// under jsdom/linkedom works without touching globalThis. The import
|
|
916
|
+
// cleanup pass (see cleanupImports) auto-injects the import when this
|
|
917
|
+
// emission fires.
|
|
918
|
+
return f.createCallExpression(f.createIdentifier('__cloneStaticTemplate'), undefined, [
|
|
919
|
+
f.createStringLiteral(html),
|
|
920
|
+
]);
|
|
921
|
+
}
|
|
922
|
+
function isPerItemCall(node) {
|
|
923
|
+
// Matches: item(t => t.field) or item(t => expr)
|
|
924
|
+
// where item is an identifier (the scoped accessor from each() render)
|
|
925
|
+
if (!ts.isIdentifier(node.expression))
|
|
926
|
+
return false;
|
|
927
|
+
// Check that the first argument is an arrow function (the selector)
|
|
928
|
+
if (node.arguments.length !== 1)
|
|
929
|
+
return false;
|
|
930
|
+
const arg = node.arguments[0];
|
|
931
|
+
return ts.isArrowFunction(arg) || ts.isFunctionExpression(arg);
|
|
932
|
+
}
|
|
933
|
+
// Matches: item.FIELD — the item-proxy shorthand equivalent of item(t => t.FIELD).
|
|
934
|
+
// Lifetime-checked: the `item` identifier must resolve to a parameter of an
|
|
935
|
+
// `each({ render })` callback. Without this check, plain
|
|
936
|
+
// `arr.map((item) => item.field)` outside each() would be rewritten as a
|
|
937
|
+
// per-item binding and crash at runtime with "accessor is not a function"
|
|
938
|
+
// because `item.field` evaluates to a bare value (not a function) when
|
|
939
|
+
// treated as an accessor.
|
|
940
|
+
function isPerItemFieldAccess(node) {
|
|
941
|
+
if (!ts.isPropertyAccessExpression(node))
|
|
942
|
+
return false;
|
|
943
|
+
if (!ts.isIdentifier(node.expression))
|
|
944
|
+
return false;
|
|
945
|
+
if (node.expression.text !== 'item')
|
|
946
|
+
return false;
|
|
947
|
+
if (!ts.isIdentifier(node.name))
|
|
948
|
+
return false;
|
|
949
|
+
return isItemBoundToEachRender(node);
|
|
950
|
+
}
|
|
951
|
+
/**
|
|
952
|
+
* Walks up from a node and returns true iff the nearest enclosing function
|
|
953
|
+
* that binds an `item` parameter is the `render` property of an `each()`
|
|
954
|
+
* call. Handles both positional (`(item) => …`) and destructured
|
|
955
|
+
* (`({ item, index }) => …`) parameter bindings.
|
|
956
|
+
*/
|
|
957
|
+
function isItemBoundToEachRender(node) {
|
|
958
|
+
let current = node.parent;
|
|
959
|
+
while (current) {
|
|
960
|
+
if (ts.isArrowFunction(current) || ts.isFunctionExpression(current)) {
|
|
961
|
+
if (functionParamsBindItem(current)) {
|
|
962
|
+
return isEachRenderCallback(current);
|
|
963
|
+
}
|
|
964
|
+
}
|
|
965
|
+
current = current.parent;
|
|
966
|
+
}
|
|
967
|
+
return false;
|
|
968
|
+
}
|
|
969
|
+
function functionParamsBindItem(fn) {
|
|
970
|
+
for (const param of fn.parameters) {
|
|
971
|
+
if (bindingNameBindsItem(param.name))
|
|
972
|
+
return true;
|
|
973
|
+
}
|
|
974
|
+
return false;
|
|
975
|
+
}
|
|
976
|
+
function bindingNameBindsItem(name) {
|
|
977
|
+
if (ts.isIdentifier(name))
|
|
978
|
+
return name.text === 'item';
|
|
979
|
+
if (ts.isObjectBindingPattern(name) || ts.isArrayBindingPattern(name)) {
|
|
980
|
+
for (const el of name.elements) {
|
|
981
|
+
if (ts.isBindingElement(el) && bindingNameBindsItem(el.name))
|
|
982
|
+
return true;
|
|
983
|
+
}
|
|
984
|
+
}
|
|
985
|
+
return false;
|
|
986
|
+
}
|
|
987
|
+
function isEachRenderCallback(fn) {
|
|
988
|
+
const parent = fn.parent;
|
|
989
|
+
if (!parent || !ts.isPropertyAssignment(parent))
|
|
990
|
+
return false;
|
|
991
|
+
if (!ts.isIdentifier(parent.name) || parent.name.text !== 'render')
|
|
992
|
+
return false;
|
|
993
|
+
const objLit = parent.parent;
|
|
994
|
+
if (!objLit || !ts.isObjectLiteralExpression(objLit))
|
|
995
|
+
return false;
|
|
996
|
+
const call = objLit.parent;
|
|
997
|
+
if (!call || !ts.isCallExpression(call))
|
|
998
|
+
return false;
|
|
999
|
+
if (!ts.isIdentifier(call.expression) || call.expression.text !== 'each')
|
|
1000
|
+
return false;
|
|
1001
|
+
return true;
|
|
1002
|
+
}
|
|
1003
|
+
// Matches the hoisted identifiers produced by tryDeduplicateItemSelectors: __a0, __a1, …
|
|
1004
|
+
// These represent already-cached per-item accessors.
|
|
1005
|
+
function isHoistedPerItem(node) {
|
|
1006
|
+
if (!ts.isIdentifier(node))
|
|
1007
|
+
return false;
|
|
1008
|
+
return /^__a\d+$/.test(node.text);
|
|
1009
|
+
}
|
|
1010
|
+
// ── Mask computation ─────────────────────────────────────────────
|
|
1011
|
+
// Returns { mask, readsState }
|
|
1012
|
+
// mask = 0 + readsState = false → constant (can fold to static)
|
|
1013
|
+
// mask = 0 + readsState = true → unresolvable state access (FULL_MASK)
|
|
1014
|
+
// mask > 0 → precise mask
|
|
1015
|
+
// See `NON_DELEGATION_HELPERS` in collect-deps.ts — same set of names
|
|
1016
|
+
// that aren't followed when scanning for `helper(s)` delegation calls.
|
|
1017
|
+
//# sourceMappingURL=element-rewrite.js.map
|