@barefootjs/vite 0.31.6 → 0.31.7

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 (2) hide show
  1. package/dist/index.js +96 -9
  2. package/package.json +6 -6
package/dist/index.js CHANGED
@@ -3916,6 +3916,11 @@ function irToComponentTemplateWithOpts(node, opts) {
3916
3916
  case "expression": {
3917
3917
  if (node.expr === "null" || node.expr === "undefined")
3918
3918
  return "";
3919
+ if (node.clientOnly && node.slotId) {
3920
+ if (node.markerless)
3921
+ return "";
3922
+ return `<!--bf:${node.slotId}--><!--/-->`;
3923
+ }
3919
3924
  const wrapped = transformExpr(node.expr, node.templateExpr);
3920
3925
  const value = node.joinArrayChild ? `Array.isArray(${wrapped}) ? ${wrapped}.join('') : (${wrapped} ?? '')` : wrapped;
3921
3926
  if (node.slotId) {
@@ -5035,6 +5040,7 @@ var ErrorCodes = {
5035
5040
  COMPONENT_REQUIRED_PROP_MISSING: "BF046",
5036
5041
  JSX_BRANCH_LOCAL_IN_CALLBACK: "BF047",
5037
5042
  SIBLING_COMPONENT_NOT_COMPILED: "BF048",
5043
+ RICH_TYPE_PROP_NOT_HYDRATABLE: "BF049",
5038
5044
  SHARED_PROGRAM_REQUIRED: "BF050",
5039
5045
  WRONG_PACKAGE_IMPORT: "BF051",
5040
5046
  BUILTIN_REQUIRES_IMPORT: "BF054",
@@ -5065,6 +5071,7 @@ var errorMessages = {
5065
5071
  [ErrorCodes.COMPONENT_REQUIRED_PROP_MISSING]: "Built-in component is missing a required prop.",
5066
5072
  [ErrorCodes.JSX_BRANCH_LOCAL_IN_CALLBACK]: "JSX-typed local declared inside an `if`-block cannot be referenced from a callback body (ref / event handler). " + "Render it as a child instead: `<div ref={...}>{local}</div>`.",
5067
5073
  [ErrorCodes.SIBLING_COMPONENT_NOT_COMPILED]: "Referenced component did not compile to a template, so this reference would throw " + "`ReferenceError` at render time. Multi-return JSX dispatch (a `switch` or `if`/`else` " + "chain across multiple JSX-returning branches) cannot compile as a component in a " + `'use client' file. Extract it to a separate non-"use client" file (where it is preserved ` + "verbatim, see #932), or rewrite it as a single-return ternary/conditional chain so the " + "component pipeline can compile it.",
5074
+ [ErrorCodes.RICH_TYPE_PROP_NOT_HYDRATABLE]: "Rich-typed prop cannot cross the bf-p hydration boundary as JSON.",
5068
5075
  [ErrorCodes.SHARED_PROGRAM_REQUIRED]: "Shared ts.Program required for type-based reactivity classification. This source imports a Reactive<T>-branded library (e.g. @barefootjs/form) whose getters cannot be classified by regex alone. Pass `options.program` (built via `createProgramForCorpus`) so the analyzer can resolve the brand through the TypeChecker.",
5069
5076
  [ErrorCodes.WRONG_PACKAGE_IMPORT]: "Import from wrong package.",
5070
5077
  [ErrorCodes.BUILTIN_REQUIRES_IMPORT]: "Built-in <Async> / <Region> must be imported from '@barefootjs/client'. " + "The compiler recognises these tags by their import (not by tag name), " + "so an unimported tag with this name is treated as an undeclared component.",
@@ -5182,6 +5189,8 @@ var HOST_RICH_TYPE_NAMES = new Set([
5182
5189
  "BigInt",
5183
5190
  "Function"
5184
5191
  ]);
5192
+ var JSON_REVIVABLE_RICH_TYPE_NAMES = new Set(["Date", "URL"]);
5193
+ var JSON_UNSAFE_RICH_TYPE_NAMES = new Set([...HOST_RICH_TYPE_NAMES].filter((n) => !JSON_REVIVABLE_RICH_TYPE_NAMES.has(n)));
5185
5194
  function baseTypeName(raw) {
5186
5195
  const idx = raw.indexOf("<");
5187
5196
  return (idx === -1 ? raw : raw.slice(0, idx)).trim();
@@ -5214,6 +5223,21 @@ function lookupProperty(objType, propName, meta) {
5214
5223
  const prop = deref.properties?.find((p) => p.name === propName);
5215
5224
  return prop ? stripUnion(prop.type) : null;
5216
5225
  }
5226
+ function resolvePropDeclaredType(propName, meta) {
5227
+ return lookupProperty(meta.propsType, propName, meta);
5228
+ }
5229
+ function jsonUnsafeTypeName(type) {
5230
+ if (!type)
5231
+ return null;
5232
+ if (type.kind === "interface") {
5233
+ const name = baseTypeName(type.raw);
5234
+ return JSON_UNSAFE_RICH_TYPE_NAMES.has(name) ? name : null;
5235
+ }
5236
+ if (type.kind === "unknown" && (type.raw === "bigint" || type.raw === "symbol")) {
5237
+ return type.raw;
5238
+ }
5239
+ return null;
5240
+ }
5217
5241
  function resolveReceiverType(expr, meta, bindings) {
5218
5242
  if (expr.kind === "identifier") {
5219
5243
  if (bindings.has(expr.name))
@@ -14977,6 +15001,9 @@ function buildReferencesGraph(ctx, irRoot) {
14977
15001
  addExprEdges(ROOT_SOURCE, event.handler, "init-body");
14978
15002
  }
14979
15003
  }
15004
+ for (const elem of ctx.clientOnlyElements) {
15005
+ addExprEdges(ROOT_SOURCE, elem.expression, "init-body");
15006
+ }
14980
15007
  for (const elem of ctx.loopElements) {
14981
15008
  addExprEdges(ROOT_SOURCE, elem.array, "template-closure");
14982
15009
  addTemplateEdges(ROOT_SOURCE, elem.template, "template-closure");
@@ -19393,9 +19420,15 @@ function lowerDateCallsInReactiveExpr(expr, matcher) {
19393
19420
  }
19394
19421
  return restore(result);
19395
19422
  }
19396
- function emitDynamicTextUpdates(lines, ctx) {
19397
- const dateLoweringMatcher = getReactiveDateLoweringMatcher(ctx);
19423
+ function makeCataloguedCallLowerer(ctx) {
19424
+ const dateMatcher = getReactiveDateLoweringMatcher(ctx);
19398
19425
  const toLocaleMatcher = getReactiveToLocaleMatcher(ctx);
19426
+ if (!dateMatcher && !toLocaleMatcher)
19427
+ return (expr) => expr;
19428
+ return (expr) => lowerToLocaleCallsInReactiveExpr(lowerDateCallsInReactiveExpr(expr, dateMatcher), toLocaleMatcher);
19429
+ }
19430
+ function emitDynamicTextUpdates(lines, ctx) {
19431
+ const lower = makeCataloguedCallLowerer(ctx);
19399
19432
  const byExpression = new Map;
19400
19433
  for (const elem of ctx.dynamicElements) {
19401
19434
  const key = elem.expression;
@@ -19405,7 +19438,7 @@ function emitDynamicTextUpdates(lines, ctx) {
19405
19438
  byExpression.get(key).push(elem);
19406
19439
  }
19407
19440
  for (const [rawExpr, elems] of byExpression) {
19408
- const expr = lowerToLocaleCallsInReactiveExpr(lowerDateCallsInReactiveExpr(rawExpr, dateLoweringMatcher), toLocaleMatcher);
19441
+ const expr = lower(rawExpr);
19409
19442
  const conditionalElems = elems.filter((e) => e.insideConditional);
19410
19443
  const normalElems = elems.filter((e) => !e.insideConditional);
19411
19444
  if (normalElems.length > 0 || conditionalElems.length > 0) {
@@ -19442,19 +19475,21 @@ function emitDynamicTextUpdates(lines, ctx) {
19442
19475
  }
19443
19476
  }
19444
19477
  function emitClientOnlyExpressions(lines, ctx) {
19478
+ const lower = makeCataloguedCallLowerer(ctx);
19445
19479
  for (const elem of ctx.clientOnlyElements) {
19446
19480
  const slots = elem.elidedPath ? [{ id: elem.slotId, kind: "text", path: elem.elidedPath, markerless: true }] : [{ id: elem.slotId, kind: "text", path: [] }];
19447
19481
  const writer = claimWriterVarName(slots, varSlotId);
19448
19482
  lines.push(` // @client: ${elem.slotId}`);
19449
19483
  lines.push(` { const ${writer} = lazySlots(__scope, ${claimPlanLiteral(slots)})`);
19450
19484
  lines.push(` createEffect(() => {`);
19451
- lines.push(` ${writer}('${elem.slotId}', ${elem.expression})`);
19485
+ lines.push(` ${writer}('${elem.slotId}', ${lower(elem.expression)})`);
19452
19486
  lines.push(` }${bindingIdArg(ctx, elem.slotId)}) }`);
19453
19487
  lines.push("");
19454
19488
  }
19455
19489
  }
19456
19490
  function emitReactiveAttributeUpdates(lines, ctx) {
19457
19491
  if (ctx.reactiveAttrs.length > 0) {
19492
+ const lower = makeCataloguedCallLowerer(ctx);
19458
19493
  const attrsBySlot = new Map;
19459
19494
  for (const attr of ctx.reactiveAttrs) {
19460
19495
  if (!attrsBySlot.has(attr.slotId)) {
@@ -19467,7 +19502,7 @@ function emitReactiveAttributeUpdates(lines, ctx) {
19467
19502
  lines.push(` createEffect(() => {`);
19468
19503
  lines.push(` if (_${v}) {`);
19469
19504
  for (const attr of attrs) {
19470
- const expression = rewriteDestructuredPropsInExpr(attr.expression, ctx);
19505
+ const expression = rewriteDestructuredPropsInExpr(lower(attr.expression), ctx);
19471
19506
  for (const stmt of emitAttrUpdate(`_${v}`, attr.attrName, expression, attr)) {
19472
19507
  lines.push(` ${stmt}`);
19473
19508
  }
@@ -22893,6 +22928,38 @@ function checkRichTypeMethodCalls(root, metadata, errors) {
22893
22928
  const seen = new Set;
22894
22929
  walkNode2(root, metadata, EMPTY_BINDINGS2, matchers, errors, seen);
22895
22930
  }
22931
+ function checkRichTypePropSerialization(root, metadata, errors, declLoc) {
22932
+ if (!metadata.propsType || !metadata.clientAnalysis?.needsInit)
22933
+ return;
22934
+ const usedProps = new Set(metadata.clientAnalysis.usedProps);
22935
+ const loc = declLoc ?? root.loc;
22936
+ for (const param of metadata.propsParams) {
22937
+ if (param.isRest || param.name.startsWith("on") || param.name.startsWith("__"))
22938
+ continue;
22939
+ if (!usedProps.has(param.name))
22940
+ continue;
22941
+ const declared = resolvePropDeclaredType(param.sourceName ?? param.name, metadata);
22942
+ const typeName = jsonUnsafeTypeName(declared);
22943
+ if (!typeName)
22944
+ continue;
22945
+ if (metadata.typeDefinitions.some((d) => d.name === typeName))
22946
+ continue;
22947
+ pushPropSerializationDiagnostic(errors, loc, param.name, typeName, declared.raw);
22948
+ }
22949
+ }
22950
+ function pushPropSerializationDiagnostic(errors, loc, propName, typeName, declaredRaw) {
22951
+ const consequence = typeName === "bigint" || typeName === "BigInt" ? "JSON.stringify throws at SSR render ('Do not know how to serialize a BigInt'), failing the whole page" : typeName === "symbol" || typeName === "Symbol" || typeName === "Function" ? "JSON.stringify drops the value entirely, so the client reads undefined at hydrate" : "it serializes de-riched (e.g. a Map or Set becomes {} with every entry silently dropped), so the client hydrates against corrupt data";
22952
+ errors.push({
22953
+ code: ErrorCodes.RICH_TYPE_PROP_NOT_HYDRATABLE,
22954
+ severity: "error",
22955
+ message: `Prop '${propName}' is typed '${declaredRaw}' and is read by this component's own client code, so it must cross the bf-p hydration boundary as JSON — and ${typeName} cannot: ${consequence}.`,
22956
+ loc,
22957
+ suggestion: {
22958
+ message: "Pre-compute a JSON-serializable value server-side — a string, number, boolean, array, or plain object " + "(e.g. pass [...map.entries()] and rebuild the Map client-side where needed) — and pass that as the prop " + "instead. /* @client */ is NOT an escape here: the prop still crosses the bf-p boundary as JSON and arrives " + "de-riched (#2636).",
22959
+ escape: [{ kind: "prop-precompute" }]
22960
+ }
22961
+ });
22962
+ }
22896
22963
  function isLoweringClaimed(matchers, callee, args) {
22897
22964
  return matchers.some((m) => m(callee, args) !== null);
22898
22965
  }
@@ -22909,21 +22976,39 @@ function receiverRootIsProp(expr, bindings) {
22909
22976
  root = root.object;
22910
22977
  return root.kind === "identifier" && !bindings.has(root.name);
22911
22978
  }
22979
+ function buildSuggestion(method, receiverPath, receiver, typeName) {
22980
+ const revivalExpr = receiverPath === "<expression>" ? `wrapping the receiver in new ${typeName}(...) before calling .${method}()` : `{/* @client */ new ${typeName}(${receiverPath}).${method}(...)}`;
22981
+ const revivalReason = `a bare /* @client */ crashes at hydrate because ${receiver} crosses the bf-p boundary as JSON and arrives as a plain string, not a ${typeName} instance (#2636)`;
22982
+ if (method === "toLocaleDateString" && typeName === "Date") {
22983
+ return {
22984
+ message: "Pass a literal locale and an explicit literal timeZone — .toLocaleDateString('ja-JP', { timeZone: 'UTC' }), a fixed '±HH:MM' offset, or a canonical IANA zone ID like 'Asia/Tokyo' (exact case; #2344) — to compile it to the format_date helper; for a runtime locale, resolve the pattern in your i18n layer and use formatDate(date, pattern, tz) from @barefootjs/client. " + `Alternatively pre-compute server-side, or evaluate client-only by ${revivalExpr} — ${revivalReason}.`,
22985
+ escape: [{ kind: "rewrite" }, { kind: "prop-precompute" }, { kind: "client-directive" }]
22986
+ };
22987
+ }
22988
+ if (JSON_REVIVABLE_RICH_TYPE_NAMES.has(typeName)) {
22989
+ return {
22990
+ message: `Pre-compute the value server-side and pass it as a prop. Alternatively, evaluate client-only by ${revivalExpr} — ${revivalReason}.`,
22991
+ escape: [{ kind: "prop-precompute" }, { kind: "client-directive" }]
22992
+ };
22993
+ }
22994
+ return {
22995
+ message: `Pre-compute the value server-side and pass the result — a string, number, array, or plain object — as a prop. ` + `/* @client */ is NOT a safe escape here: ${receiver} cannot cross the bf-p hydration boundary as JSON — it arrives de-riched ` + `(e.g. a Map or Set serializes to {}), so the call throws or silently returns the wrong result at hydrate (#2636).`,
22996
+ escape: [{ kind: "prop-precompute" }]
22997
+ };
22998
+ }
22912
22999
  function pushDiagnostic(errors, seen, loc, method, receiverPath, isProp, typeName) {
22913
23000
  const key = `${loc.start.line}:${loc.start.column}:${receiverPath}.${method}`;
22914
23001
  if (seen.has(key))
22915
23002
  return;
22916
23003
  seen.add(key);
22917
23004
  const receiver = isProp ? `prop '${receiverPath}'` : `'${receiverPath}'`;
22918
- const suggestion = method === "toLocaleDateString" && typeName === "Date" ? "Pass a literal locale and an explicit literal timeZone — .toLocaleDateString('ja-JP', { timeZone: 'UTC' }), a fixed '±HH:MM' offset, or a canonical IANA zone ID like 'Asia/Tokyo' (exact case; #2344) — to compile it to the format_date helper; for a runtime locale, resolve the pattern in your i18n layer and use formatDate(date, pattern, tz) from @barefootjs/client. Alternatively add /* @client */ or pre-compute server-side." : "Add /* @client */ to evaluate this expression on the client only, or pre-compute the value server-side.";
23005
+ const suggestion = buildSuggestion(method, receiverPath, receiver, typeName);
22919
23006
  errors.push({
22920
23007
  code: ErrorCodes.UNSUPPORTED_JSX_PATTERN,
22921
23008
  severity: "error",
22922
23009
  message: `Expression cannot be compiled to marked template: method '.${method}()' on ${receiver} of host type '${typeName}' has no catalogued lowering.`,
22923
23010
  loc,
22924
- suggestion: {
22925
- message: suggestion
22926
- }
23011
+ suggestion
22927
23012
  });
22928
23013
  }
22929
23014
  function checkExpr(expr, loc, meta, bindings, matchers, errors, seen) {
@@ -23168,6 +23253,7 @@ function compileMultipleComponents(source, filePath, componentNames, options) {
23168
23253
  };
23169
23254
  componentIR.metadata.clientAnalysis = analyzeClientNeeds(componentIR);
23170
23255
  checkRichTypeMethodCalls(componentIR.root, componentIR.metadata, errors);
23256
+ checkRichTypePropSerialization(componentIR.root, componentIR.metadata, errors, ctx.propsDestructuring?.loc);
23171
23257
  decideClientOnlyElision(componentIR.root);
23172
23258
  entries.push({ componentIR, ctx });
23173
23259
  }
@@ -23570,6 +23656,7 @@ function compileJSX(source, filePath, options) {
23570
23656
  };
23571
23657
  componentIR.metadata.clientAnalysis = analyzeClientNeeds(componentIR);
23572
23658
  checkRichTypeMethodCalls(componentIR.root, componentIR.metadata, errors);
23659
+ checkRichTypePropSerialization(componentIR.root, componentIR.metadata, errors, ctx.propsDestructuring?.loc);
23573
23660
  decideClientOnlyElision(componentIR.root);
23574
23661
  if (ctx.importedClientSignalNames.size > 0) {
23575
23662
  const sources = new Set;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@barefootjs/vite",
3
- "version": "0.31.6",
3
+ "version": "0.31.7",
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.6"
41
+ "@barefootjs/shared": "0.31.7"
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.6",
49
- "@barefootjs/go-template": "0.31.6",
50
- "@barefootjs/hono": "0.31.6",
51
- "@barefootjs/jsx": "0.31.6",
48
+ "@barefootjs/client": "0.31.7",
49
+ "@barefootjs/go-template": "0.31.7",
50
+ "@barefootjs/hono": "0.31.7",
51
+ "@barefootjs/jsx": "0.31.7",
52
52
  "typescript": "^5.0.0",
53
53
  "vite": "^6.0.0"
54
54
  }