@fictjs/compiler 0.1.0 → 0.2.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.
Files changed (3) hide show
  1. package/dist/index.cjs +329 -249
  2. package/dist/index.js +329 -249
  3. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -14267,6 +14267,35 @@ var SAFE_FUNCTIONS = /* @__PURE__ */ new Set([
14267
14267
  "Date.parse"
14268
14268
  ]);
14269
14269
 
14270
+ // src/debug.ts
14271
+ function parseFlag(raw) {
14272
+ if (!raw) return false;
14273
+ const val = raw.toLowerCase();
14274
+ return val === "1" || val === "true" || val === "yes" || val === "on";
14275
+ }
14276
+ function debugEnabled(flag) {
14277
+ const normalized = flag.toLowerCase();
14278
+ const legacy = process.env[`DEBUG_${flag.toUpperCase()}`];
14279
+ if (parseFlag(legacy)) return true;
14280
+ const fictLegacy = process.env[`FICT_DEBUG_${flag.toUpperCase()}`];
14281
+ if (parseFlag(fictLegacy)) return true;
14282
+ const raw = process.env.FICT_DEBUG ?? process.env.DEBUG_FICT;
14283
+ if (!raw) return false;
14284
+ if (parseFlag(raw)) return true;
14285
+ const parts = raw.split(",").map((p) => p.trim().toLowerCase()).filter(Boolean);
14286
+ return parts.includes(normalized) || parts.includes("all");
14287
+ }
14288
+ function debugLog(flag, message, data) {
14289
+ if (!debugEnabled(flag)) return;
14290
+ const msg = typeof message === "function" ? message() : message;
14291
+ const prefix = `[fict:${flag}]`;
14292
+ if (data !== void 0) {
14293
+ console.log(prefix, msg, data);
14294
+ } else {
14295
+ console.log(prefix, msg);
14296
+ }
14297
+ }
14298
+
14270
14299
  // src/ir/build-hir.ts
14271
14300
  var t = __toESM(require_lib3(), 1);
14272
14301
 
@@ -15668,7 +15697,7 @@ function convertExpression(node) {
15668
15697
  }
15669
15698
  if (t.isObjectMethod(prop)) {
15670
15699
  if (prop.computed) return void 0;
15671
- const keyExpr2 = t.isIdentifier(prop.key) ? { kind: "Identifier", name: prop.key.name } : t.isStringLiteral(prop.key) ? { kind: "Literal", value: prop.key.value } : void 0;
15700
+ const keyExpr2 = t.isIdentifier(prop.key) ? { kind: "Identifier", name: prop.key.name } : t.isStringLiteral(prop.key) ? { kind: "Literal", value: prop.key.value } : t.isNumericLiteral(prop.key) ? { kind: "Literal", value: prop.key.value } : void 0;
15672
15701
  if (!keyExpr2) return void 0;
15673
15702
  const fnExpr = t.functionExpression(
15674
15703
  null,
@@ -15685,7 +15714,7 @@ function convertExpression(node) {
15685
15714
  };
15686
15715
  }
15687
15716
  if (!t.isObjectProperty(prop) || prop.computed) return void 0;
15688
- const keyExpr = t.isIdentifier(prop.key) ? { kind: "Identifier", name: prop.key.name } : t.isStringLiteral(prop.key) ? { kind: "Literal", value: prop.key.value } : void 0;
15717
+ const keyExpr = t.isIdentifier(prop.key) ? { kind: "Identifier", name: prop.key.name } : t.isStringLiteral(prop.key) ? { kind: "Literal", value: prop.key.value } : t.isNumericLiteral(prop.key) ? { kind: "Literal", value: prop.key.value } : void 0;
15689
15718
  if (!keyExpr) return void 0;
15690
15719
  if (!t.isExpression(prop.value)) return void 0;
15691
15720
  return {
@@ -16027,23 +16056,6 @@ function convertJSXMemberExpr(node) {
16027
16056
  };
16028
16057
  }
16029
16058
 
16030
- // src/debug.ts
16031
- function parseFlag(raw) {
16032
- if (!raw) return false;
16033
- const val = raw.toLowerCase();
16034
- return val === "1" || val === "true" || val === "yes" || val === "on";
16035
- }
16036
- function debugEnabled(flag) {
16037
- const normalized = flag.toLowerCase();
16038
- const legacy = process.env[`DEBUG_${flag.toUpperCase()}`];
16039
- if (parseFlag(legacy)) return true;
16040
- const raw = process.env.FICT_DEBUG ?? process.env.DEBUG_FICT;
16041
- if (!raw) return false;
16042
- if (parseFlag(raw)) return true;
16043
- const parts = raw.split(",").map((p) => p.trim().toLowerCase()).filter(Boolean);
16044
- return parts.includes(normalized) || parts.includes("all");
16045
- }
16046
-
16047
16059
  // src/fine-grained-dom.ts
16048
16060
  function normalizeDependencyKey(name) {
16049
16061
  return name.split(".").map((part) => part.replace(/_\d+$/, "")).join(".");
@@ -18093,6 +18105,35 @@ function structurizeTry(ctx, block, term) {
18093
18105
 
18094
18106
  // src/ir/regions.ts
18095
18107
  var REACTIVE_CREATORS = /* @__PURE__ */ new Set(["createEffect", "createMemo", "createSelector"]);
18108
+ function buildEffectCall(ctx, t2, effectFn, options) {
18109
+ if (ctx.inModule) {
18110
+ ctx.helpersUsed.add("effect");
18111
+ return t2.callExpression(t2.identifier(RUNTIME_ALIASES.effect), [effectFn]);
18112
+ }
18113
+ ctx.helpersUsed.add("useEffect");
18114
+ ctx.needsCtx = true;
18115
+ const args = [t2.identifier("__fictCtx"), effectFn];
18116
+ const slot = options?.slot;
18117
+ if (options?.forceSlot) {
18118
+ args.push(slot !== void 0 && slot >= 0 ? t2.numericLiteral(slot) : t2.identifier("undefined"));
18119
+ } else if (slot !== void 0 && slot >= 0) {
18120
+ args.push(t2.numericLiteral(slot));
18121
+ }
18122
+ return t2.callExpression(t2.identifier(RUNTIME_ALIASES.useEffect), args);
18123
+ }
18124
+ function buildMemoCall(ctx, t2, memoFn, slot) {
18125
+ if (ctx.inModule) {
18126
+ ctx.helpersUsed.add("memo");
18127
+ return t2.callExpression(t2.identifier(RUNTIME_ALIASES.memo), [memoFn]);
18128
+ }
18129
+ ctx.helpersUsed.add("useMemo");
18130
+ ctx.needsCtx = true;
18131
+ const args = [t2.identifier("__fictCtx"), memoFn];
18132
+ if (slot !== void 0 && slot >= 0) {
18133
+ args.push(t2.numericLiteral(slot));
18134
+ }
18135
+ return t2.callExpression(t2.identifier(RUNTIME_ALIASES.useMemo), args);
18136
+ }
18096
18137
  function expressionCreatesReactive(expr) {
18097
18138
  if (expr.kind === "CallExpression" && expr.callee.kind === "Identifier") {
18098
18139
  const base = getSSABaseName(expr.callee.name);
@@ -18383,8 +18424,10 @@ function expressionUsesTracked(expr, ctx) {
18383
18424
  case "Identifier":
18384
18425
  return ctx.trackedVars.has(deSSAVarName(expr.name)) || (ctx.externalTracked?.has(deSSAVarName(expr.name)) ?? false) || (ctx.memoVars?.has(deSSAVarName(expr.name)) ?? false) || (ctx.aliasVars?.has(deSSAVarName(expr.name)) ?? false);
18385
18426
  case "MemberExpression":
18427
+ case "OptionalMemberExpression":
18386
18428
  return expressionUsesTracked(expr.object, ctx);
18387
18429
  case "CallExpression":
18430
+ case "OptionalCallExpression":
18388
18431
  if (expressionUsesTracked(expr.callee, ctx)) return true;
18389
18432
  return expr.arguments.some((arg) => expressionUsesTracked(arg, ctx));
18390
18433
  case "LogicalExpression":
@@ -18606,16 +18649,8 @@ function lowerNodeWithRegionContext(node, t2, ctx, declaredVars, regionCtx) {
18606
18649
  const inNonReactiveScope = !!(ctx.nonReactiveScopeDepth && ctx.nonReactiveScopeDepth > 0);
18607
18650
  const shouldWrapEffect = ctx.wrapTrackedExpressions !== false && !ctx.inRegionMemo && !inNonReactiveScope && expressionUsesTracked(node.test, ctx) && !statementHasEarlyExit(ifStmt, t2);
18608
18651
  if (shouldWrapEffect) {
18609
- ctx.helpersUsed.add("useEffect");
18610
- ctx.needsCtx = true;
18611
- return [
18612
- t2.expressionStatement(
18613
- t2.callExpression(t2.identifier(RUNTIME_ALIASES.useEffect), [
18614
- t2.identifier("__fictCtx"),
18615
- t2.arrowFunctionExpression([], t2.blockStatement([ifStmt]))
18616
- ])
18617
- )
18618
- ];
18652
+ const effectFn = t2.arrowFunctionExpression([], t2.blockStatement([ifStmt]));
18653
+ return [t2.expressionStatement(buildEffectCall(ctx, t2, effectFn))];
18619
18654
  }
18620
18655
  return [ifStmt];
18621
18656
  }
@@ -18880,16 +18915,8 @@ function lowerStructuredNodeForRegion(node, region, t2, ctx, declaredVars, regio
18880
18915
  ifStmt = buildIfStmt(consequent, alternate);
18881
18916
  }
18882
18917
  if (shouldWrapEffect) {
18883
- ctx.helpersUsed.add("useEffect");
18884
- ctx.needsCtx = true;
18885
- return [
18886
- t2.expressionStatement(
18887
- t2.callExpression(t2.identifier(RUNTIME_ALIASES.useEffect), [
18888
- t2.identifier("__fictCtx"),
18889
- t2.arrowFunctionExpression([], t2.blockStatement([ifStmt]))
18890
- ])
18891
- )
18892
- ];
18918
+ const effectFn = t2.arrowFunctionExpression([], t2.blockStatement([ifStmt]));
18919
+ return [t2.expressionStatement(buildEffectCall(ctx, t2, effectFn))];
18893
18920
  }
18894
18921
  return [ifStmt];
18895
18922
  }
@@ -19261,30 +19288,16 @@ function wrapInMemo(region, t2, declaredVars, ctx, bodyStatementsOverride, outpu
19261
19288
  const outputNames = outputNamesOverride ?? Array.from(region.declarations).map((name) => deSSAVarName(name));
19262
19289
  const uniqueOutputNames = [...new Set(outputNames)];
19263
19290
  const bindableOutputs = uniqueOutputNames.filter((name) => !declaredVars.has(name));
19264
- if (debugEnabled("region")) {
19265
- console.log("Region memo", region.id, {
19266
- instructions: region.instructions.map((instr) => instr.kind),
19267
- outputs: uniqueOutputNames
19268
- });
19269
- }
19291
+ debugLog("region", `Region memo ${region.id}`, {
19292
+ instructions: region.instructions.map((instr) => instr.kind),
19293
+ outputs: uniqueOutputNames
19294
+ });
19270
19295
  if (uniqueOutputNames.length === 0) {
19271
- ctx.helpersUsed.add("useEffect");
19272
- ctx.needsCtx = true;
19273
- const effectCallArgs = [
19274
- t2.identifier("__fictCtx"),
19275
- t2.arrowFunctionExpression([], t2.blockStatement(bodyStatements))
19276
- ];
19277
- {
19278
- const slot = reserveHookSlot(ctx);
19279
- if (slot >= 0) {
19280
- effectCallArgs.push(t2.numericLiteral(slot));
19281
- }
19282
- }
19283
- const effectCall = t2.callExpression(t2.identifier(RUNTIME_ALIASES.useEffect), effectCallArgs);
19296
+ const effectFn = t2.arrowFunctionExpression([], t2.blockStatement(bodyStatements));
19297
+ const slot = ctx.inModule ? void 0 : reserveHookSlot(ctx);
19298
+ const effectCall = buildEffectCall(ctx, t2, effectFn, { slot });
19284
19299
  statements.push(t2.expressionStatement(effectCall));
19285
19300
  } else {
19286
- ctx.helpersUsed.add("useMemo");
19287
- ctx.needsCtx = true;
19288
19301
  if (!bodyStatementsOverride) {
19289
19302
  const lazyInfo = analyzeHIRConditionalUsage(region, ctx);
19290
19303
  if (lazyInfo) {
@@ -19313,15 +19326,8 @@ function wrapInMemo(region, t2, declaredVars, ctx, bodyStatementsOverride, outpu
19313
19326
  };
19314
19327
  const returnObj = t2.objectExpression(uniqueOutputNames.map((name) => buildOutputProperty(name)));
19315
19328
  const memoBody = t2.blockStatement([...bodyStatements, t2.returnStatement(returnObj)]);
19316
- const slot = reserveHookSlot(ctx);
19317
- const memoArgs = [
19318
- t2.identifier("__fictCtx"),
19319
- t2.arrowFunctionExpression([], memoBody)
19320
- ];
19321
- if (slot >= 0) {
19322
- memoArgs.push(t2.numericLiteral(slot));
19323
- }
19324
- const memoCall = t2.callExpression(t2.identifier(RUNTIME_ALIASES.useMemo), memoArgs);
19329
+ const slot = ctx.inModule ? void 0 : reserveHookSlot(ctx);
19330
+ const memoCall = buildMemoCall(ctx, t2, t2.arrowFunctionExpression([], memoBody), slot);
19325
19331
  const regionVarName = `__region_${region.id}`;
19326
19332
  statements.push(
19327
19333
  t2.variableDeclaration("const", [t2.variableDeclarator(t2.identifier(regionVarName), memoCall)])
@@ -19331,16 +19337,13 @@ function wrapInMemo(region, t2, declaredVars, ctx, bodyStatementsOverride, outpu
19331
19337
  (name) => ctx.trackedVars.has(name) && !isAccessorOutput(name)
19332
19338
  );
19333
19339
  const directOutputs = bindableOutputs.filter((name) => !getterOutputs.includes(name));
19334
- if (debugEnabled("region")) {
19335
- console.log("Region debug", {
19336
- id: region.id,
19337
- outputs: uniqueOutputNames,
19338
- getterOutputs,
19339
- directOutputs,
19340
- tracked: Array.from(ctx.trackedVars),
19341
- memoVars: Array.from(ctx.memoVars ?? [])
19342
- });
19343
- }
19340
+ debugLog("region", `Region debug ${region.id}`, {
19341
+ outputs: uniqueOutputNames,
19342
+ getterOutputs,
19343
+ directOutputs,
19344
+ tracked: Array.from(ctx.trackedVars),
19345
+ memoVars: Array.from(ctx.memoVars ?? [])
19346
+ });
19344
19347
  if (directOutputs.length > 0) {
19345
19348
  directOutputs.forEach((name) => declaredVars.add(name));
19346
19349
  statements.push(
@@ -19368,21 +19371,15 @@ function wrapInMemo(region, t2, declaredVars, ctx, bodyStatementsOverride, outpu
19368
19371
  ctx.memoVars?.add(name);
19369
19372
  }
19370
19373
  if (region.hasControlFlow && getterOutputs.length > 0) {
19371
- ctx.helpersUsed.add("useEffect");
19372
- ctx.needsCtx = true;
19373
19374
  const effectBody = t2.blockStatement(
19374
19375
  getterOutputs.map((name) => t2.expressionStatement(t2.callExpression(t2.identifier(name), [])))
19375
19376
  );
19376
19377
  statements.push(
19377
19378
  t2.expressionStatement(
19378
- t2.callExpression(t2.identifier(RUNTIME_ALIASES.useEffect), [
19379
- t2.identifier("__fictCtx"),
19380
- t2.arrowFunctionExpression([], effectBody),
19381
- (() => {
19382
- const slot2 = reserveHookSlot(ctx);
19383
- return slot2 >= 0 ? t2.numericLiteral(slot2) : t2.identifier("undefined");
19384
- })()
19385
- ])
19379
+ buildEffectCall(ctx, t2, t2.arrowFunctionExpression([], effectBody), {
19380
+ slot: ctx.inModule ? void 0 : reserveHookSlot(ctx),
19381
+ forceSlot: true
19382
+ })
19386
19383
  )
19387
19384
  );
19388
19385
  }
@@ -19596,18 +19593,13 @@ function generateLazyConditionalMemo(region, orderedOutputs, bodyStatements, con
19596
19593
  t2.ifStatement(conditionId, t2.blockStatement(trueBlock), t2.blockStatement(falseBlock))
19597
19594
  );
19598
19595
  }
19599
- ctx.helpersUsed.add("useMemo");
19600
- ctx.needsCtx = true;
19601
19596
  const regionVarName = `__region_${region.id}`;
19602
- const slotForMemo = reserveHookSlot(ctx);
19603
- const memoArgs = [
19604
- t2.identifier("__fictCtx"),
19605
- t2.arrowFunctionExpression([], t2.blockStatement(memoBody))
19606
- ];
19607
- if (slotForMemo >= 0) {
19608
- memoArgs.push(t2.numericLiteral(slotForMemo));
19609
- }
19610
- const memoCall = t2.callExpression(t2.identifier("__fictUseMemo"), memoArgs);
19597
+ const memoCall = buildMemoCall(
19598
+ ctx,
19599
+ t2,
19600
+ t2.arrowFunctionExpression([], t2.blockStatement(memoBody)),
19601
+ ctx.inModule ? void 0 : reserveHookSlot(ctx)
19602
+ );
19611
19603
  statements.push(
19612
19604
  t2.variableDeclaration("const", [t2.variableDeclarator(t2.identifier(regionVarName), memoCall)])
19613
19605
  );
@@ -19696,20 +19688,9 @@ function instructionToStatement(instr, t2, declaredVars, ctx, _buildMemoCall) {
19696
19688
  const isReactiveObjectCall = instr.value.kind === "CallExpression" && instr.value.callee.kind === "Identifier" && ["mergeProps"].includes(instr.value.callee.name);
19697
19689
  const isMemoReturningCall = isAccessorReturningCall || isReactiveObjectCall;
19698
19690
  const lowerAssignedValue = (forceAssigned = false) => lowerExpressionWithDeSSA(instr.value, ctx, forceAssigned || isFunctionValue);
19699
- const buildMemoCall = (expr) => {
19700
- const args = [
19701
- t2.identifier("__fictCtx"),
19702
- t2.arrowFunctionExpression([], expr)
19703
- ];
19704
- if (inRegionMemo) {
19705
- const slot = reserveHookSlot(ctx);
19706
- if (slot >= 0) {
19707
- args.push(t2.numericLiteral(slot));
19708
- }
19709
- }
19710
- ctx.helpersUsed.add("useMemo");
19711
- ctx.needsCtx = true;
19712
- return t2.callExpression(t2.identifier(RUNTIME_ALIASES.useMemo), args);
19691
+ const buildDerivedMemoCall = (expr) => {
19692
+ const slot = !ctx.inModule && inRegionMemo ? reserveHookSlot(ctx) : void 0;
19693
+ return buildMemoCall(ctx, t2, t2.arrowFunctionExpression([], expr), slot);
19713
19694
  };
19714
19695
  if (isShadowDeclaration && declKind) {
19715
19696
  ctx.trackedVars.delete(baseName2);
@@ -19748,7 +19729,7 @@ function instructionToStatement(instr, t2, declaredVars, ctx, _buildMemoCall) {
19748
19729
  return t2.variableDeclaration(normalizedDecl, [
19749
19730
  t2.variableDeclarator(
19750
19731
  t2.identifier(baseName2),
19751
- isMemoReturningCall ? derivedExpr : buildMemoCall(derivedExpr)
19732
+ isMemoReturningCall ? derivedExpr : buildDerivedMemoCall(derivedExpr)
19752
19733
  )
19753
19734
  ]);
19754
19735
  }
@@ -19775,7 +19756,7 @@ function instructionToStatement(instr, t2, declaredVars, ctx, _buildMemoCall) {
19775
19756
  return t2.variableDeclaration(normalizedDecl, [
19776
19757
  t2.variableDeclarator(
19777
19758
  t2.identifier(baseName2),
19778
- isMemoReturningCall ? derivedExpr : buildMemoCall(derivedExpr)
19759
+ isMemoReturningCall ? derivedExpr : buildDerivedMemoCall(derivedExpr)
19779
19760
  )
19780
19761
  ]);
19781
19762
  }
@@ -19784,7 +19765,14 @@ function instructionToStatement(instr, t2, declaredVars, ctx, _buildMemoCall) {
19784
19765
  ]);
19785
19766
  }
19786
19767
  if (aliasVars.has(baseName2) && declaredVars.has(baseName2)) {
19787
- throw new Error(`Alias reassignment is not supported for "${baseName2}"`);
19768
+ throw new Error(
19769
+ `Alias reassignment is not supported for "${baseName2}".
19770
+
19771
+ "${baseName2}" was assigned from a reactive value and cannot be reassigned.
19772
+ Consider:
19773
+ - Using a new variable name for the new value
19774
+ - Updating the original reactive source instead`
19775
+ );
19788
19776
  }
19789
19777
  if (capturedTracked && isSignal) {
19790
19778
  return t2.expressionStatement(
@@ -19792,7 +19780,14 @@ function instructionToStatement(instr, t2, declaredVars, ctx, _buildMemoCall) {
19792
19780
  );
19793
19781
  }
19794
19782
  if (aliasVars.has(baseName2) && !declaredVars.has(baseName2)) {
19795
- throw new Error(`Alias reassignment is not supported for "${baseName2}"`);
19783
+ throw new Error(
19784
+ `Alias reassignment is not supported for "${baseName2}".
19785
+
19786
+ "${baseName2}" was assigned from a reactive value and cannot be reassigned.
19787
+ Consider:
19788
+ - Using a new variable name for the new value
19789
+ - Updating the original reactive source instead`
19790
+ );
19796
19791
  }
19797
19792
  if (dependsOnTracked && !declKind && !isDestructuringTemp && !isTracked && !isSignal && instr.value.kind === "Identifier" && ctx.trackedVars.has(deSSAVarName(instr.value.name))) {
19798
19793
  const derivedExpr = lowerAssignedValue(true);
@@ -19816,13 +19811,20 @@ function instructionToStatement(instr, t2, declaredVars, ctx, _buildMemoCall) {
19816
19811
  t2.assignmentExpression(
19817
19812
  "=",
19818
19813
  t2.identifier(baseName2),
19819
- isMemoReturningCall ? derivedExpr : buildMemoCall(derivedExpr)
19814
+ isMemoReturningCall ? derivedExpr : buildDerivedMemoCall(derivedExpr)
19820
19815
  )
19821
19816
  );
19822
19817
  }
19823
19818
  if (declaredVars.has(baseName2)) {
19824
19819
  if (aliasVars.has(baseName2)) {
19825
- throw new Error(`Alias reassignment is not supported for "${baseName2}"`);
19820
+ throw new Error(
19821
+ `Alias reassignment is not supported for "${baseName2}".
19822
+
19823
+ "${baseName2}" was assigned from a reactive value and cannot be reassigned.
19824
+ Consider:
19825
+ - Using a new variable name for the new value
19826
+ - Updating the original reactive source instead`
19827
+ );
19826
19828
  }
19827
19829
  if (isSignal) {
19828
19830
  return t2.expressionStatement(
@@ -19864,7 +19866,7 @@ function instructionToStatement(instr, t2, declaredVars, ctx, _buildMemoCall) {
19864
19866
  return t2.variableDeclaration("const", [
19865
19867
  t2.variableDeclarator(
19866
19868
  t2.identifier(baseName2),
19867
- isMemoReturningCall ? derivedExpr : buildMemoCall(derivedExpr)
19869
+ isMemoReturningCall ? derivedExpr : buildDerivedMemoCall(derivedExpr)
19868
19870
  )
19869
19871
  ]);
19870
19872
  }
@@ -19888,7 +19890,7 @@ function instructionToStatement(instr, t2, declaredVars, ctx, _buildMemoCall) {
19888
19890
  return t2.variableDeclaration("const", [
19889
19891
  t2.variableDeclarator(
19890
19892
  t2.identifier(baseName2),
19891
- isMemoReturningCall ? derivedExpr : buildMemoCall(derivedExpr)
19893
+ isMemoReturningCall ? derivedExpr : buildDerivedMemoCall(derivedExpr)
19892
19894
  )
19893
19895
  ]);
19894
19896
  }
@@ -19908,8 +19910,6 @@ function instructionToStatement(instr, t2, declaredVars, ctx, _buildMemoCall) {
19908
19910
  const inNonReactiveScope = !!(ctx.nonReactiveScopeDepth && ctx.nonReactiveScopeDepth > 0);
19909
19911
  const shouldWrapExpr = ctx.wrapTrackedExpressions !== false && !inNonReactiveScope && (usesTracked || hasTrackedControlDep);
19910
19912
  if (shouldWrapExpr) {
19911
- ctx.helpersUsed.add("useEffect");
19912
- ctx.needsCtx = true;
19913
19913
  const depReads = [];
19914
19914
  if (hasTrackedControlDep) {
19915
19915
  const uniqueDeps = new Set(Array.from(controlDeps).map((dep) => deSSAVarName(dep)));
@@ -19921,12 +19921,8 @@ function instructionToStatement(instr, t2, declaredVars, ctx, _buildMemoCall) {
19921
19921
  }
19922
19922
  const loweredExpr = lowerExpressionWithDeSSA(instr.value, ctx);
19923
19923
  const effectBody = depReads.length > 0 ? ctx.t.blockStatement([...depReads, ctx.t.expressionStatement(loweredExpr)]) : loweredExpr;
19924
- return t2.expressionStatement(
19925
- t2.callExpression(t2.identifier(RUNTIME_ALIASES.useEffect), [
19926
- t2.identifier("__fictCtx"),
19927
- ctx.t.isBlockStatement(effectBody) ? t2.arrowFunctionExpression([], effectBody) : t2.arrowFunctionExpression([], effectBody)
19928
- ])
19929
- );
19924
+ const effectFn = ctx.t.isBlockStatement(effectBody) ? t2.arrowFunctionExpression([], effectBody) : t2.arrowFunctionExpression([], effectBody);
19925
+ return t2.expressionStatement(buildEffectCall(ctx, t2, effectFn));
19930
19926
  }
19931
19927
  return t2.expressionStatement(lowerExpressionWithDeSSA(instr.value, ctx));
19932
19928
  }
@@ -20469,6 +20465,7 @@ function createCodegenContext(t2) {
20469
20465
  mutatedVars: /* @__PURE__ */ new Set(),
20470
20466
  inRegionMemo: false,
20471
20467
  inListRender: false,
20468
+ inModule: false,
20472
20469
  nextHookSlot: HOOK_SLOT_BASE,
20473
20470
  nonReactiveScopeDepth: 0,
20474
20471
  inConditional: 0,
@@ -20789,7 +20786,15 @@ function detectDerivedCycles(fn, _scopeResult) {
20789
20786
  if (visiting.has(node)) {
20790
20787
  const idx = stack.indexOf(node);
20791
20788
  const cycle = idx >= 0 ? [...stack.slice(idx), node] : [...stack, node];
20792
- throw new Error(`Detected cyclic derived dependency: ${cycle.join(" -> ")}`);
20789
+ throw new Error(
20790
+ `Detected cyclic derived dependency: ${cycle.join(" -> ")}
20791
+
20792
+ Tip: This usually happens when derived values depend on each other in a loop.
20793
+ Consider:
20794
+ - Using untrack() to break the dependency cycle
20795
+ - Restructuring your derived values to avoid circular dependencies
20796
+ - Moving one of the values to $state if it should be independently mutable`
20797
+ );
20793
20798
  }
20794
20799
  if (visited.has(node)) return;
20795
20800
  visiting.add(node);
@@ -20804,12 +20809,11 @@ function detectDerivedCycles(fn, _scopeResult) {
20804
20809
  for (const node of graph.keys()) {
20805
20810
  visit(node);
20806
20811
  }
20807
- if (debugEnabled("cycles")) {
20808
- console.error(
20809
- "cycle graph",
20810
- Array.from(graph.entries()).map(([k, v]) => [k, Array.from(v)])
20811
- );
20812
- }
20812
+ debugLog(
20813
+ "cycles",
20814
+ "cycle graph",
20815
+ Array.from(graph.entries()).map(([k, v]) => [k, Array.from(v)])
20816
+ );
20813
20817
  }
20814
20818
  function collectExpressionIdentifiers(expr, into) {
20815
20819
  if (!expr || typeof expr !== "object") return;
@@ -21905,22 +21909,32 @@ function lowerExpressionImpl(expr, ctx, _isAssigned = false) {
21905
21909
  return t2.identifier("undefined");
21906
21910
  case "CallExpression": {
21907
21911
  if (expr.callee.kind === "Identifier" && expr.callee.name === "$state") {
21912
+ const args = lowerCallArguments(expr.arguments);
21913
+ if (ctx.inModule) {
21914
+ ctx.helpersUsed.add("signal");
21915
+ return t2.callExpression(t2.identifier(RUNTIME_ALIASES.signal), args);
21916
+ }
21908
21917
  ctx.helpersUsed.add("useSignal");
21909
21918
  ctx.needsCtx = true;
21910
21919
  return t2.callExpression(t2.identifier(RUNTIME_ALIASES.useSignal), [
21911
21920
  t2.identifier("__fictCtx"),
21912
- ...lowerCallArguments(expr.arguments)
21921
+ ...args
21913
21922
  ]);
21914
21923
  }
21915
21924
  if (expr.callee.kind === "Identifier" && expr.callee.name === "$effect") {
21925
+ const args = lowerCallArguments(
21926
+ expr.arguments,
21927
+ (arg) => arg.kind === "ArrowFunction" || arg.kind === "FunctionExpression" ? withNonReactiveScope(ctx, () => lowerExpression(arg, ctx)) : lowerExpression(arg, ctx)
21928
+ );
21929
+ if (ctx.inModule) {
21930
+ ctx.helpersUsed.add("effect");
21931
+ return t2.callExpression(t2.identifier(RUNTIME_ALIASES.effect), args);
21932
+ }
21916
21933
  ctx.helpersUsed.add("useEffect");
21917
21934
  ctx.needsCtx = true;
21918
21935
  return t2.callExpression(t2.identifier(RUNTIME_ALIASES.useEffect), [
21919
21936
  t2.identifier("__fictCtx"),
21920
- ...lowerCallArguments(
21921
- expr.arguments,
21922
- (arg) => arg.kind === "ArrowFunction" || arg.kind === "FunctionExpression" ? withNonReactiveScope(ctx, () => lowerExpression(arg, ctx)) : lowerExpression(arg, ctx)
21923
- )
21937
+ ...args
21924
21938
  ]);
21925
21939
  }
21926
21940
  if (expr.callee.kind === "Identifier" && expr.callee.name === "__forOf") {
@@ -22812,6 +22826,16 @@ function isExpressionReactive(expr, ctx) {
22812
22826
  for (const dep of deps) {
22813
22827
  if (ctx.trackedVars.has(dep)) return true;
22814
22828
  }
22829
+ if (ctx.memoVars) {
22830
+ for (const dep of deps) {
22831
+ if (ctx.memoVars.has(dep)) return true;
22832
+ }
22833
+ }
22834
+ if (ctx.signalVars) {
22835
+ for (const dep of deps) {
22836
+ if (ctx.signalVars.has(dep)) return true;
22837
+ }
22838
+ }
22815
22839
  for (const region of regionsToCheck) {
22816
22840
  for (const dep of deps) {
22817
22841
  if (region.declarations.has(dep) || region.dependencies.has(dep)) {
@@ -23079,8 +23103,12 @@ function lowerIntrinsicElement(jsx, ctx) {
23079
23103
  const regionMeta = containingRegion ? regionInfoToMetadata(containingRegion) : null;
23080
23104
  const shouldMemo = !ctx.inListRender && !(ctx.inConditional && ctx.inConditional > 0) && regionMeta ? shouldMemoizeRegion(regionMeta) : false;
23081
23105
  if (shouldMemo) {
23082
- ctx.helpersUsed.add("useMemo");
23083
- ctx.needsCtx = true;
23106
+ if (ctx.inModule) {
23107
+ ctx.helpersUsed.add("memo");
23108
+ } else {
23109
+ ctx.helpersUsed.add("useMemo");
23110
+ ctx.needsCtx = true;
23111
+ }
23084
23112
  }
23085
23113
  const hoistedTmplId = getOrCreateHoistedTemplate(html, ctx);
23086
23114
  const rootId = genTemp(ctx, "root");
@@ -23354,16 +23382,15 @@ function lowerIntrinsicElement(jsx, ctx) {
23354
23382
  statements.push(t2.returnStatement(elId));
23355
23383
  const body = t2.blockStatement(statements);
23356
23384
  if (shouldMemo && containingRegion) {
23357
- const memoArgs = [
23358
- t2.identifier("__fictCtx"),
23359
- t2.arrowFunctionExpression([], body)
23360
- ];
23385
+ const memoBody = t2.arrowFunctionExpression([], body);
23386
+ if (ctx.inModule) {
23387
+ return t2.callExpression(t2.callExpression(t2.identifier(RUNTIME_ALIASES.memo), [memoBody]), []);
23388
+ }
23389
+ const memoArgs = [t2.identifier("__fictCtx"), memoBody];
23361
23390
  if (ctx.isComponentFn) {
23362
- {
23363
- const slot = reserveHookSlot2(ctx);
23364
- if (slot >= 0) {
23365
- memoArgs.push(t2.numericLiteral(slot));
23366
- }
23391
+ const slot = reserveHookSlot2(ctx);
23392
+ if (slot >= 0) {
23393
+ memoArgs.push(t2.numericLiteral(slot));
23367
23394
  }
23368
23395
  }
23369
23396
  return t2.callExpression(t2.callExpression(t2.identifier(RUNTIME_ALIASES.useMemo), memoArgs), []);
@@ -23423,9 +23450,9 @@ function emitHIRChildBinding(markerId, expr, statements, ctx, containingRegion)
23423
23450
  emitConditionalChild(parentId, markerId, expr, statements, ctx);
23424
23451
  return;
23425
23452
  }
23426
- if (expr.kind === "CallExpression") {
23453
+ if (expr.kind === "CallExpression" || expr.kind === "OptionalCallExpression") {
23427
23454
  const callee = expr.callee;
23428
- if (callee.kind === "MemberExpression" && callee.property.kind === "Identifier" && callee.property.name === "map") {
23455
+ if ((callee.kind === "MemberExpression" || callee.kind === "OptionalMemberExpression") && callee.property.kind === "Identifier" && callee.property.name === "map") {
23429
23456
  emitListChild(parentId, markerId, expr, statements, ctx);
23430
23457
  return;
23431
23458
  }
@@ -23465,10 +23492,14 @@ function emitConditionalChild(parentId, markerId, expr, statements, ctx) {
23465
23492
  ctx.helpersUsed.add("conditional");
23466
23493
  ctx.helpersUsed.add("createElement");
23467
23494
  ctx.helpersUsed.add("onDestroy");
23468
- ctx.helpersUsed.add("toNodeArray");
23469
23495
  let condition;
23470
23496
  let consequent;
23471
23497
  let alternate = null;
23498
+ const lowerBranch = (branch) => {
23499
+ const listExpr = buildListCallExpression(branch, statements, ctx);
23500
+ if (listExpr) return listExpr;
23501
+ return lowerDomExpression(branch, ctx);
23502
+ };
23472
23503
  const enterConditional = () => {
23473
23504
  ctx.inConditional = (ctx.inConditional ?? 0) + 1;
23474
23505
  };
@@ -23478,13 +23509,13 @@ function emitConditionalChild(parentId, markerId, expr, statements, ctx) {
23478
23509
  if (expr.kind === "ConditionalExpression") {
23479
23510
  condition = lowerDomExpression(expr.test, ctx);
23480
23511
  enterConditional();
23481
- consequent = lowerDomExpression(expr.consequent, ctx);
23482
- alternate = lowerDomExpression(expr.alternate, ctx);
23512
+ consequent = lowerBranch(expr.consequent);
23513
+ alternate = lowerBranch(expr.alternate);
23483
23514
  exitConditional();
23484
23515
  } else if (expr.kind === "LogicalExpression" && expr.operator === "&&") {
23485
23516
  condition = lowerDomExpression(expr.left, ctx);
23486
23517
  enterConditional();
23487
- consequent = lowerDomExpression(expr.right, ctx);
23518
+ consequent = lowerBranch(expr.right);
23488
23519
  exitConditional();
23489
23520
  } else {
23490
23521
  return;
@@ -23506,29 +23537,11 @@ function emitConditionalChild(parentId, markerId, expr, statements, ctx) {
23506
23537
  )
23507
23538
  ])
23508
23539
  );
23509
- const markersId = genTemp(ctx, "markers");
23510
- statements.push(
23511
- t2.variableDeclaration("const", [
23512
- t2.variableDeclarator(
23513
- markersId,
23514
- t2.callExpression(t2.identifier(RUNTIME_ALIASES.toNodeArray), [
23515
- t2.memberExpression(bindingId, t2.identifier("marker"))
23516
- ])
23517
- )
23518
- ])
23519
- );
23520
- const mId = genTemp(ctx, "m");
23521
23540
  statements.push(
23522
- t2.forOfStatement(
23523
- t2.variableDeclaration("const", [t2.variableDeclarator(mId)]),
23524
- markersId,
23525
- t2.blockStatement([
23526
- t2.expressionStatement(
23527
- t2.callExpression(t2.memberExpression(parentId, t2.identifier("insertBefore")), [
23528
- mId,
23529
- markerId
23530
- ])
23531
- )
23541
+ t2.expressionStatement(
23542
+ t2.callExpression(t2.memberExpression(parentId, t2.identifier("insertBefore")), [
23543
+ t2.memberExpression(bindingId, t2.identifier("marker")),
23544
+ markerId
23532
23545
  ])
23533
23546
  )
23534
23547
  );
@@ -23878,10 +23891,18 @@ function applySelectorHoist(callbackExpr, itemParamName, keyParamName, statement
23878
23891
  };
23879
23892
  const visitNode = (node) => {
23880
23893
  if (t2.isFunctionExpression(node) || t2.isArrowFunctionExpression(node)) {
23881
- if (node !== callbackExpr) return;
23894
+ if (node !== callbackExpr) {
23895
+ if (t2.isBlockStatement(node.body)) {
23896
+ node.body.body.forEach((stmt) => visitNode(stmt));
23897
+ } else if (t2.isExpression(node.body)) {
23898
+ visitNode(node.body);
23899
+ }
23900
+ return;
23901
+ }
23882
23902
  }
23883
- if (t2.isCallExpression(node) && t2.isIdentifier(node.callee)) {
23884
- if (node.callee.name === RUNTIME_ALIASES.bindClass) {
23903
+ if (t2.isCallExpression(node)) {
23904
+ const calleeName = t2.isIdentifier(node.callee) ? node.callee.name : t2.isMemberExpression(node.callee) && t2.isIdentifier(node.callee.property) ? node.callee.property.name : null;
23905
+ if (calleeName === RUNTIME_ALIASES.bindClass || calleeName === "bindClass") {
23885
23906
  const handler = node.arguments[1];
23886
23907
  if (handler && (t2.isArrowFunctionExpression(handler) || t2.isFunctionExpression(handler))) {
23887
23908
  rewriteInFunction(handler);
@@ -23964,20 +23985,26 @@ function applySelectorHoist(callbackExpr, itemParamName, keyParamName, statement
23964
23985
  }
23965
23986
  }
23966
23987
  }
23967
- function emitListChild(parentId, markerId, expr, statements, ctx) {
23988
+ function buildListCallExpression(expr, statements, ctx) {
23968
23989
  const { t: t2 } = ctx;
23969
- if (expr.kind !== "CallExpression" || expr.callee.kind !== "MemberExpression") {
23970
- return;
23990
+ if (expr.kind !== "CallExpression" && expr.kind !== "OptionalCallExpression") {
23991
+ return null;
23992
+ }
23993
+ if (expr.callee.kind !== "MemberExpression" && expr.callee.kind !== "OptionalMemberExpression") {
23994
+ return null;
23971
23995
  }
23972
- const arrayExpr = lowerDomExpression(expr.callee.object, ctx);
23996
+ if (expr.callee.property.kind !== "Identifier" || expr.callee.property.name !== "map") {
23997
+ return null;
23998
+ }
23999
+ const isOptional = expr.kind === "OptionalCallExpression" || expr.callee.kind === "OptionalMemberExpression" && expr.callee.optional;
24000
+ const arrayExprBase = lowerDomExpression(expr.callee.object, ctx);
24001
+ const arrayExpr = isOptional ? t2.logicalExpression("??", arrayExprBase, t2.arrayExpression([])) : arrayExprBase;
23973
24002
  const mapCallback = expr.arguments[0];
23974
24003
  if (!mapCallback) {
23975
24004
  throw new Error("map callback is required");
23976
24005
  }
23977
24006
  const keyExpr = extractKeyFromMapCallback(mapCallback);
23978
24007
  const isKeyed = !!keyExpr;
23979
- ctx.helpersUsed.add("onDestroy");
23980
- ctx.helpersUsed.add("toNodeArray");
23981
24008
  if (isKeyed) {
23982
24009
  ctx.helpersUsed.add("keyedList");
23983
24010
  } else {
@@ -24005,6 +24032,7 @@ function emitListChild(parentId, markerId, expr, statements, ctx) {
24005
24032
  ctx.inListRender = true;
24006
24033
  let callbackExpr = lowerExpression(mapCallback, ctx);
24007
24034
  ctx.inListRender = prevInListRender;
24035
+ const capturedKeyParamName = ctx.listKeyParamName;
24008
24036
  ctx.listKeyExpr = prevListKeyExpr;
24009
24037
  ctx.listItemParamName = prevListItemParamName;
24010
24038
  ctx.listKeyParamName = prevListKeyParamName;
@@ -24042,18 +24070,17 @@ function emitListChild(parentId, markerId, expr, statements, ctx) {
24042
24070
  }
24043
24071
  }
24044
24072
  }
24045
- const listId = genTemp(ctx, "list");
24046
24073
  if (isKeyed) {
24047
24074
  const itemParamName = t2.isArrowFunctionExpression(callbackExpr) || t2.isFunctionExpression(callbackExpr) ? t2.isIdentifier(callbackExpr.params[0]) ? callbackExpr.params[0].name : null : null;
24048
- const keyParamName = ctx.listKeyParamName ?? null;
24049
24075
  applySelectorHoist(
24050
24076
  callbackExpr,
24051
24077
  itemParamName,
24052
- keyParamName,
24078
+ capturedKeyParamName ?? null,
24053
24079
  statements,
24054
24080
  ctx
24055
24081
  );
24056
24082
  }
24083
+ let listCall;
24057
24084
  if (isKeyed && keyExpr) {
24058
24085
  let keyExprAst = lowerExpression(keyExpr, ctx);
24059
24086
  if (t2.isArrowFunctionExpression(callbackExpr) || t2.isFunctionExpression(callbackExpr)) {
@@ -24096,19 +24123,12 @@ function emitListChild(parentId, markerId, expr, statements, ctx) {
24096
24123
  }
24097
24124
  }
24098
24125
  statements.push(...hoistedStatements);
24099
- statements.push(
24100
- t2.variableDeclaration("const", [
24101
- t2.variableDeclarator(
24102
- listId,
24103
- t2.callExpression(t2.identifier(RUNTIME_ALIASES.keyedList), [
24104
- t2.arrowFunctionExpression([], arrayExpr),
24105
- keyFn,
24106
- callbackExpr,
24107
- t2.booleanLiteral(hasIndexParam)
24108
- ])
24109
- )
24110
- ])
24111
- );
24126
+ listCall = t2.callExpression(t2.identifier(RUNTIME_ALIASES.keyedList), [
24127
+ t2.arrowFunctionExpression([], arrayExpr),
24128
+ keyFn,
24129
+ callbackExpr,
24130
+ t2.booleanLiteral(hasIndexParam)
24131
+ ]);
24112
24132
  } else {
24113
24133
  statements.push(...hoistedStatements);
24114
24134
  const itemParamName = t2.isArrowFunctionExpression(callbackExpr) || t2.isFunctionExpression(callbackExpr) ? t2.isIdentifier(callbackExpr.params[0]) ? callbackExpr.params[0].name : "__item" : "__item";
@@ -24118,20 +24138,23 @@ function emitListChild(parentId, markerId, expr, statements, ctx) {
24118
24138
  [t2.identifier(itemParamName), t2.identifier(indexParamName)],
24119
24139
  t2.identifier(indexParamName)
24120
24140
  );
24121
- statements.push(
24122
- t2.variableDeclaration("const", [
24123
- t2.variableDeclarator(
24124
- listId,
24125
- t2.callExpression(t2.identifier(RUNTIME_ALIASES.keyedList), [
24126
- t2.arrowFunctionExpression([], arrayExpr),
24127
- keyFn,
24128
- callbackExpr,
24129
- t2.booleanLiteral(hasIndexParam)
24130
- ])
24131
- )
24132
- ])
24133
- );
24141
+ listCall = t2.callExpression(t2.identifier(RUNTIME_ALIASES.keyedList), [
24142
+ t2.arrowFunctionExpression([], arrayExpr),
24143
+ keyFn,
24144
+ callbackExpr,
24145
+ t2.booleanLiteral(hasIndexParam)
24146
+ ]);
24134
24147
  }
24148
+ return listCall;
24149
+ }
24150
+ function emitListChild(parentId, markerId, expr, statements, ctx) {
24151
+ const { t: t2 } = ctx;
24152
+ const listCall = buildListCallExpression(expr, statements, ctx);
24153
+ if (!listCall) return;
24154
+ ctx.helpersUsed.add("onDestroy");
24155
+ ctx.helpersUsed.add("toNodeArray");
24156
+ const listId = genTemp(ctx, "list");
24157
+ statements.push(t2.variableDeclaration("const", [t2.variableDeclarator(listId, listCall)]));
24135
24158
  const markersId = genTemp(ctx, "markers");
24136
24159
  statements.push(
24137
24160
  t2.variableDeclaration("const", [
@@ -24486,8 +24509,14 @@ function lowerTopLevelStatementBlock(statements, ctx, t2, name = "__module_segme
24486
24509
  ctx.hookReturnInfo.set(fn.name, info);
24487
24510
  }
24488
24511
  }
24489
- const lowered = generateRegionCode(fn, scopeResult, t2, ctx);
24490
- return { statements: lowered, aliases: aliasVars };
24512
+ const prevInModule = ctx.inModule;
24513
+ ctx.inModule = true;
24514
+ try {
24515
+ const lowered = generateRegionCode(fn, scopeResult, t2, ctx);
24516
+ return { statements: lowered, aliases: aliasVars };
24517
+ } finally {
24518
+ ctx.inModule = prevInModule;
24519
+ }
24491
24520
  }
24492
24521
  function transformControlFlowReturns(statements, ctx) {
24493
24522
  const { t: t2 } = ctx;
@@ -24588,12 +24617,14 @@ function lowerFunctionWithRegions(fn, ctx) {
24588
24617
  const prevWrapTracked = ctx.wrapTrackedExpressions;
24589
24618
  const prevIsComponent = ctx.isComponentFn;
24590
24619
  const prevHookResultVarMap = ctx.hookResultVarMap;
24620
+ const prevInModule = ctx.inModule;
24591
24621
  const scopedTracked = new Set(ctx.trackedVars);
24592
24622
  const shadowedParams = new Set(fn.params.map((p) => deSSAVarName(p.name)));
24593
24623
  fn.params.forEach((p) => scopedTracked.delete(deSSAVarName(p.name)));
24594
24624
  ctx.trackedVars = scopedTracked;
24595
24625
  const prevNeedsCtx = ctx.needsCtx;
24596
24626
  ctx.needsCtx = false;
24627
+ ctx.inModule = false;
24597
24628
  const prevShadowed = ctx.shadowedNames;
24598
24629
  const functionShadowed = new Set(prevShadowed ?? []);
24599
24630
  shadowedParams.forEach((n) => functionShadowed.add(n));
@@ -24878,9 +24909,9 @@ function lowerFunctionWithRegions(fn, ctx) {
24878
24909
  ctx.hookReturnInfo.set(fn.name, info);
24879
24910
  }
24880
24911
  }
24881
- if (debugEnabled("region") && fn.name === "Counter") {
24882
- console.log("Tracked vars for Counter", Array.from(ctx.trackedVars));
24883
- console.log("Memo vars for Counter", Array.from(ctx.memoVars));
24912
+ if (fn.name === "Counter") {
24913
+ debugLog("region", "Tracked vars for Counter", Array.from(ctx.trackedVars));
24914
+ debugLog("region", "Memo vars for Counter", Array.from(ctx.memoVars));
24884
24915
  }
24885
24916
  hookResultVars.forEach((varName) => {
24886
24917
  const hookName = ctx.hookResultVarMap?.get(varName);
@@ -24910,6 +24941,7 @@ function lowerFunctionWithRegions(fn, ctx) {
24910
24941
  ctx.noMemo = prevNoMemo;
24911
24942
  ctx.wrapTrackedExpressions = prevWrapTracked;
24912
24943
  ctx.hookResultVarMap = prevHookResultVarMap;
24944
+ ctx.inModule = prevInModule;
24913
24945
  return null;
24914
24946
  }
24915
24947
  let statements;
@@ -24998,6 +25030,7 @@ function lowerFunctionWithRegions(fn, ctx) {
24998
25030
  ctx.propsParamName = prevPropsParam;
24999
25031
  ctx.propAccessorDecls = prevPropAccessors;
25000
25032
  ctx.delegatedEventsUsed = prevDelegatedEventsUsed;
25033
+ ctx.inModule = prevInModule;
25001
25034
  return funcDecl;
25002
25035
  }
25003
25036
  function flattenRegions(regions) {
@@ -25641,28 +25674,49 @@ function createHIREntrypointVisitor(t2, options) {
25641
25674
  if (!init) return;
25642
25675
  if (isStateCall(init, t2)) {
25643
25676
  if (!fictImports.has("$state")) {
25644
- throw varPath.buildCodeFrameError('$state() must be imported from "fict"');
25677
+ throw varPath.buildCodeFrameError(
25678
+ `$state() must be imported from "fict".
25679
+
25680
+ Add this import at the top of your file:
25681
+ import { $state } from 'fict'`
25682
+ );
25645
25683
  }
25646
25684
  if (!t2.isIdentifier(varPath.node.id)) {
25647
25685
  throw varPath.buildCodeFrameError(
25648
- "Destructuring $state is not supported. Use a simple identifier."
25686
+ `Destructuring $state is not supported.
25687
+
25688
+ Instead of: const { a, b } = $state({ a: 1, b: 2 })
25689
+ Use: let state = $state({ a: 1, b: 2 })
25690
+ const { a, b } = state // read-only aliases
25691
+
25692
+ For deep reactivity, consider using $store from 'fict'.`
25649
25693
  );
25650
25694
  }
25651
25695
  const ownerComponent = varPath.getFunctionParent();
25652
25696
  if (!ownerComponent || !isComponentOrHookDefinition(ownerComponent)) {
25653
25697
  throw varPath.buildCodeFrameError(
25654
- "$state() must be declared inside a component or hook function body"
25698
+ `$state() must be declared inside a component or hook function body.
25699
+
25700
+ For module-level shared state, use one of these alternatives:
25701
+ \u2022 $store from 'fict' - for deep reactive objects
25702
+ \u2022 createSignal from 'fict/advanced' - for primitives`
25655
25703
  );
25656
25704
  }
25657
25705
  stateVars.add(varPath.node.id.name);
25658
25706
  if (isInsideLoop(varPath) || isInsideConditional(varPath)) {
25659
25707
  throw varPath.buildCodeFrameError(
25660
- "$state() cannot be declared inside loops or conditionals"
25708
+ `$state() cannot be declared inside loops or conditionals.
25709
+
25710
+ Signals must be created at the top level of components for stable identity.
25711
+ Move the $state() declaration before the loop/condition.`
25661
25712
  );
25662
25713
  }
25663
25714
  if (isInsideNestedFunction(varPath)) {
25664
25715
  throw varPath.buildCodeFrameError(
25665
- "$state() cannot be declared inside nested functions"
25716
+ `$state() cannot be declared inside nested functions.
25717
+
25718
+ Move the $state() declaration to the component's top level,
25719
+ or extract the nested logic into a custom hook (useXxx).`
25666
25720
  );
25667
25721
  }
25668
25722
  } else if (t2.isIdentifier(varPath.node.id)) {
@@ -25704,43 +25758,77 @@ function createHIREntrypointVisitor(t2, options) {
25704
25758
  const isVariableDeclarator = parentPath?.isVariableDeclarator() && parentPath.node.init === callPath.node;
25705
25759
  if (!isVariableDeclarator) {
25706
25760
  throw callPath.buildCodeFrameError(
25707
- "$state() must be assigned directly to a variable (e.g. let count = $state(0)). For object state, consider using $store from fict/plus."
25761
+ `$state() must be assigned directly to a variable.
25762
+
25763
+ Correct usage:
25764
+ let count = $state(0)
25765
+ let user = $state({ name: 'Alice' })
25766
+
25767
+ For object state with deep reactivity, consider:
25768
+ import { $store } from 'fict'
25769
+ const user = $store({ name: 'Alice', address: { city: 'NYC' } })`
25708
25770
  );
25709
25771
  }
25710
25772
  if (!t2.isIdentifier(parentPath.node.id)) {
25711
25773
  throw callPath.buildCodeFrameError(
25712
- "Destructuring $state is not supported. Use a simple identifier."
25774
+ `Destructuring $state is not supported.
25775
+
25776
+ Instead of: const { a, b } = $state({ a: 1, b: 2 })
25777
+ Use: let state = $state({ a: 1, b: 2 })
25778
+ const { a, b } = state // read-only aliases`
25713
25779
  );
25714
25780
  }
25715
25781
  const ownerComponent = callPath.getFunctionParent();
25716
25782
  if (!ownerComponent || !isComponentOrHookDefinition(ownerComponent)) {
25717
25783
  throw callPath.buildCodeFrameError(
25718
- "$state() must be declared inside a component or hook function body"
25784
+ `$state() must be declared inside a component or hook function body.
25785
+
25786
+ For module-level shared state, use one of these alternatives:
25787
+ \u2022 $store from 'fict' - for deep reactive objects
25788
+ \u2022 createSignal from 'fict/advanced' - for primitives`
25719
25789
  );
25720
25790
  }
25721
25791
  if (isInsideLoop(callPath) || isInsideConditional(callPath)) {
25722
25792
  throw callPath.buildCodeFrameError(
25723
- "$state() cannot be declared inside loops or conditionals"
25793
+ `$state() cannot be declared inside loops or conditionals.
25794
+
25795
+ Move the declaration to the top of your component.
25796
+ For dynamic collections, consider using $store with an array/object.`
25724
25797
  );
25725
25798
  }
25726
25799
  if (isInsideNestedFunction(callPath)) {
25727
25800
  throw callPath.buildCodeFrameError(
25728
- "$state() cannot be declared inside nested functions"
25801
+ `$state() cannot be declared inside nested functions.
25802
+
25803
+ Move the declaration to the component's top level,
25804
+ or extract the nested logic into a custom hook (useXxx).`
25729
25805
  );
25730
25806
  }
25731
25807
  }
25732
25808
  if (isEffectCall(callPath.node, t2)) {
25733
25809
  if (!fictImports.has("$effect")) {
25734
- throw callPath.buildCodeFrameError('$effect() must be imported from "fict"');
25810
+ throw callPath.buildCodeFrameError(
25811
+ `$effect() must be imported from "fict".
25812
+
25813
+ Add this import at the top of your file:
25814
+ import { $effect } from 'fict'`
25815
+ );
25735
25816
  }
25736
25817
  if (isInsideLoop(callPath) || isInsideConditional(callPath)) {
25737
25818
  throw callPath.buildCodeFrameError(
25738
- "$effect() cannot be called inside loops or conditionals"
25819
+ `$effect() cannot be called inside loops or conditionals.
25820
+
25821
+ Effects must be registered at the top level of components.
25822
+ For conditional effects, use a condition inside the effect body instead:
25823
+ $effect(() => { if (condition) { /* ... */ } })`
25739
25824
  );
25740
25825
  }
25741
25826
  if (isInsideNestedFunction(callPath)) {
25742
25827
  throw callPath.buildCodeFrameError(
25743
- "$effect() cannot be called inside nested functions"
25828
+ `$effect() cannot be called inside nested functions.
25829
+
25830
+ Move the effect to the component's top level,
25831
+ or extract the nested logic into a custom hook (useXxx).`
25744
25832
  );
25745
25833
  }
25746
25834
  }
@@ -25828,9 +25916,7 @@ function createHIREntrypointVisitor(t2, options) {
25828
25916
  });
25829
25917
  return usesState;
25830
25918
  };
25831
- if (process.env.FICT_DEBUG_ALIAS) {
25832
- console.log("[fict] alias check state vars", Array.from(stateVars));
25833
- }
25919
+ debugLog("alias", "state vars", Array.from(stateVars));
25834
25920
  path.traverse({
25835
25921
  Function: {
25836
25922
  enter() {
@@ -25843,9 +25929,7 @@ function createHIREntrypointVisitor(t2, options) {
25843
25929
  VariableDeclarator(varPath) {
25844
25930
  const aliasSet = currentAliasSet();
25845
25931
  if (aliasSet && t2.isIdentifier(varPath.node.id) && rhsUsesState(varPath.get("init"))) {
25846
- if (process.env.FICT_DEBUG_ALIAS) {
25847
- console.log("[fict] alias add from decl", varPath.node.id.name);
25848
- }
25932
+ debugLog("alias", "add from decl", varPath.node.id.name);
25849
25933
  aliasSet.add(varPath.node.id.name);
25850
25934
  }
25851
25935
  },
@@ -25856,16 +25940,12 @@ function createHIREntrypointVisitor(t2, options) {
25856
25940
  const targetName = assignPath.node.left.name;
25857
25941
  const rightPath = assignPath.get("right");
25858
25942
  if (rhsUsesState(rightPath)) {
25859
- if (process.env.FICT_DEBUG_ALIAS) {
25860
- console.log("[fict] alias add from assign", targetName);
25861
- }
25943
+ debugLog("alias", "add from assign", targetName);
25862
25944
  aliasSet.add(targetName);
25863
25945
  return;
25864
25946
  }
25865
25947
  if (aliasSet.has(targetName)) {
25866
- if (process.env.FICT_DEBUG_ALIAS) {
25867
- console.log("[fict] alias reassignment detected", targetName);
25868
- }
25948
+ debugLog("alias", "reassignment detected", targetName);
25869
25949
  throw assignPath.buildCodeFrameError(
25870
25950
  `Alias reassignment is not supported for "${targetName}"`
25871
25951
  );