@barefootjs/vite 0.31.3 → 0.31.4

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/index.js CHANGED
@@ -1655,6 +1655,24 @@ function templatePartsToJsExpr(parts, opts) {
1655
1655
  return result;
1656
1656
  }
1657
1657
 
1658
+ // ../jsx/src/identifier-pattern.ts
1659
+ function withUnicodeFlag(flags) {
1660
+ return flags.includes("u") ? flags : `${flags}u`;
1661
+ }
1662
+ function escapeIdentifierForRegex(name) {
1663
+ return name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1664
+ }
1665
+ var ID_BOUNDARY_BEFORE = "(?<![\\p{ID_Continue}$])";
1666
+ var ID_BOUNDARY_AFTER = "(?![\\p{ID_Continue}$])";
1667
+ function identifierPattern(name, flags = "") {
1668
+ const esc = escapeIdentifierForRegex(name);
1669
+ return new RegExp(`${ID_BOUNDARY_BEFORE}${esc}${ID_BOUNDARY_AFTER}`, withUnicodeFlag(flags));
1670
+ }
1671
+ function identifierCallPattern(name, flags = "") {
1672
+ const esc = escapeIdentifierForRegex(name);
1673
+ return new RegExp(`${ID_BOUNDARY_BEFORE}${esc}\\s*\\(`, withUnicodeFlag(flags));
1674
+ }
1675
+
1658
1676
  // ../jsx/src/scanner/js-scanner.ts
1659
1677
  import ts2 from "typescript";
1660
1678
  function* iterateJsTokens(text, start = 0, end = text.length) {
@@ -2227,9 +2245,6 @@ function inferDefaultValue(type) {
2227
2245
  return "{}";
2228
2246
  return "undefined";
2229
2247
  }
2230
- function escapeRegExp(s) {
2231
- return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
2232
- }
2233
2248
  function freeIdsFromRefs(refs) {
2234
2249
  const out = new Set;
2235
2250
  if (!refs)
@@ -2357,16 +2372,16 @@ function wrapLoopParamAsAccessor(expr, paramName, bindings) {
2357
2372
  if (bindings && bindings.length > 0) {
2358
2373
  return rewriteLoopBindingRefs(expr, bindings, "__bfItem()");
2359
2374
  }
2360
- const re = new RegExp(`\\b${escapeRegExp(paramName)}\\b(?!\\s*\\()(?!-)`, "g");
2361
- return replaceInExprContexts(expr, re, `${paramName}()`);
2375
+ const re = new RegExp(`${ID_BOUNDARY_BEFORE}${escapeIdentifierForRegex(paramName)}(?!\\s*\\()(?!-)${ID_BOUNDARY_AFTER}`, "gu");
2376
+ return replaceInExprContexts(expr, re, () => `${paramName}()`);
2362
2377
  }
2363
2378
  function rewriteLoopBindingRefs(expr, bindings, accessor) {
2364
2379
  const byName = new Map;
2365
2380
  for (const b of bindings)
2366
2381
  byName.set(b.name, b);
2367
2382
  const preprocessed = expandShorthandBindings(expr, new Set(byName.keys()));
2368
- const alt = bindings.map((b) => escapeRegExp(b.name)).join("|");
2369
- const re = new RegExp(`\\b(${alt})\\b`, "g");
2383
+ const alt = bindings.map((b) => escapeIdentifierForRegex(b.name)).join("|");
2384
+ const re = new RegExp(`${ID_BOUNDARY_BEFORE}(${alt})${ID_BOUNDARY_AFTER}`, "gu");
2370
2385
  return replaceInExprContexts(preprocessed, re, (_m, name) => renderLoopBindingAccess(byName.get(name), accessor));
2371
2386
  }
2372
2387
  function expandShorthandBindings(expr, bindingNames) {
@@ -2633,7 +2648,7 @@ function stopAt(...kinds) {
2633
2648
 
2634
2649
  // ../jsx/src/ir-to-client-js/csr-substitute.ts
2635
2650
  import ts4 from "typescript";
2636
- function csrSubstitute(value, env) {
2651
+ function csrSubstitute(value, env, enclosingScope) {
2637
2652
  if (!value || value.trim().length === 0) {
2638
2653
  return { rewritten: value, freeIdentifiers: new Set };
2639
2654
  }
@@ -2641,7 +2656,7 @@ function csrSubstitute(value, env) {
2641
2656
  let current = value;
2642
2657
  let lastFreeIdentifiers = new Set;
2643
2658
  for (let i = 0;i < maxIter; i++) {
2644
- const step = csrSubstituteOnce(current, env);
2659
+ const step = csrSubstituteOnce(current, env, enclosingScope);
2645
2660
  lastFreeIdentifiers = step.freeIdentifiers;
2646
2661
  if (step.rewritten === current)
2647
2662
  break;
@@ -2649,7 +2664,7 @@ function csrSubstitute(value, env) {
2649
2664
  }
2650
2665
  return { rewritten: current, freeIdentifiers: lastFreeIdentifiers };
2651
2666
  }
2652
- function csrSubstituteOnce(value, env) {
2667
+ function csrSubstituteOnce(value, env, enclosingScope) {
2653
2668
  if (!value || value.trim().length === 0) {
2654
2669
  return { rewritten: value, freeIdentifiers: new Set };
2655
2670
  }
@@ -2667,7 +2682,7 @@ function csrSubstituteOnce(value, env) {
2667
2682
  if (boundStack[i].has(name))
2668
2683
  return true;
2669
2684
  }
2670
- return false;
2685
+ return enclosingScope?.isBound(name) ?? false;
2671
2686
  };
2672
2687
  const recordSubstitution = (start, end, sub) => {
2673
2688
  splices.push({ start: start - OFFSET, end: end - OFFSET, text: `(${sub.replacement})` });
@@ -2852,6 +2867,83 @@ function derivesScopeFromSlot(comp) {
2852
2867
  return comp.slotId != null && comp.loopItemRoot !== true;
2853
2868
  }
2854
2869
 
2870
+ // ../jsx/src/scope/binding-scope.ts
2871
+ class BindingScope {
2872
+ frames;
2873
+ static EMPTY = new BindingScope([]);
2874
+ constructor(frames) {
2875
+ this.frames = frames;
2876
+ }
2877
+ enterLoopRow(loop) {
2878
+ const bindings = new Map;
2879
+ if (loop.paramBindings && loop.paramBindings.length > 0) {
2880
+ for (const b of loop.paramBindings)
2881
+ bindings.set(b.name, { source: "destructure" });
2882
+ } else {
2883
+ bindings.set(loop.param, { source: "item" });
2884
+ }
2885
+ if (loop.index != null)
2886
+ bindings.set(loop.index, { source: "index" });
2887
+ for (const name of loop.preamble?.declaredNames ?? [])
2888
+ bindings.set(name, { source: "preamble" });
2889
+ const frame = { kind: "loop-row", bindings };
2890
+ return new BindingScope([frame, ...this.frames]);
2891
+ }
2892
+ enterCallback(params) {
2893
+ const bindings = new Map;
2894
+ for (const name of params)
2895
+ bindings.set(name, { source: "param" });
2896
+ const frame = { kind: "callback", bindings };
2897
+ return new BindingScope([frame, ...this.frames]);
2898
+ }
2899
+ isBound(name) {
2900
+ for (const frame of this.frames) {
2901
+ if (frame.bindings.has(name))
2902
+ return true;
2903
+ }
2904
+ return false;
2905
+ }
2906
+ lookup(name) {
2907
+ for (let depth = 0;depth < this.frames.length; depth++) {
2908
+ const frame = this.frames[depth];
2909
+ const binding = frame.bindings.get(name);
2910
+ if (binding)
2911
+ return { depth, frame, binding };
2912
+ }
2913
+ return null;
2914
+ }
2915
+ boundNames() {
2916
+ if (this.boundNamesCache)
2917
+ return this.boundNamesCache;
2918
+ const names = new Set;
2919
+ for (const frame of this.frames) {
2920
+ for (const name of frame.bindings.keys())
2921
+ names.add(name);
2922
+ }
2923
+ this.boundNamesCache = names;
2924
+ return names;
2925
+ }
2926
+ boundNamesCache;
2927
+ valueBoundNamesCache;
2928
+ valueBoundNames() {
2929
+ if (this.valueBoundNamesCache)
2930
+ return this.valueBoundNamesCache;
2931
+ const names = new Set;
2932
+ for (const frame of this.frames) {
2933
+ for (const [name, binding] of frame.bindings) {
2934
+ if (binding.source === "item" || binding.source === "index" || binding.source === "destructure") {
2935
+ names.add(name);
2936
+ }
2937
+ }
2938
+ }
2939
+ this.valueBoundNamesCache = names;
2940
+ return names;
2941
+ }
2942
+ asShadowPredicate() {
2943
+ return (name) => this.isBound(name);
2944
+ }
2945
+ }
2946
+
2855
2947
  // ../jsx/src/ir-to-client-js/html-template.ts
2856
2948
  function createStringProtector() {
2857
2949
  const strings = [];
@@ -4077,7 +4169,7 @@ function generateCsrTemplateWithOpts(node, opts) {
4077
4169
  const source = templateExpr ?? expr;
4078
4170
  if (!source)
4079
4171
  return source;
4080
- const { rewritten, freeIdentifiers: freeIdentifiers2 } = csrSubstitute(source, env);
4172
+ const { rewritten, freeIdentifiers: freeIdentifiers2 } = csrSubstitute(source, env, opts.scope);
4081
4173
  if (unsafeLocalNames && unsafeLocalNames.size > 0 && setIntersects(freeIdentifiers2, unsafeLocalNames)) {
4082
4174
  return UNSAFE_TEMPLATE_EXPR;
4083
4175
  }
@@ -4219,15 +4311,8 @@ function generateCsrTemplateWithOpts(node, opts) {
4219
4311
  return `\${renderChild('${nameForRegistryRef(node.name)}', ${propsExpr}${keyArg || (slotArg ? ", undefined" : "")}${slotArg})}`;
4220
4312
  }
4221
4313
  case "loop": {
4222
- const boundHere = new Set(opts.loopBoundNames ?? []);
4223
- if (node.paramBindings && node.paramBindings.length > 0) {
4224
- for (const b of node.paramBindings)
4225
- boundHere.add(b.name);
4226
- } else if (!node.param.startsWith("[") && !node.param.startsWith("{")) {
4227
- boundHere.add(node.param);
4228
- }
4229
- if (node.index)
4230
- boundHere.add(node.index);
4314
+ const childScope = (opts.scope ?? BindingScope.EMPTY).enterLoopRow(node);
4315
+ const boundHere = childScope.boundNames();
4231
4316
  const childEnv = {
4232
4317
  ...env,
4233
4318
  substitutions: new Map([...env.substitutions].filter(([name]) => !boundHere.has(name)))
@@ -4236,7 +4321,7 @@ function generateCsrTemplateWithOpts(node, opts) {
4236
4321
  ...opts,
4237
4322
  loopDepth: loopDepth + 1,
4238
4323
  inHoistedChildren: false,
4239
- loopBoundNames: boundHere,
4324
+ scope: childScope,
4240
4325
  csrEnv: childEnv
4241
4326
  });
4242
4327
  let childTemplate = node.children.map(recurseInLoopBody).join("");
@@ -8754,7 +8839,7 @@ function findReachableNames(primaryRefs, declarations) {
8754
8839
  const reachable = new Set;
8755
8840
  const queue = [];
8756
8841
  for (const name of allNames) {
8757
- if (new RegExp(`\\b${name}\\b`).test(primaryRefs)) {
8842
+ if (identifierPattern(name).test(primaryRefs)) {
8758
8843
  reachable.add(name);
8759
8844
  queue.push(name);
8760
8845
  }
@@ -8763,7 +8848,7 @@ function findReachableNames(primaryRefs, declarations) {
8763
8848
  const current = queue.shift();
8764
8849
  const body = bodyMap.get(current) || "";
8765
8850
  for (const name of allNames) {
8766
- if (!reachable.has(name) && new RegExp(`\\b${name}\\b`).test(body)) {
8851
+ if (!reachable.has(name) && identifierPattern(name).test(body)) {
8767
8852
  reachable.add(name);
8768
8853
  queue.push(name);
8769
8854
  }
@@ -9429,83 +9514,6 @@ var toLocaleDatePlugin = {
9429
9514
  }
9430
9515
  };
9431
9516
 
9432
- // ../jsx/src/scope/binding-scope.ts
9433
- class BindingScope {
9434
- frames;
9435
- static EMPTY = new BindingScope([]);
9436
- constructor(frames) {
9437
- this.frames = frames;
9438
- }
9439
- enterLoopRow(loop) {
9440
- const bindings = new Map;
9441
- if (loop.paramBindings && loop.paramBindings.length > 0) {
9442
- for (const b of loop.paramBindings)
9443
- bindings.set(b.name, { source: "destructure" });
9444
- } else {
9445
- bindings.set(loop.param, { source: "item" });
9446
- }
9447
- if (loop.index != null)
9448
- bindings.set(loop.index, { source: "index" });
9449
- for (const name of loop.preamble?.declaredNames ?? [])
9450
- bindings.set(name, { source: "preamble" });
9451
- const frame = { kind: "loop-row", bindings };
9452
- return new BindingScope([frame, ...this.frames]);
9453
- }
9454
- enterCallback(params) {
9455
- const bindings = new Map;
9456
- for (const name of params)
9457
- bindings.set(name, { source: "param" });
9458
- const frame = { kind: "callback", bindings };
9459
- return new BindingScope([frame, ...this.frames]);
9460
- }
9461
- isBound(name) {
9462
- for (const frame of this.frames) {
9463
- if (frame.bindings.has(name))
9464
- return true;
9465
- }
9466
- return false;
9467
- }
9468
- lookup(name) {
9469
- for (let depth = 0;depth < this.frames.length; depth++) {
9470
- const frame = this.frames[depth];
9471
- const binding = frame.bindings.get(name);
9472
- if (binding)
9473
- return { depth, frame, binding };
9474
- }
9475
- return null;
9476
- }
9477
- boundNames() {
9478
- if (this.boundNamesCache)
9479
- return this.boundNamesCache;
9480
- const names = new Set;
9481
- for (const frame of this.frames) {
9482
- for (const name of frame.bindings.keys())
9483
- names.add(name);
9484
- }
9485
- this.boundNamesCache = names;
9486
- return names;
9487
- }
9488
- boundNamesCache;
9489
- valueBoundNamesCache;
9490
- valueBoundNames() {
9491
- if (this.valueBoundNamesCache)
9492
- return this.valueBoundNamesCache;
9493
- const names = new Set;
9494
- for (const frame of this.frames) {
9495
- for (const [name, binding] of frame.bindings) {
9496
- if (binding.source === "item" || binding.source === "index" || binding.source === "destructure") {
9497
- names.add(name);
9498
- }
9499
- }
9500
- }
9501
- this.valueBoundNamesCache = names;
9502
- return names;
9503
- }
9504
- asShadowPredicate() {
9505
- return (name) => this.isBound(name);
9506
- }
9507
- }
9508
-
9509
9517
  // ../jsx/src/jsx-to-ir.ts
9510
9518
  var CLIENT_DIRECTIVE_INTERIOR_RE2 = /^\s*@client\s*$/;
9511
9519
  var BLOCK_COMMENT_RE2 = /\/\*([\s\S]*?)\*\//g;
@@ -9769,17 +9777,17 @@ function createTransformContext(analyzer) {
9769
9777
  patterns: {
9770
9778
  signals: analyzer.signals.map((s) => ({
9771
9779
  getter: s.getter,
9772
- pattern: new RegExp(`\\b${s.getter}\\s*\\(`)
9780
+ pattern: identifierCallPattern(s.getter)
9773
9781
  })),
9774
9782
  memos: analyzer.memos.map((m) => ({
9775
9783
  name: m.name,
9776
- pattern: new RegExp(`\\b${m.name}\\s*\\(`)
9784
+ pattern: identifierCallPattern(m.name)
9777
9785
  })),
9778
- props: analyzer.propsParams.filter((p) => p.name !== "children").map((p) => ({ name: p.name, pattern: new RegExp(`\\b${p.name}\\b`) })),
9786
+ props: analyzer.propsParams.filter((p) => p.name !== "children").map((p) => ({ name: p.name, pattern: identifierPattern(p.name) })),
9779
9787
  constants: analyzer.localConstants.map((c) => ({
9780
9788
  name: c.name,
9781
9789
  value: c.value,
9782
- pattern: new RegExp(`\\b${c.name}\\b`)
9790
+ pattern: identifierPattern(c.name)
9783
9791
  }))
9784
9792
  },
9785
9793
  getJS(node) {
@@ -10551,7 +10559,7 @@ function transformExpressionInner(expr, ctx, node, isClientOnly) {
10551
10559
  };
10552
10560
  const reactive = isReactiveExpression(exprText, ctx, expr) || isReactiveOrigin(origin);
10553
10561
  const scopeValueNames = ctx.scope.valueBoundNames();
10554
- const refsLoopParam = scopeValueNames.size > 0 && Array.from(scopeValueNames).some((p) => new RegExp(`\\b${p}\\b`).test(exprText));
10562
+ const refsLoopParam = scopeValueNames.size > 0 && Array.from(scopeValueNames).some((p) => identifierPattern(p).test(exprText));
10555
10563
  const callsReactive = exprCallsReactiveGetters(expr, ctx);
10556
10564
  const hasCalls = exprHasFunctionCalls(expr);
10557
10565
  const needsSlot = reactive || isClientOnly || refsLoopParam || callsReactive || hasCalls;
@@ -10586,7 +10594,7 @@ function transformJsxFunctionCall(callExpr, jsxFunc, ctx, _isClientOnly) {
10586
10594
  const substitutedGetJS = (node) => {
10587
10595
  let text = baseGetJS(node);
10588
10596
  for (const [paramName, argExpr] of substitutions) {
10589
- text = text.replace(new RegExp(`\\b${paramName}\\b`, "g"), argExpr);
10597
+ text = text.replace(identifierPattern(paramName, "g"), () => argExpr);
10590
10598
  }
10591
10599
  return text;
10592
10600
  };
@@ -10628,7 +10636,7 @@ function transformMultiReturnJsxFunctionCall(callExpr, info, ctx) {
10628
10636
  const substitutedGetJS = (node) => {
10629
10637
  let text = baseGetJS(node);
10630
10638
  for (const [paramName, argExpr] of substitutions) {
10631
- text = text.replace(new RegExp(`\\b${paramName}\\b`, "g"), argExpr);
10639
+ text = text.replace(identifierPattern(paramName, "g"), () => argExpr);
10632
10640
  }
10633
10641
  return text;
10634
10642
  };
@@ -13173,7 +13181,7 @@ function referencesLoopParam(expr, ctx) {
13173
13181
  if (boundNames.size === 0)
13174
13182
  return false;
13175
13183
  for (const p of boundNames) {
13176
- if (new RegExp(`\\b${p}\\b`).test(expr))
13184
+ if (identifierPattern(p).test(expr))
13177
13185
  return true;
13178
13186
  }
13179
13187
  return false;
@@ -13255,7 +13263,7 @@ function hasReactiveAttributes(attrs, ctx) {
13255
13263
  const scopeValueNames = ctx.scope.valueBoundNames();
13256
13264
  if (scopeValueNames.size > 0) {
13257
13265
  for (const p of scopeValueNames) {
13258
- if (new RegExp(`\\b${p}\\b`).test(valueToCheck))
13266
+ if (identifierPattern(p).test(valueToCheck))
13259
13267
  return true;
13260
13268
  }
13261
13269
  }
@@ -13461,18 +13469,22 @@ function buildIfStatementChain(analyzer, ctx, opts) {
13461
13469
  }
13462
13470
 
13463
13471
  // ../jsx/src/ir-to-client-js/prop-handling.ts
13464
- function expandDynamicPropValue(value, ctx) {
13472
+ function expandDynamicPropValue(value, ctx, scope) {
13465
13473
  const trimmedValue = value.trim();
13474
+ if (scope?.isBound(trimmedValue))
13475
+ return value;
13466
13476
  const constant = ctx.localConstants.find((c) => c.name === trimmedValue);
13467
13477
  if (constant && constant.value) {
13468
13478
  return constant.value;
13469
13479
  }
13470
13480
  return value;
13471
13481
  }
13472
- function expandConstantForReactivity(expr, ctx, originalFreeIds) {
13482
+ function expandConstantForReactivity(expr, ctx, originalFreeIds, scope) {
13473
13483
  if (ctx.propsObjectName)
13474
13484
  return { expr, freeIds: originalFreeIds };
13475
13485
  const trimmedValue = expr.trim();
13486
+ if (scope?.isBound(trimmedValue))
13487
+ return { expr, freeIds: originalFreeIds };
13476
13488
  const constant = ctx.localConstants.find((c) => c.name === trimmedValue);
13477
13489
  if (constant && constant.value) {
13478
13490
  return { expr: constant.value, freeIds: constant.freeIdentifiers };
@@ -13508,6 +13520,16 @@ function getControlledPropName(signal, propsParams, propsObjectName = null) {
13508
13520
  }
13509
13521
 
13510
13522
  // ../jsx/src/ir-to-client-js/reactivity.ts
13523
+ function buildLoopRowScope(loopParam, loopParamBindings, preambleNames, loopIndex) {
13524
+ if (!loopParam)
13525
+ return;
13526
+ return BindingScope.EMPTY.enterLoopRow({
13527
+ param: loopParam,
13528
+ paramBindings: loopParamBindings,
13529
+ index: loopIndex,
13530
+ preamble: preambleNames && preambleNames.size > 0 ? { declaredNames: [...preambleNames] } : undefined
13531
+ });
13532
+ }
13511
13533
  function decideWrapFromAstFlags(node) {
13512
13534
  if (node.origin && isReactiveOrigin(node.origin)) {
13513
13535
  return { wrap: true, reason: "proven-reactive" };
@@ -13536,12 +13558,12 @@ function decideWrapForChildProp(expandedValue, ctx, prop) {
13536
13558
  }
13537
13559
  function needsEffectWrapper(expr, ctx, freeIdentifiers2) {
13538
13560
  for (const signal of ctx.signals) {
13539
- if (new RegExp(`\\b${signal.getter}\\s*\\(`).test(expr)) {
13561
+ if (identifierCallPattern(signal.getter).test(expr)) {
13540
13562
  return true;
13541
13563
  }
13542
13564
  }
13543
13565
  for (const memo of ctx.memos) {
13544
- if (new RegExp(`\\b${memo.name}\\s*\\(`).test(expr)) {
13566
+ if (identifierCallPattern(memo.name).test(expr)) {
13545
13567
  return true;
13546
13568
  }
13547
13569
  }
@@ -13731,8 +13753,9 @@ function traverseForComponents(node, components, skipConditionals = false) {
13731
13753
  }
13732
13754
  });
13733
13755
  }
13734
- function collectLoopChildReactiveTexts(node, ctx, loopParam, loopParamBindings, stopAtReactiveConditionals = false) {
13756
+ function collectLoopChildReactiveTexts(node, ctx, loopParam, loopParamBindings, stopAtReactiveConditionals = false, preambleNames, loopIndex) {
13735
13757
  const texts = [];
13758
+ const scope = buildLoopRowScope(loopParam, loopParamBindings, preambleNames, loopIndex);
13736
13759
  walkIR(node, false, {
13737
13760
  ...stopAt("loop", "async", "ifStatement"),
13738
13761
  expression: ({ node: n, scope: insideConditional }) => {
@@ -13741,7 +13764,7 @@ function collectLoopChildReactiveTexts(node, ctx, loopParam, loopParamBindings,
13741
13764
  if (n.preambleRegion)
13742
13765
  return;
13743
13766
  const originFreeIds = freeIdsFromRefs(n.origin?.freeRefs);
13744
- const expanded = expandConstantForReactivity(n.expr, ctx, originFreeIds);
13767
+ const expanded = expandConstantForReactivity(n.expr, ctx, originFreeIds, scope);
13745
13768
  const reactive = classifyReactivity(expanded.expr, ctx, loopParam, loopParamBindings, expanded.freeIds).kind !== "none" || decideWrapFromAstFlags(n).wrap;
13746
13769
  if (!reactive)
13747
13770
  return;
@@ -13765,8 +13788,9 @@ function anyNameIn(names, set) {
13765
13788
  return true;
13766
13789
  return false;
13767
13790
  }
13768
- function collectLoopChildReactiveAttrs(node, ctx, loopParam, loopParamBindings, stopAtReactiveConditionals = false, preambleNames) {
13791
+ function collectLoopChildReactiveAttrs(node, ctx, loopParam, loopParamBindings, stopAtReactiveConditionals = false, preambleNames, loopIndex) {
13769
13792
  const attrs = [];
13793
+ const scope = buildLoopRowScope(loopParam, loopParamBindings, preambleNames, loopIndex);
13770
13794
  traverseElements(node, (el) => {
13771
13795
  if (el.slotId) {
13772
13796
  for (const attr of el.attrs) {
@@ -13779,7 +13803,7 @@ function collectLoopChildReactiveAttrs(node, ctx, loopParam, loopParamBindings,
13779
13803
  const valueStr = attrValueToString(attr.value);
13780
13804
  if (!valueStr)
13781
13805
  continue;
13782
- const expanded = expandConstantForReactivity(valueStr, ctx, attr.freeIdentifiers);
13806
+ const expanded = expandConstantForReactivity(valueStr, ctx, attr.freeIdentifiers, scope);
13783
13807
  const readsPreamble = preambleNames !== undefined && preambleNames.size > 0 && anyNameIn(expanded.freeIds ?? extractFreeIdentifiersFromText(expanded.expr), preambleNames);
13784
13808
  const reactive = classifyReactivity(expanded.expr, ctx, loopParam, loopParamBindings, expanded.freeIds).kind !== "none" || readsPreamble || attr.callsReactiveGetters || attr.hasFunctionCalls;
13785
13809
  if (!attr.clientOnly && !reactive)
@@ -14084,13 +14108,13 @@ function collectInnerLoops(nodes, siblingOffsets, outerLoopParam, ctx, options)
14084
14108
  const emitDepth = fixedDepth ?? scope.depth + 1;
14085
14109
  const loopParamsForTemplate = outerLoopParam ? [outerLoopParam, { param: n.param, bindings: n.paramBindings }] : undefined;
14086
14110
  const template = n.children.map((c) => irToPlaceholderTemplate(c, undefined, emitDepth, loopParamsForTemplate)).join("");
14087
- const refsOuter = outerLoopParam ? new RegExp(`\\b${outerLoopParam}\\b`).test(n.array) : false;
14111
+ const refsOuter = outerLoopParam ? identifierPattern(outerLoopParam).test(n.array) : false;
14088
14112
  const bindings = emptyLoopChildBindings();
14089
14113
  const innerPreambleNames = preambleNamesOf(n);
14090
14114
  if (ctx) {
14091
14115
  for (const child of n.children) {
14092
- bindings.reactiveTexts.push(...collectLoopChildReactiveTexts(child, ctx, n.param, n.paramBindings));
14093
- bindings.reactiveAttrs.push(...collectLoopChildReactiveAttrs(child, ctx, n.param, n.paramBindings, false, innerPreambleNames));
14116
+ bindings.reactiveTexts.push(...collectLoopChildReactiveTexts(child, ctx, n.param, n.paramBindings, false, innerPreambleNames, n.index));
14117
+ bindings.reactiveAttrs.push(...collectLoopChildReactiveAttrs(child, ctx, n.param, n.paramBindings, false, innerPreambleNames, n.index));
14094
14118
  bindings.refs.push(...collectLoopChildRefs(child));
14095
14119
  }
14096
14120
  }
@@ -14117,7 +14141,7 @@ function collectInnerLoops(nodes, siblingOffsets, outerLoopParam, ctx, options)
14117
14141
  bindings.events.push(...collectLoopChildEventsWithNesting(child));
14118
14142
  }
14119
14143
  if (ctx) {
14120
- bindings.conditionals.push(...collectLoopChildConditionals({ type: "fragment", children: n.children, loc: n.loc }, ctx, siblingOffsets, n.param, n.paramBindings));
14144
+ bindings.conditionals.push(...collectLoopChildConditionals({ type: "fragment", children: n.children, loc: n.loc }, ctx, siblingOffsets, n.param, n.paramBindings, innerPreambleNames, n.index));
14121
14145
  }
14122
14146
  }
14123
14147
  result.push({
@@ -14313,7 +14337,7 @@ function collectElements(node, ctx, siblingOffsets, insideConditional = false) {
14313
14337
  return;
14314
14338
  const projectionInner = l.method === "flatMap" && l.children.length === 1 && l.children[0].type === "loop" ? l.children[0] : undefined;
14315
14339
  const childHandlers = [];
14316
- const bindings = projectionInner ? emptyLoopChildBindings() : collectLoopChildBindings(l.children, ctx, siblingOffsets, l.param, l.paramBindings, preambleNamesOf(l));
14340
+ const bindings = projectionInner ? emptyLoopChildBindings() : collectLoopChildBindings(l.children, ctx, siblingOffsets, l.param, l.paramBindings, preambleNamesOf(l), l.index);
14317
14341
  if (!projectionInner) {
14318
14342
  for (const child of l.children) {
14319
14343
  childHandlers.push(...collectEventHandlersFromIR(child));
@@ -14568,7 +14592,7 @@ function collectBranchLoops(node, ctx, siblingOffsets) {
14568
14592
  } else {
14569
14593
  childTemplate = n.children.map((c) => irToHtmlTemplate(c, undefined, 0, branchLoopParamSpec)).join("");
14570
14594
  }
14571
- const branchBindings = ctx && !projectionInner ? collectLoopChildBindings(n.children, ctx, siblingOffsets, n.param, n.paramBindings, preambleNamesOf(n)) : emptyLoopChildBindings();
14595
+ const branchBindings = ctx && !projectionInner ? collectLoopChildBindings(n.children, ctx, siblingOffsets, n.param, n.paramBindings, preambleNamesOf(n), n.index) : emptyLoopChildBindings();
14572
14596
  loops.push({
14573
14597
  kind: "branch",
14574
14598
  array: n.array,
@@ -14654,19 +14678,20 @@ function preambleNamesOf(loop) {
14654
14678
  const declared = loop.preamble?.declaredNames;
14655
14679
  return declared && declared.length > 0 ? new Set(declared) : undefined;
14656
14680
  }
14657
- function collectLoopChildBindings(children, ctx, siblingOffsets, loopParam, loopParamBindings, preambleNames) {
14681
+ function collectLoopChildBindings(children, ctx, siblingOffsets, loopParam, loopParamBindings, preambleNames, loopIndex) {
14658
14682
  const bindings = emptyLoopChildBindings();
14659
14683
  for (const child of children) {
14660
14684
  bindings.events.push(...collectLoopChildEventsWithNesting(child));
14661
- bindings.reactiveAttrs.push(...collectLoopChildReactiveAttrs(child, ctx, loopParam, loopParamBindings, true, preambleNames));
14662
- bindings.reactiveTexts.push(...collectLoopChildReactiveTexts(child, ctx, loopParam, loopParamBindings, true));
14685
+ bindings.reactiveAttrs.push(...collectLoopChildReactiveAttrs(child, ctx, loopParam, loopParamBindings, true, preambleNames, loopIndex));
14686
+ bindings.reactiveTexts.push(...collectLoopChildReactiveTexts(child, ctx, loopParam, loopParamBindings, true, preambleNames, loopIndex));
14663
14687
  bindings.refs.push(...collectLoopChildRefs(child));
14664
- bindings.conditionals.push(...collectLoopChildConditionals(child, ctx, siblingOffsets, loopParam, loopParamBindings));
14688
+ bindings.conditionals.push(...collectLoopChildConditionals(child, ctx, siblingOffsets, loopParam, loopParamBindings, preambleNames, loopIndex));
14665
14689
  }
14666
14690
  return bindings;
14667
14691
  }
14668
- function collectLoopChildConditionals(node, ctx, siblingOffsets, loopParam, loopParamBindings) {
14692
+ function collectLoopChildConditionals(node, ctx, siblingOffsets, loopParam, loopParamBindings, preambleNames, loopIndex) {
14669
14693
  const conditionals = [];
14694
+ const scope = buildLoopRowScope(loopParam, loopParamBindings, preambleNames, loopIndex);
14670
14695
  const refsAnyBindingViaFreeIds = (freeIds) => {
14671
14696
  if (loopParamBindings && loopParamBindings.length > 0) {
14672
14697
  for (const b of loopParamBindings) {
@@ -14686,7 +14711,7 @@ function collectLoopChildConditionals(node, ctx, siblingOffsets, loopParam, loop
14686
14711
  const refsLoopParamInSource = refsAnyBindingViaFreeIds(sourceFreeIds);
14687
14712
  if (!n.reactive && !refsLoopParamInSource)
14688
14713
  return;
14689
- const expanded = expandConstantForReactivity(n.condition, ctx, sourceFreeIds);
14714
+ const expanded = expandConstantForReactivity(n.condition, ctx, sourceFreeIds, scope);
14690
14715
  if (classifyReactivity(expanded.expr, ctx, loopParam, loopParamBindings, expanded.freeIds).kind === "none")
14691
14716
  return;
14692
14717
  const loopParamsForCond = loopParam ? [{ param: loopParam, bindings: loopParamBindings }] : undefined;
@@ -14697,23 +14722,23 @@ function collectLoopChildConditionals(node, ctx, siblingOffsets, loopParam, loop
14697
14722
  condition: expanded.expr,
14698
14723
  whenTrueHtml,
14699
14724
  whenFalseHtml,
14700
- whenTrue: summarizeLoopChildBranch(n.whenTrue, ctx, siblingOffsets, loopParam, loopParamBindings),
14701
- whenFalse: summarizeLoopChildBranch(n.whenFalse, ctx, siblingOffsets, loopParam, loopParamBindings),
14725
+ whenTrue: summarizeLoopChildBranch(n.whenTrue, ctx, siblingOffsets, loopParam, loopParamBindings, preambleNames, loopIndex),
14726
+ whenFalse: summarizeLoopChildBranch(n.whenFalse, ctx, siblingOffsets, loopParam, loopParamBindings, preambleNames, loopIndex),
14702
14727
  ...expanded.freeIds !== undefined && { conditionFreeIdentifiers: expanded.freeIds }
14703
14728
  });
14704
14729
  }
14705
14730
  });
14706
14731
  return conditionals;
14707
14732
  }
14708
- function summarizeLoopChildBranch(node, ctx, siblingOffsets, loopParam, loopParamBindings) {
14733
+ function summarizeLoopChildBranch(node, ctx, siblingOffsets, loopParam, loopParamBindings, preambleNames, loopIndex) {
14709
14734
  const inner = collectInnerLoops([node], siblingOffsets, loopParam, ctx, branchInnerLoopOptions);
14710
14735
  return {
14711
14736
  childComponents: collectConditionalBranchChildComponents(node),
14712
14737
  innerLoops: inner.length > 0 ? inner : undefined,
14713
- conditionals: collectLoopChildConditionals(node, ctx, siblingOffsets, loopParam, loopParamBindings),
14738
+ conditionals: collectLoopChildConditionals(node, ctx, siblingOffsets, loopParam, loopParamBindings, preambleNames, loopIndex),
14714
14739
  events: collectConditionalBranchEvents(node),
14715
- reactiveAttrs: collectLoopChildReactiveAttrs(node, ctx, loopParam, loopParamBindings, true),
14716
- reactiveTexts: node.type === "expression" && node.hasFunctionCalls ? [] : collectLoopChildReactiveTexts(node, ctx, loopParam, loopParamBindings, true)
14740
+ reactiveAttrs: collectLoopChildReactiveAttrs(node, ctx, loopParam, loopParamBindings, true, preambleNames, loopIndex),
14741
+ reactiveTexts: node.type === "expression" && node.hasFunctionCalls ? [] : collectLoopChildReactiveTexts(node, ctx, loopParam, loopParamBindings, true, preambleNames, loopIndex)
14717
14742
  };
14718
14743
  }
14719
14744
 
@@ -15285,7 +15310,7 @@ var MODULE_CONSTANTS_PLACEHOLDER = "/* __MODULE_LEVEL_CONSTANTS__ */";
15285
15310
  function detectUsedImports(code) {
15286
15311
  const used = new Set;
15287
15312
  for (const name of RUNTIME_IMPORT_CANDIDATES) {
15288
- if (new RegExp(`\\b${name}\\s*\\(`).test(code)) {
15313
+ if (identifierCallPattern(name).test(code)) {
15289
15314
  used.add(name);
15290
15315
  }
15291
15316
  }
@@ -15686,7 +15711,7 @@ function containsAnyIdentifier(node, names) {
15686
15711
  function scanRefsByName(text, bindings) {
15687
15712
  const result = new Map;
15688
15713
  for (const name of bindings.keys()) {
15689
- const re = new RegExp(`\\b${name}\\b`);
15714
+ const re = identifierPattern(name);
15690
15715
  if (re.test(text))
15691
15716
  result.set(name, []);
15692
15717
  }
@@ -18354,7 +18379,7 @@ function buildStaticArrayDelegationPlan(elem, profileComponentName) {
18354
18379
  function buildKeyedOrIndexLookup(args) {
18355
18380
  const hasBindings = (args.paramBindings?.length ?? 0) > 0;
18356
18381
  if (args.key !== null) {
18357
- const keyWithItem = hasBindings ? substituteLoopBindings(args.key, args.paramBindings, "item") : args.key.replace(new RegExp(`\\b${args.param}\\b`, "g"), "item");
18382
+ const keyWithItem = hasBindings ? substituteLoopBindings(args.key, args.paramBindings, "item") : args.key.replace(identifierPattern(args.param, "g"), "item");
18358
18383
  return {
18359
18384
  kind: "keyed",
18360
18385
  arrayExpr: args.array,
@@ -20560,7 +20585,7 @@ function emitKeyedLookup(ls, ev, handlerCall, lookup) {
20560
20585
  }
20561
20586
  for (const nested of ev.nestedLoops) {
20562
20587
  const rawKey = nested.key ?? "";
20563
- const innerKeyExpr = nested.paramBindings && nested.paramBindings.length > 0 ? substituteLoopBindings(rawKey, nested.paramBindings, "item") : rawKey.replace(new RegExp(`\\b${nested.param}\\b`, "g"), "item");
20588
+ const innerKeyExpr = nested.paramBindings && nested.paramBindings.length > 0 ? substituteLoopBindings(rawKey, nested.paramBindings, "item") : rawKey.replace(identifierPattern(nested.param, "g"), "item");
20564
20589
  const outerRef = hasBindings ? "__bfLoopItem" : param;
20565
20590
  ls.push(` const ${nested.param} = ${outerRef} && ${nested.array}.find(item => String(${innerKeyExpr}) === innerKey${nested.depth})`);
20566
20591
  }
@@ -21254,7 +21279,7 @@ function rewritePropsObjectRef(code, propsObjectName) {
21254
21279
  const srcPropsName = propsObjectName ?? "props";
21255
21280
  if (srcPropsName === PROPS_PARAM)
21256
21281
  return code;
21257
- if (!new RegExp(`\\b${srcPropsName}\\b`).test(code))
21282
+ if (!identifierPattern(srcPropsName).test(code))
21258
21283
  return code;
21259
21284
  const sourceFile = ts19.createSourceFile("init-body.ts", code, ts19.ScriptTarget.Latest, true, ts19.ScriptKind.TS);
21260
21285
  const spans = [];
@@ -23664,7 +23689,7 @@ class JsxAdapter extends BaseAdapter {
23664
23689
  lines.push(` const ${signal.getter} = () => ${initialValue}`);
23665
23690
  }
23666
23691
  if (signal.setter) {
23667
- const setterUsed = new RegExp(`\\b${signal.setter}\\b`).test(setterRefText);
23692
+ const setterUsed = identifierPattern(signal.setter).test(setterRefText);
23668
23693
  if (setterUsed) {
23669
23694
  lines.push(` const ${signal.setter} = (..._args: any[]) => {}`);
23670
23695
  }
@@ -23862,6 +23887,7 @@ class JsxAdapter extends BaseAdapter {
23862
23887
  }
23863
23888
 
23864
23889
  // ../jsx/src/adapters/template-imports.ts
23890
+ import ts25 from "typescript";
23865
23891
  var CLIENT_PACKAGE_SOURCES = new Set([
23866
23892
  "@barefootjs/client",
23867
23893
  "@barefootjs/client/runtime"
@@ -24251,11 +24277,11 @@ function registerBuiltinLoweringPlugins() {
24251
24277
  registerLoweringPlugin(plugin);
24252
24278
  }
24253
24279
  // ../jsx/src/combine-client-js.ts
24254
- import ts25 from "typescript";
24255
- // ../jsx/src/debug.ts
24256
24280
  import ts26 from "typescript";
24257
- // ../jsx/src/profiler.ts
24281
+ // ../jsx/src/debug.ts
24258
24282
  import ts27 from "typescript";
24283
+ // ../jsx/src/profiler.ts
24284
+ import ts28 from "typescript";
24259
24285
 
24260
24286
  // ../jsx/src/index.ts
24261
24287
  registerBuiltinLoweringPlugins();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@barefootjs/vite",
3
- "version": "0.31.3",
3
+ "version": "0.31.4",
4
4
  "description": "Vite plugin for BarefootJS: Vite/Rollup owns bundling, hashing, chunking, tree-shaking and minification of client assets, BarefootJS keeps only the JSX to (template, client JS) compile",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -38,17 +38,17 @@
38
38
  "directory": "packages/vite"
39
39
  },
40
40
  "dependencies": {
41
- "@barefootjs/shared": "0.31.3"
41
+ "@barefootjs/shared": "0.31.4"
42
42
  },
43
43
  "peerDependencies": {
44
44
  "@barefootjs/jsx": ">=0.2.0",
45
45
  "vite": "^6.0.0"
46
46
  },
47
47
  "devDependencies": {
48
- "@barefootjs/client": "0.31.3",
49
- "@barefootjs/go-template": "0.31.3",
50
- "@barefootjs/hono": "0.31.3",
51
- "@barefootjs/jsx": "0.31.3",
48
+ "@barefootjs/client": "0.31.4",
49
+ "@barefootjs/go-template": "0.31.4",
50
+ "@barefootjs/hono": "0.31.4",
51
+ "@barefootjs/jsx": "0.31.4",
52
52
  "typescript": "^5.0.0",
53
53
  "vite": "^6.0.0"
54
54
  }
@@ -0,0 +1,92 @@
1
+ /**
2
+ * Regression (#2588): a relative specifier inside a DYNAMIC `import()` must
3
+ * be re-anchored to the emitted template's directory, exactly like the
4
+ * static `import` statements `rewriteImportsForTemplate` already handles.
5
+ *
6
+ * Dynamic imports never reach `ir.metadata.templateImports` — they ride
7
+ * along inside declaration source text that the adapter re-emits verbatim
8
+ * (`generateModuleScopeDeclarations`' consts/functions, and a component
9
+ * body's local handlers). Before the fix they were emitted untouched, so a
10
+ * specifier written relative to `components/` still said `../lib/heavy`
11
+ * once the template landed in `app/dist/components/` — a path that does
12
+ * not exist. The backend bundler then hard-fails (`Could not resolve
13
+ * "../lib/heavy"`), so this is a build break, not a type-only defect.
14
+ *
15
+ * The fixture mirrors the layout that hits this in the wild (piconic-ai/koma):
16
+ * a `components` dir and a plain `lib` dir side by side, with templates
17
+ * emitted to a DEEPER directory — root-relative and template-relative only
18
+ * diverge when the two depths differ, so a flat layout would not reproduce.
19
+ * Lives under `packages/vite/` (not a system tmpdir) so `@barefootjs/client`
20
+ * resolves through the monorepo's real workspace symlinks — same reason
21
+ * `e2e-fixture`/`e2e-fixture-dev`/`e2e-fixture-relimport` do.
22
+ */
23
+ import { describe, test, expect, afterAll } from 'bun:test'
24
+ import { build } from 'vite'
25
+ import { mkdtemp, rm, readFile } from 'node:fs/promises'
26
+ import { tmpdir } from 'node:os'
27
+ import { join, resolve } from 'node:path'
28
+ import { HonoAdapter } from '@barefootjs/hono/adapter'
29
+ import { barefoot } from '../plugin.ts'
30
+
31
+ const FIXTURE_ROOT = resolve(import.meta.dirname, '../../e2e-fixture-dynimport')
32
+ const APP_ROOT = join(FIXTURE_ROOT, 'app')
33
+ const COMPONENTS_DIR = join(FIXTURE_ROOT, 'components')
34
+
35
+ // `app/dist/components/Lazy.tsx` → up three → the fixture root, where `lib/`
36
+ // sits. The source says `../lib/heavy` from `components/`; the emitted
37
+ // template has to say this instead.
38
+ const REANCHORED = '../../../lib/heavy'
39
+
40
+ describe('dynamic import() re-anchoring in emitted templates', () => {
41
+ let outDir: string
42
+ let templatesDir: string
43
+
44
+ afterAll(async () => {
45
+ await rm(outDir, { recursive: true, force: true })
46
+ await rm(join(APP_ROOT, 'dist'), { recursive: true, force: true })
47
+ })
48
+
49
+ test('re-anchors dynamic imports at module scope, in type position, and inside a component body', async () => {
50
+ outDir = await mkdtemp(join(tmpdir(), 'barefoot-vite-dynimport-dist-'))
51
+ templatesDir = join(APP_ROOT, 'dist/components')
52
+
53
+ await build({
54
+ configFile: false,
55
+ root: APP_ROOT,
56
+ base: '/static/',
57
+ logLevel: 'warn',
58
+ build: { outDir, emptyOutDir: true },
59
+ plugins: [
60
+ barefoot({
61
+ adapter: new HonoAdapter(),
62
+ components: [COMPONENTS_DIR],
63
+ templates: templatesDir,
64
+ }),
65
+ ],
66
+ })
67
+
68
+ const template = await readFile(join(templatesDir, 'Lazy.tsx'), 'utf8')
69
+
70
+ // Nothing anywhere may still carry the source-relative form. Asserted
71
+ // first and globally: a per-site check would pass while some fourth
72
+ // emission path silently leaked the old specifier.
73
+ expect(template).not.toContain("'../lib/heavy'")
74
+
75
+ // Module scope, TYPE position — `typeof import('…')` is an
76
+ // ImportTypeNode, a different AST node from the call expression below,
77
+ // and was missed independently.
78
+ expect(template).toContain(`typeof import('${REANCHORED}')`)
79
+
80
+ // Module scope, VALUE position — inside a re-emitted const's body.
81
+ expect(template).toContain(`modPromise = import('${REANCHORED}')`)
82
+
83
+ // Component scope — inside a local handler in the component body,
84
+ // which is emitted by a different code path than module declarations.
85
+ expect(template).toContain(`await import('${REANCHORED}')`)
86
+
87
+ // The re-anchored path must actually resolve on disk from the emitted
88
+ // template's own directory — the assertions above only pin the string.
89
+ const target = resolve(templatesDir, REANCHORED + '.ts')
90
+ expect(await readFile(target, 'utf8')).toContain('export function heavy')
91
+ }, 60_000)
92
+ })