@llui/vite-plugin 0.0.11 → 0.0.14
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/collect-deps.js.map +1 -1
- package/dist/diagnostics.js +24 -5
- package/dist/diagnostics.js.map +1 -1
- package/dist/index.d.ts +4 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +125 -1
- package/dist/index.js.map +1 -1
- package/dist/msg-schema.js.map +1 -1
- package/dist/state-schema.js.map +1 -1
- package/dist/transform.d.ts.map +1 -1
- package/dist/transform.js +153 -4
- package/dist/transform.js.map +1 -1
- package/package.json +9 -9
package/dist/transform.js
CHANGED
|
@@ -90,6 +90,68 @@ const PROP_KEYS = new Set([
|
|
|
90
90
|
'innerHTML',
|
|
91
91
|
'textContent',
|
|
92
92
|
]);
|
|
93
|
+
/**
|
|
94
|
+
* Walk from `use` outward toward the source file root, looking for a
|
|
95
|
+
* `const <name> = <initializer>` (or `let`/`var`) declaration that binds
|
|
96
|
+
* this identifier in an enclosing scope. Returns the initializer if found.
|
|
97
|
+
*
|
|
98
|
+
* This enables reactive-binding detection for hoisted accessors:
|
|
99
|
+
*
|
|
100
|
+
* const cls = (s: State) => isActive(item, s.path) ? 'on' : 'off'
|
|
101
|
+
* return a({ class: cls }, ...)
|
|
102
|
+
*
|
|
103
|
+
* Without resolution, `class: cls` would fall through the static path
|
|
104
|
+
* and emit `__e.className = cls`, coercing the function to its source
|
|
105
|
+
* string. With resolution, we see that `cls` is an arrow and emit a
|
|
106
|
+
* binding exactly as if the arrow had been inlined.
|
|
107
|
+
*
|
|
108
|
+
* Scope rules:
|
|
109
|
+
* - Only single-binding `const`/`let`/`var` declarations with an
|
|
110
|
+
* initializer are considered. No destructuring, no multi-declarator
|
|
111
|
+
* statements (too easy to get wrong without a type checker).
|
|
112
|
+
* - Later reassignments are NOT tracked — if the identifier is `let`
|
|
113
|
+
* and gets reassigned, the resolution is unreliable. We conservatively
|
|
114
|
+
* refuse to resolve `let` bindings for now (arrow-valued accessors
|
|
115
|
+
* are ~always `const` in practice).
|
|
116
|
+
* - The declaration must dominate the use (same block or an enclosing
|
|
117
|
+
* one). TypeScript's block semantics mean walking up parent blocks
|
|
118
|
+
* is sufficient.
|
|
119
|
+
*/
|
|
120
|
+
function resolveLocalConstInitializer(use) {
|
|
121
|
+
const name = use.text;
|
|
122
|
+
let node = use;
|
|
123
|
+
while (node.parent) {
|
|
124
|
+
const parent = node.parent;
|
|
125
|
+
// Scan statements of an enclosing block/source-file for a matching declaration
|
|
126
|
+
let statements = null;
|
|
127
|
+
if (ts.isBlock(parent) || ts.isSourceFile(parent) || ts.isModuleBlock(parent)) {
|
|
128
|
+
statements = parent.statements;
|
|
129
|
+
}
|
|
130
|
+
else if (ts.isCaseClause(parent) || ts.isDefaultClause(parent)) {
|
|
131
|
+
statements = parent.statements;
|
|
132
|
+
}
|
|
133
|
+
if (statements) {
|
|
134
|
+
for (const stmt of statements) {
|
|
135
|
+
if (!ts.isVariableStatement(stmt))
|
|
136
|
+
continue;
|
|
137
|
+
// Skip `let` — reassignment would invalidate our resolution
|
|
138
|
+
const flags = stmt.declarationList.flags;
|
|
139
|
+
if (!(flags & ts.NodeFlags.Const))
|
|
140
|
+
continue;
|
|
141
|
+
if (stmt.declarationList.declarations.length !== 1)
|
|
142
|
+
continue;
|
|
143
|
+
const decl = stmt.declarationList.declarations[0];
|
|
144
|
+
if (!ts.isIdentifier(decl.name) || decl.name.text !== name)
|
|
145
|
+
continue;
|
|
146
|
+
if (!decl.initializer)
|
|
147
|
+
continue;
|
|
148
|
+
return decl.initializer;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
node = parent;
|
|
152
|
+
}
|
|
153
|
+
return null;
|
|
154
|
+
}
|
|
93
155
|
function classifyKind(key) {
|
|
94
156
|
if (key === 'class' || key === 'className')
|
|
95
157
|
return 'class';
|
|
@@ -328,11 +390,24 @@ __enableDevTools()${relayCall}
|
|
|
328
390
|
const replaceCalls = components
|
|
329
391
|
.map(({ varName, componentName }) => ` __replaceComponent("${componentName}", ${varName})`)
|
|
330
392
|
.join('\n');
|
|
393
|
+
// HMR auto-connect: when the Vite plugin detects that @llui/mcp's
|
|
394
|
+
// active marker file exists or appears, it sends `llui:mcp-ready`
|
|
395
|
+
// with the MCP bridge port. We forward that to __lluiConnect so the
|
|
396
|
+
// browser connects automatically — no console gymnastics, no retry
|
|
397
|
+
// spam, regardless of whether MCP or Vite started first.
|
|
398
|
+
const mcpHmrHandler = mcpPort !== null
|
|
399
|
+
? `
|
|
400
|
+
import.meta.hot.on('llui:mcp-ready', (data) => {
|
|
401
|
+
if (typeof globalThis.__lluiConnect === 'function') {
|
|
402
|
+
globalThis.__lluiConnect(data?.port)
|
|
403
|
+
}
|
|
404
|
+
})`
|
|
405
|
+
: '';
|
|
331
406
|
const bottom = `
|
|
332
407
|
if (import.meta.hot) {
|
|
333
408
|
import.meta.hot.accept(() => {
|
|
334
409
|
${replaceCalls}
|
|
335
|
-
})
|
|
410
|
+
})${mcpHmrHandler}
|
|
336
411
|
}
|
|
337
412
|
`.trim();
|
|
338
413
|
return { top, bottom };
|
|
@@ -629,6 +704,17 @@ function tryTransformElementCall(node, helpers, fieldBits, compiled, bailed, f)
|
|
|
629
704
|
events.push(f.createArrayLiteralExpression([f.createStringLiteral(eventName), value]));
|
|
630
705
|
continue;
|
|
631
706
|
}
|
|
707
|
+
// If the value is an Identifier that refers to a local const-bound
|
|
708
|
+
// arrow/function, resolve it and treat it as if the arrow had been
|
|
709
|
+
// inlined. Without this, `class: cls` where `const cls = (s) => ...`
|
|
710
|
+
// falls through to the static path and emits `.className = cls`,
|
|
711
|
+
// coercing the function to its source string in the DOM.
|
|
712
|
+
if (ts.isIdentifier(value)) {
|
|
713
|
+
const resolved = resolveLocalConstInitializer(value);
|
|
714
|
+
if (resolved && (ts.isArrowFunction(resolved) || ts.isFunctionExpression(resolved))) {
|
|
715
|
+
value = resolved;
|
|
716
|
+
}
|
|
717
|
+
}
|
|
632
718
|
// Reactive binding — value is an arrow function or function expression
|
|
633
719
|
if (ts.isArrowFunction(value) || ts.isFunctionExpression(value)) {
|
|
634
720
|
const kind = classifyKind(key);
|
|
@@ -2373,6 +2459,14 @@ function analyzeSubtree(node, helpers, fieldBits, path) {
|
|
|
2373
2459
|
events.push([key.slice(2).toLowerCase(), value]);
|
|
2374
2460
|
continue;
|
|
2375
2461
|
}
|
|
2462
|
+
// Resolve identifier → local const arrow initializer (see elSplit
|
|
2463
|
+
// path for the full rationale).
|
|
2464
|
+
if (ts.isIdentifier(value)) {
|
|
2465
|
+
const resolved = resolveLocalConstInitializer(value);
|
|
2466
|
+
if (resolved && (ts.isArrowFunction(resolved) || ts.isFunctionExpression(resolved))) {
|
|
2467
|
+
value = resolved;
|
|
2468
|
+
}
|
|
2469
|
+
}
|
|
2376
2470
|
// Reactive binding
|
|
2377
2471
|
if (ts.isArrowFunction(value) || ts.isFunctionExpression(value)) {
|
|
2378
2472
|
const kind = classifyKind(key);
|
|
@@ -2851,9 +2945,12 @@ function isPerItemCall(node) {
|
|
|
2851
2945
|
return ts.isArrowFunction(arg) || ts.isFunctionExpression(arg);
|
|
2852
2946
|
}
|
|
2853
2947
|
// Matches: item.FIELD — the item-proxy shorthand equivalent of item(t => t.FIELD).
|
|
2854
|
-
//
|
|
2855
|
-
//
|
|
2856
|
-
//
|
|
2948
|
+
// Scope-checked: the `item` identifier must resolve to a parameter of an
|
|
2949
|
+
// `each({ render })` callback. Without this check, plain
|
|
2950
|
+
// `arr.map((item) => item.field)` outside each() would be rewritten as a
|
|
2951
|
+
// per-item binding and crash at runtime with "accessor is not a function"
|
|
2952
|
+
// because `item.field` evaluates to a bare value (not a function) when
|
|
2953
|
+
// treated as an accessor.
|
|
2857
2954
|
function isPerItemFieldAccess(node) {
|
|
2858
2955
|
if (!ts.isPropertyAccessExpression(node))
|
|
2859
2956
|
return false;
|
|
@@ -2863,6 +2960,58 @@ function isPerItemFieldAccess(node) {
|
|
|
2863
2960
|
return false;
|
|
2864
2961
|
if (!ts.isIdentifier(node.name))
|
|
2865
2962
|
return false;
|
|
2963
|
+
return isItemBoundToEachRender(node);
|
|
2964
|
+
}
|
|
2965
|
+
/**
|
|
2966
|
+
* Walks up from a node and returns true iff the nearest enclosing function
|
|
2967
|
+
* that binds an `item` parameter is the `render` property of an `each()`
|
|
2968
|
+
* call. Handles both positional (`(item) => …`) and destructured
|
|
2969
|
+
* (`({ item, index }) => …`) parameter bindings.
|
|
2970
|
+
*/
|
|
2971
|
+
function isItemBoundToEachRender(node) {
|
|
2972
|
+
let current = node.parent;
|
|
2973
|
+
while (current) {
|
|
2974
|
+
if (ts.isArrowFunction(current) || ts.isFunctionExpression(current)) {
|
|
2975
|
+
if (functionParamsBindItem(current)) {
|
|
2976
|
+
return isEachRenderCallback(current);
|
|
2977
|
+
}
|
|
2978
|
+
}
|
|
2979
|
+
current = current.parent;
|
|
2980
|
+
}
|
|
2981
|
+
return false;
|
|
2982
|
+
}
|
|
2983
|
+
function functionParamsBindItem(fn) {
|
|
2984
|
+
for (const param of fn.parameters) {
|
|
2985
|
+
if (bindingNameBindsItem(param.name))
|
|
2986
|
+
return true;
|
|
2987
|
+
}
|
|
2988
|
+
return false;
|
|
2989
|
+
}
|
|
2990
|
+
function bindingNameBindsItem(name) {
|
|
2991
|
+
if (ts.isIdentifier(name))
|
|
2992
|
+
return name.text === 'item';
|
|
2993
|
+
if (ts.isObjectBindingPattern(name) || ts.isArrayBindingPattern(name)) {
|
|
2994
|
+
for (const el of name.elements) {
|
|
2995
|
+
if (ts.isBindingElement(el) && bindingNameBindsItem(el.name))
|
|
2996
|
+
return true;
|
|
2997
|
+
}
|
|
2998
|
+
}
|
|
2999
|
+
return false;
|
|
3000
|
+
}
|
|
3001
|
+
function isEachRenderCallback(fn) {
|
|
3002
|
+
const parent = fn.parent;
|
|
3003
|
+
if (!parent || !ts.isPropertyAssignment(parent))
|
|
3004
|
+
return false;
|
|
3005
|
+
if (!ts.isIdentifier(parent.name) || parent.name.text !== 'render')
|
|
3006
|
+
return false;
|
|
3007
|
+
const objLit = parent.parent;
|
|
3008
|
+
if (!objLit || !ts.isObjectLiteralExpression(objLit))
|
|
3009
|
+
return false;
|
|
3010
|
+
const call = objLit.parent;
|
|
3011
|
+
if (!call || !ts.isCallExpression(call))
|
|
3012
|
+
return false;
|
|
3013
|
+
if (!ts.isIdentifier(call.expression) || call.expression.text !== 'each')
|
|
3014
|
+
return false;
|
|
2866
3015
|
return true;
|
|
2867
3016
|
}
|
|
2868
3017
|
// Matches the hoisted identifiers produced by tryDeduplicateItemSelectors: __a0, __a1, …
|