@llui/vite-plugin 0.0.40 → 0.0.41
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/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/collect-deps.d.ts +15 -8
- package/dist/collect-deps.d.ts.map +1 -1
- package/dist/collect-deps.js +65 -32
- package/dist/collect-deps.js.map +1 -1
- package/dist/transform.d.ts.map +1 -1
- package/dist/transform.js +142 -105
- package/dist/transform.js.map +1 -1
- package/package.json +1 -1
package/dist/transform.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import ts from 'typescript';
|
|
2
2
|
import { collectDeps } from './collect-deps.js';
|
|
3
|
+
import { resolveLocalConstInitializer, resolveAccessorBody, isMemoCallWithArrowArg, } from './accessor-resolver.js';
|
|
3
4
|
import { extractMsgSchema, extractEffectSchema, isRichField, } from './msg-schema.js';
|
|
4
5
|
import { extractMsgAnnotations } from './msg-annotations.js';
|
|
5
6
|
import { extractStateSchema } from './state-schema.js';
|
|
@@ -94,66 +95,68 @@ const PROP_KEYS = new Set([
|
|
|
94
95
|
'innerHTML',
|
|
95
96
|
'textContent',
|
|
96
97
|
]);
|
|
98
|
+
function isStaticPrimitiveLiteral(expr) {
|
|
99
|
+
return (ts.isStringLiteral(expr) ||
|
|
100
|
+
ts.isNumericLiteral(expr) ||
|
|
101
|
+
ts.isNoSubstitutionTemplateLiteral(expr) ||
|
|
102
|
+
expr.kind === ts.SyntaxKind.TrueKeyword ||
|
|
103
|
+
expr.kind === ts.SyntaxKind.FalseKeyword ||
|
|
104
|
+
expr.kind === ts.SyntaxKind.NullKeyword);
|
|
105
|
+
}
|
|
97
106
|
/**
|
|
98
|
-
*
|
|
99
|
-
* `
|
|
100
|
-
*
|
|
101
|
-
*
|
|
102
|
-
*
|
|
103
|
-
*
|
|
104
|
-
* const cls = (s: State) => isActive(item, s.path) ? 'on' : 'off'
|
|
105
|
-
* return a({ class: cls }, ...)
|
|
106
|
-
*
|
|
107
|
-
* Without resolution, `class: cls` would fall through the static path
|
|
108
|
-
* and emit `__e.className = cls`, coercing the function to its source
|
|
109
|
-
* string. With resolution, we see that `cls` is an arrow and emit a
|
|
110
|
-
* binding exactly as if the arrow had been inlined.
|
|
111
|
-
*
|
|
112
|
-
* Lifetime rules:
|
|
113
|
-
* - Only single-binding `const`/`let`/`var` declarations with an
|
|
114
|
-
* initializer are considered. No destructuring, no multi-declarator
|
|
115
|
-
* statements (too easy to get wrong without a type checker).
|
|
116
|
-
* - Later reassignments are NOT tracked — if the identifier is `let`
|
|
117
|
-
* and gets reassigned, the resolution is unreliable. We conservatively
|
|
118
|
-
* refuse to resolve `let` bindings for now (arrow-valued accessors
|
|
119
|
-
* are ~always `const` in practice).
|
|
120
|
-
* - The declaration must dominate the use (same block or an enclosing
|
|
121
|
-
* one). TypeScript's block semantics mean walking up parent blocks
|
|
122
|
-
* is sufficient.
|
|
107
|
+
* Classify a reactive-prop value. See `ResolvedReactiveValue` for the
|
|
108
|
+
* contract. Returns `null` only when the value is none of the recognized
|
|
109
|
+
* shapes (caller can fall back to its own branches — currently only
|
|
110
|
+
* `tryTransformElementCall` does this for `isPerItemFieldAccess` /
|
|
111
|
+
* `isHoistedPerItem`).
|
|
123
112
|
*/
|
|
124
|
-
function
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
113
|
+
function classifyReactiveValue(value) {
|
|
114
|
+
// Inline arrow / function expression at the call site
|
|
115
|
+
if (ts.isArrowFunction(value) || ts.isFunctionExpression(value)) {
|
|
116
|
+
return { kind: 'arrow', accessor: value, valueForBinding: value };
|
|
117
|
+
}
|
|
118
|
+
// Inline `memo(arrow)` at the call site
|
|
119
|
+
if (isMemoCallWithArrowArg(value)) {
|
|
120
|
+
return {
|
|
121
|
+
kind: 'memo-call',
|
|
122
|
+
accessor: value.arguments[0],
|
|
123
|
+
valueForBinding: value,
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
// Identifier — resolve and classify the resolved declaration
|
|
127
|
+
if (ts.isIdentifier(value)) {
|
|
128
|
+
const resolved = resolveLocalConstInitializer(value);
|
|
129
|
+
if (!resolved) {
|
|
130
|
+
// Imported / parameter / unbound — can't prove it's a primitive,
|
|
131
|
+
// can't prove it's a function. Caller must bail to runtime.
|
|
132
|
+
return { kind: 'bail' };
|
|
133
133
|
}
|
|
134
|
-
|
|
135
|
-
|
|
134
|
+
if (ts.isArrowFunction(resolved) || ts.isFunctionExpression(resolved)) {
|
|
135
|
+
return { kind: 'arrow', accessor: resolved, valueForBinding: value };
|
|
136
136
|
}
|
|
137
|
-
if (
|
|
138
|
-
|
|
139
|
-
if (!ts.isVariableStatement(stmt))
|
|
140
|
-
continue;
|
|
141
|
-
// Skip `let` — reassignment would invalidate our resolution
|
|
142
|
-
const flags = stmt.declarationList.flags;
|
|
143
|
-
if (!(flags & ts.NodeFlags.Const))
|
|
144
|
-
continue;
|
|
145
|
-
if (stmt.declarationList.declarations.length !== 1)
|
|
146
|
-
continue;
|
|
147
|
-
const decl = stmt.declarationList.declarations[0];
|
|
148
|
-
if (!ts.isIdentifier(decl.name) || decl.name.text !== name)
|
|
149
|
-
continue;
|
|
150
|
-
if (!decl.initializer)
|
|
151
|
-
continue;
|
|
152
|
-
return decl.initializer;
|
|
153
|
-
}
|
|
137
|
+
if (ts.isFunctionDeclaration(resolved)) {
|
|
138
|
+
return { kind: 'fn-decl', accessor: resolved, valueForBinding: value };
|
|
154
139
|
}
|
|
155
|
-
|
|
140
|
+
if (isMemoCallWithArrowArg(resolved)) {
|
|
141
|
+
return {
|
|
142
|
+
kind: 'memo-call',
|
|
143
|
+
accessor: resolved.arguments[0],
|
|
144
|
+
valueForBinding: value,
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
if (isStaticPrimitiveLiteral(resolved)) {
|
|
148
|
+
return { kind: 'static-literal' };
|
|
149
|
+
}
|
|
150
|
+
// Resolved to something else (object/array/expression) — conservative
|
|
151
|
+
// bail. We don't know if the runtime value is a function; the runtime
|
|
152
|
+
// element helper handles both cases correctly.
|
|
153
|
+
return { kind: 'bail' };
|
|
156
154
|
}
|
|
155
|
+
// Static literals at the call site
|
|
156
|
+
if (isStaticPrimitiveLiteral(value)) {
|
|
157
|
+
return { kind: 'static-literal' };
|
|
158
|
+
}
|
|
159
|
+
// CallExpression — caller decides (per-item, etc.)
|
|
157
160
|
return null;
|
|
158
161
|
}
|
|
159
162
|
function classifyKind(key) {
|
|
@@ -821,70 +824,92 @@ function tryTransformElementCall(node, helpers, fieldBits, compiled, bailed, f)
|
|
|
821
824
|
events.push(f.createArrayLiteralExpression([f.createStringLiteral(eventName), value]));
|
|
822
825
|
continue;
|
|
823
826
|
}
|
|
824
|
-
//
|
|
825
|
-
//
|
|
826
|
-
//
|
|
827
|
-
//
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
const
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
827
|
+
// Per-item shapes — handled before the general classifier because
|
|
828
|
+
// they appear inside `each().render` callbacks where `item` is a
|
|
829
|
+
// closed-over per-row accessor (zero-arg). The resolver above can't
|
|
830
|
+
// see them; they're shape-matched syntactically.
|
|
831
|
+
if (isPerItemFieldAccess(value) || isHoistedPerItem(value)) {
|
|
832
|
+
const kind = classifyKind(key);
|
|
833
|
+
const resolvedKey = resolveKey(key, kind);
|
|
834
|
+
bindings.push(f.createArrayLiteralExpression([
|
|
835
|
+
createMaskLiteral(f, 0xffffffff | 0),
|
|
836
|
+
f.createStringLiteral(kind),
|
|
837
|
+
f.createStringLiteral(resolvedKey),
|
|
838
|
+
value,
|
|
839
|
+
]));
|
|
840
|
+
continue;
|
|
834
841
|
}
|
|
835
|
-
|
|
836
|
-
if (ts.isArrowFunction(value) || ts.isFunctionExpression(value)) {
|
|
842
|
+
if (ts.isCallExpression(value) && isPerItemCall(value)) {
|
|
837
843
|
const kind = classifyKind(key);
|
|
838
844
|
const resolvedKey = resolveKey(key, kind);
|
|
839
|
-
const { mask, readsState } = computeAccessorMask(value, fieldBits);
|
|
840
|
-
// Zero-mask constant folding: accessor doesn't read state → treat as static
|
|
841
|
-
if (mask === 0 && !readsState) {
|
|
842
|
-
emitStaticProp(staticProps, f, kind, resolvedKey, f.createCallExpression(value, undefined, []));
|
|
843
|
-
continue;
|
|
844
|
-
}
|
|
845
845
|
bindings.push(f.createArrayLiteralExpression([
|
|
846
|
-
createMaskLiteral(f,
|
|
846
|
+
createMaskLiteral(f, 0xffffffff | 0),
|
|
847
847
|
f.createStringLiteral(kind),
|
|
848
848
|
f.createStringLiteral(resolvedKey),
|
|
849
849
|
value,
|
|
850
850
|
]));
|
|
851
851
|
continue;
|
|
852
852
|
}
|
|
853
|
-
//
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
// Unknown call expression — bail out
|
|
853
|
+
// Classify the value at a reactive-prop position:
|
|
854
|
+
// - inline arrow / fn-expr at the call site
|
|
855
|
+
// - inline `memo(arrow)` at the call site
|
|
856
|
+
// - Identifier referencing a const-bound arrow/fn-expr in scope
|
|
857
|
+
// - Identifier referencing a hoisted function declaration in scope
|
|
858
|
+
// - Identifier referencing `const x = memo(arrow)` in scope
|
|
859
|
+
// - Identifier referencing a static primitive literal
|
|
860
|
+
// - Anything else (imports, parameters, opaque expressions) — bail
|
|
861
|
+
// to runtime; the runtime helper handles `typeof v === 'function'`
|
|
862
|
+
// correctly for both function and primitive values.
|
|
863
|
+
const classified = classifyReactiveValue(value);
|
|
864
|
+
if (classified === null) {
|
|
865
|
+
// Unknown shape (a CallExpression that isn't memo/per-item, etc.)
|
|
866
|
+
// — historically bailed to runtime. Preserve that.
|
|
868
867
|
bailed.add(localName);
|
|
869
868
|
return null;
|
|
870
869
|
}
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
870
|
+
if (classified.kind === 'bail') {
|
|
871
|
+
bailed.add(localName);
|
|
872
|
+
return null;
|
|
873
|
+
}
|
|
874
|
+
if (classified.kind === 'static-literal') {
|
|
875
|
+
// Fall through to emitStaticProp (`__e.disabled = X`). Safe because
|
|
876
|
+
// we proved X is a primitive.
|
|
877
|
+
const kind = classifyKind(key);
|
|
878
|
+
const resolvedKey = resolveKey(key, kind);
|
|
879
|
+
emitStaticProp(staticProps, f, kind, resolvedKey, value);
|
|
880
|
+
continue;
|
|
881
|
+
}
|
|
882
|
+
// 'arrow' | 'fn-decl' | 'memo-call' — emit as a binding tuple. Mask is
|
|
883
|
+
// analyzed from the resolved accessor body (or the inner arrow inside
|
|
884
|
+
// a memo() call); the value emitted into the binding tuple is what the
|
|
885
|
+
// runtime calls as `accessor(state)` — for inline arrows we keep the
|
|
886
|
+
// arrow itself (preserves the historical inlining behavior), for
|
|
887
|
+
// identifier-bound forms we keep the identifier so consumers see
|
|
888
|
+
// a single canonical reference (and `memo()` proxies aren't rebuilt
|
|
889
|
+
// per render).
|
|
890
|
+
{
|
|
874
891
|
const kind = classifyKind(key);
|
|
875
892
|
const resolvedKey = resolveKey(key, kind);
|
|
893
|
+
const { mask, readsState } = computeAccessorMask(classified.accessor, fieldBits);
|
|
894
|
+
// Zero-mask constant folding only applies to inline arrows whose body
|
|
895
|
+
// we can safely call at compile time. For identifier-bound forms
|
|
896
|
+
// (`accessor !== value`) we skip the fold — calling the identifier's
|
|
897
|
+
// declaration at compile time would be unsafe (different scope) and
|
|
898
|
+
// calling the identifier in the emitted output would defeat the point.
|
|
899
|
+
if (classified.kind === 'arrow' &&
|
|
900
|
+
classified.accessor === value &&
|
|
901
|
+
mask === 0 &&
|
|
902
|
+
!readsState) {
|
|
903
|
+
emitStaticProp(staticProps, f, kind, resolvedKey, f.createCallExpression(classified.accessor, undefined, []));
|
|
904
|
+
continue;
|
|
905
|
+
}
|
|
876
906
|
bindings.push(f.createArrayLiteralExpression([
|
|
877
|
-
createMaskLiteral(f, 0xffffffff | 0),
|
|
907
|
+
createMaskLiteral(f, mask === 0 && readsState ? 0xffffffff | 0 : mask),
|
|
878
908
|
f.createStringLiteral(kind),
|
|
879
909
|
f.createStringLiteral(resolvedKey),
|
|
880
|
-
|
|
910
|
+
classified.valueForBinding,
|
|
881
911
|
]));
|
|
882
|
-
continue;
|
|
883
912
|
}
|
|
884
|
-
// Static prop
|
|
885
|
-
const kind = classifyKind(key);
|
|
886
|
-
const resolvedKey = resolveKey(key, kind);
|
|
887
|
-
emitStaticProp(staticProps, f, kind, resolvedKey, value);
|
|
888
913
|
}
|
|
889
914
|
}
|
|
890
915
|
// Build elSplit args
|
|
@@ -971,13 +996,18 @@ function tryInjectTextMask(node, lluiImport, viewHelperNames, viewHelperAliases,
|
|
|
971
996
|
const firstArg = node.arguments[0];
|
|
972
997
|
if (!firstArg)
|
|
973
998
|
return null;
|
|
974
|
-
// Only inject mask for accessor functions, not static strings
|
|
975
|
-
if (!ts.isArrowFunction(firstArg) && !ts.isFunctionExpression(firstArg))
|
|
976
|
-
return null;
|
|
977
999
|
// Don't inject if mask already provided
|
|
978
1000
|
if (node.arguments.length >= 2)
|
|
979
1001
|
return null;
|
|
980
|
-
|
|
1002
|
+
// Resolve the accessor body — accepts inline arrows, `memo(arrow)`, or
|
|
1003
|
+
// identifier references to a const-bound arrow / `memo(...)` / function
|
|
1004
|
+
// declaration in scope. Anything else (static strings, opaque imports,
|
|
1005
|
+
// parameters) leaves the call as-is — the runtime falls back to
|
|
1006
|
+
// FULL_MASK, which is correct but slower.
|
|
1007
|
+
const accessor = resolveAccessorBody(firstArg);
|
|
1008
|
+
if (!accessor)
|
|
1009
|
+
return null;
|
|
1010
|
+
const { mask } = computeAccessorMask(accessor, fieldBits);
|
|
981
1011
|
return f.createCallExpression(node.expression, node.typeArguments, [
|
|
982
1012
|
firstArg,
|
|
983
1013
|
createMaskLiteral(f, mask === 0 ? 0xffffffff | 0 : mask),
|
|
@@ -1020,9 +1050,11 @@ function tryInjectStructuralMask(node, viewHelperNames, viewHelperAliases, field
|
|
|
1020
1050
|
if (ts.isPropertyAssignment(prop) &&
|
|
1021
1051
|
ts.isIdentifier(prop.name) &&
|
|
1022
1052
|
prop.name.text === driverProp) {
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1053
|
+
// Same shape contract as `text()`'s first arg: inline arrow, inline
|
|
1054
|
+
// `memo(arrow)`, or identifier referencing a const-bound arrow /
|
|
1055
|
+
// memo / function declaration. Anything else leaves the call
|
|
1056
|
+
// unchanged — runtime falls back to FULL_MASK.
|
|
1057
|
+
driverAccessor = resolveAccessorBody(prop.initializer);
|
|
1026
1058
|
break;
|
|
1027
1059
|
}
|
|
1028
1060
|
}
|
|
@@ -3398,6 +3430,11 @@ function computeAccessorMask(accessor, fieldBits) {
|
|
|
3398
3430
|
const paramName = accessor.parameters[0].name;
|
|
3399
3431
|
if (!ts.isIdentifier(paramName))
|
|
3400
3432
|
return { mask: 0xffffffff | 0, readsState: false };
|
|
3433
|
+
// FunctionDeclaration always has a body (we never resolve overloads here);
|
|
3434
|
+
// ArrowFunction's body may be a single expression. Both shapes are walked
|
|
3435
|
+
// identically by ts.forEachChild, so no special-casing is needed below.
|
|
3436
|
+
if (!accessor.body)
|
|
3437
|
+
return { mask: 0xffffffff | 0, readsState: false };
|
|
3401
3438
|
const stateParam = paramName.text;
|
|
3402
3439
|
let mask = 0;
|
|
3403
3440
|
let readsState = false;
|