@llui/vite-plugin 0.0.20 → 0.0.22

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/transform.js CHANGED
@@ -105,7 +105,7 @@ const PROP_KEYS = new Set([
105
105
  * string. With resolution, we see that `cls` is an arrow and emit a
106
106
  * binding exactly as if the arrow had been inlined.
107
107
  *
108
- * Scope rules:
108
+ * Lifetime rules:
109
109
  * - Only single-binding `const`/`let`/`var` declarations with an
110
110
  * initializer are considered. No destructuring, no multi-declarator
111
111
  * statements (too easy to get wrong without a type checker).
@@ -172,7 +172,7 @@ function resolveKey(key, kind) {
172
172
  return 'class';
173
173
  return key;
174
174
  }
175
- export function transformLlui(source, _filename, devMode = false, mcpPort = 5200) {
175
+ export function transformLlui(source, _filename, devMode = false, mcpPort = 5200, verbose = false) {
176
176
  const sourceFile = ts.createSourceFile('input.ts', source, ts.ScriptTarget.Latest, true);
177
177
  // Find the @llui/dom import
178
178
  const imp = findLluiImport(sourceFile);
@@ -189,6 +189,13 @@ export function transformLlui(source, _filename, devMode = false, mcpPort = 5200
189
189
  // won't match. Files without component() get FULL_MASK on all bindings.
190
190
  const fileHasComponent = hasComponentDef(sourceFile, lluiImport);
191
191
  const fieldBits = fileHasComponent ? collectDeps(source) : new Map();
192
+ if (verbose && fileHasComponent) {
193
+ const pairs = [...fieldBits.entries()]
194
+ .map(([path, bit]) => `${path}=${bit === -1 ? 'FULL' : bit}`)
195
+ .join(', ');
196
+ console.info(`[llui] ${_filename}: ${fieldBits.size} reactive path${fieldBits.size === 1 ? '' : 's'}` +
197
+ (pairs.length > 0 ? ` — ${pairs}` : ''));
198
+ }
192
199
  // Identifier names bound to the View<S,M> helpers parameter of a `view` callback.
193
200
  // When the user writes `h.text(...)` / `h.show(...)` / `h.each(...)`, the
194
201
  // compiler treats the call as if it were a bare import call.
@@ -551,9 +558,11 @@ function isViewTypeReference(t) {
551
558
  const VIEW_HELPER_PRIMITIVES = new Set([
552
559
  'show',
553
560
  'branch',
561
+ 'scope',
554
562
  'each',
555
563
  'text',
556
564
  'memo',
565
+ 'sample',
557
566
  'selector',
558
567
  'ctx',
559
568
  'slice',
@@ -877,11 +886,12 @@ function tryInjectTextMask(node, lluiImport, viewHelperNames, viewHelperAliases,
877
886
  function tryInjectStructuralMask(node, viewHelperNames, viewHelperAliases, fieldBits, f) {
878
887
  if (fieldBits.size === 0)
879
888
  return null;
880
- // Match each(), branch(), show() — bare, aliased, or member-call
889
+ // Match each(), branch(), scope(), show() — bare, aliased, or member-call
881
890
  const isEach = isHelperCall(node.expression, 'each', viewHelperNames, viewHelperAliases);
882
891
  const isBranch = isHelperCall(node.expression, 'branch', viewHelperNames, viewHelperAliases);
892
+ const isScope = isHelperCall(node.expression, 'scope', viewHelperNames, viewHelperAliases);
883
893
  const isShow = isHelperCall(node.expression, 'show', viewHelperNames, viewHelperAliases);
884
- if (!isEach && !isBranch && !isShow)
894
+ if (!isEach && !isBranch && !isScope && !isShow)
885
895
  return null;
886
896
  const optsArg = node.arguments[0];
887
897
  if (!optsArg || !ts.isObjectLiteralExpression(optsArg))
@@ -894,8 +904,9 @@ function tryInjectStructuralMask(node, viewHelperNames, viewHelperAliases, field
894
904
  return null;
895
905
  }
896
906
  }
897
- // Find the driving accessor property: items/on/when
898
- const driverProp = isEach ? 'items' : isBranch ? 'on' : 'when';
907
+ // Find the driving accessor property: items / on / when
908
+ // each 'items', branch/scope 'on', show 'when'
909
+ const driverProp = isEach ? 'items' : isBranch || isScope ? 'on' : 'when';
899
910
  let driverAccessor = null;
900
911
  for (const prop of optsArg.properties) {
901
912
  if (ts.isPropertyAssignment(prop) &&
@@ -1749,7 +1760,7 @@ function _containsSelectorBind(node) {
1749
1760
  }
1750
1761
  function containsStructuralCall(node) {
1751
1762
  if (ts.isCallExpression(node) && ts.isIdentifier(node.expression)) {
1752
- if (['each', 'branch', 'show', 'child', 'foreign'].includes(node.expression.text))
1763
+ if (['each', 'branch', 'scope', 'show', 'child', 'foreign'].includes(node.expression.text))
1753
1764
  return true;
1754
1765
  }
1755
1766
  return ts.forEachChild(node, containsStructuralCall) ?? false;
@@ -1784,7 +1795,7 @@ function computeStructuralMask(configArg, fieldBits) {
1784
1795
  function walk(node) {
1785
1796
  if (ts.isCallExpression(node)) {
1786
1797
  const name = ts.isIdentifier(node.expression) ? node.expression.text : '';
1787
- if (['each', 'branch', 'show'].includes(name) && node.arguments[0]) {
1798
+ if (['each', 'branch', 'scope', 'show'].includes(name) && node.arguments[0]) {
1788
1799
  foundStructural = true;
1789
1800
  const opts = node.arguments[0];
1790
1801
  if (ts.isObjectLiteralExpression(opts)) {
@@ -2978,7 +2989,7 @@ function isPerItemCall(node) {
2978
2989
  return ts.isArrowFunction(arg) || ts.isFunctionExpression(arg);
2979
2990
  }
2980
2991
  // Matches: item.FIELD — the item-proxy shorthand equivalent of item(t => t.FIELD).
2981
- // Scope-checked: the `item` identifier must resolve to a parameter of an
2992
+ // Lifetime-checked: the `item` identifier must resolve to a parameter of an
2982
2993
  // `each({ render })` callback. Without this check, plain
2983
2994
  // `arr.map((item) => item.field)` outside each() would be rewritten as a
2984
2995
  // per-item binding and crash at runtime with "accessor is not a function"