@solidjs/babel-plugin 2.0.0-rc.10 → 2.0.0-rc.13

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.
@@ -7,4 +7,4 @@ src/shared/transform.ts -> src/dom/element.ts -> src/universal/element.ts -> src
7
7
  src/shared/transform.ts -> src/ssr/element.ts -> src/shared/transform.ts
8
8
  src/shared/transform.ts -> src/shared/component.ts -> src/shared/transform.ts
9
9
  src/shared/transform.ts -> src/shared/fragment.ts -> src/shared/transform.ts
10
- created index.js in 2.8s
10
+ created index.js in 3.4s
package/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # @solidjs/babel-plugin
2
2
 
3
+ ## 2.0.0-rc.13
4
+
5
+ ## 2.0.0-rc.12
6
+
7
+ ### Patch Changes
8
+
9
+ - ad1ecc5: `serverComponents` (SSR): keep attribute-slot positions bindable on intrinsic elements. A dynamic `class` or `style` now compiles to a whole-attribute `_$ssrElementAttribute` hole instead of a value inside the template's quotes, so a server component's `class={{ selected: filters.all }}` can mark the class name a client attribute slot owns; `ref`/`on*` positions compile, as before, to one guarded `_$ssrClaim` hole per element, which now emits `_s:on:*`/`_s:ref` markers for slot reads (the `_bnd` behavior-claim marker is gone). A spread element compiles its named `ref`/`on*` (`<button {...rest} onClick={row.go} ref={row.el} />`, wherever they sit relative to the spreads) to the same claim map — `{ click: expr, ref: [a, b] }`, duplicate refs merged, a duplicate handler last-wins as on the template path — keyed by the index of the source each attribute sits before (`<b {...a} onClick={go} {...b}>` → `{ 1: { click: go } }`) and handed to `_$ssrElement` as a seventh argument, a thunk the runtime reads only inside a server component's render, so plain SSR never evaluates the expressions (they used to drop with no marker and no finding); the runtime settles each handler position in source order, as the client's `spread`/`merge()` do (the last source that has the key owns it, `undefined` included). A `ref` beside a lone spread goes through the sources loop under the option (a dynamic spread call is thunked there, as it is beside any other attribute). The event name is always the lowercased `on*` remainder (`onClick` → `click`); `on:`/`oncapture:` are not 2.0 syntaxes and are no longer special-cased. Both compilers share the `attributeSlots` server-components fixture; plain SSR output is unchanged.
10
+ - 077c787: A `<textarea>` with a dynamic `value`/`defaultValue` no longer desyncs hydration ids for the siblings after it (#3691). The server folds the value into the textarea's text content, and that fold was taking the `_$scope` hydration-id reservation a dynamic child hole gets — but the client writes the value as a plain property effect that never allocates an id, so every component after the textarea hydrated one id off and its updates targeted detached DOM. The fold is now treated like the `innerHTML`/`textContent` redirects (#3015): opaque content, no id reservation, in both the Babel plugin and the native compiler.
11
+ - c71486d: DOM output: an element's `ref` now runs after its attributes and spread are applied, and still before its children. A ref's own attribute writes now survive client creation the way they already survived hydration (#3685).
12
+
13
+ ## 2.0.0-rc.11
14
+
3
15
  ## 2.0.0-rc.10
4
16
 
5
17
  ### Patch Changes
package/README.md CHANGED
@@ -253,7 +253,7 @@ Inline style attributes in templates when the value is a string or `Record<strin
253
253
  - Type: `boolean`
254
254
  - Default: `false`
255
255
 
256
- SSR-only: emit behavior-claim (`_bnd`) markers for `ref` / `on*` on intrinsic elements.
256
+ SSR-only: keep attribute-slot positions on intrinsic elements bindable — `ref` / `on*` compile to a guarded `ssrClaim` hole instead of dropping, and a dynamic `class` / `style` compiles to a whole-attribute `ssrElementAttribute` hole instead of a value inside template quotes.
257
257
 
258
258
  ### hoistProps
259
259
 
package/index.js CHANGED
@@ -1047,6 +1047,13 @@ isSSR)
1047
1047
  child.extra = { raw: text, rawValue: text };
1048
1048
  } else {
1049
1049
  child = t__namespace.jsxExpressionContainer(value);
1050
+ // The folded value is the textarea's text content on the server, but
1051
+ // the client writes it as a plain `value` property effect that never
1052
+ // allocates a hydration id. Flag it like the innerHTML/textContent
1053
+ // redirects (#3015) so the SSR child pipeline skips the `_$scope` id
1054
+ // reservation — otherwise the reservation shifts every keyed sibling
1055
+ // after the textarea by one id (#3691).
1056
+ child._childProperty = true;
1050
1057
  }
1051
1058
  path.node.children = [child];
1052
1059
  attr.remove();
@@ -2230,6 +2237,7 @@ results)
2230
2237
  hasChildren = path.node.children.length > 0,
2231
2238
  hasSpread = attributes.some((attribute) => t$1.isJSXSpreadAttribute(attribute.node)),
2232
2239
  config = getConfig(path);
2240
+ const refs = [];
2233
2241
 
2234
2242
  // preprocess spreads
2235
2243
  if (hasSpread) {
@@ -2709,7 +2717,7 @@ results)
2709
2717
  binding.kind === "const" || binding.kind === "module");
2710
2718
  if (!isConstant && t$1.isLVal(value.expression)) {
2711
2719
  const refIdentifier = path.scope.generateUidIdentifier("_ref$");
2712
- results.exprs.unshift(
2720
+ refs.push(
2713
2721
  t$1.variableDeclaration("var", [t$1.variableDeclarator(refIdentifier, value.expression)]),
2714
2722
  t$1.expressionStatement(
2715
2723
  t$1.conditionalExpression(
@@ -2738,7 +2746,7 @@ results)
2738
2746
  t$1.isFunction(value.expression) ||
2739
2747
  t$1.isArrayExpression(value.expression))
2740
2748
  {
2741
- results.exprs.unshift(
2749
+ refs.push(
2742
2750
  t$1.expressionStatement(
2743
2751
  t$1.callExpression(
2744
2752
  registerImportMethod(path, "ref", getRendererConfig(path, "dom").moduleName),
@@ -2748,7 +2756,7 @@ results)
2748
2756
  );
2749
2757
  } else {
2750
2758
  const refIdentifier = path.scope.generateUidIdentifier("_ref$");
2751
- results.exprs.unshift(
2759
+ refs.push(
2752
2760
  t$1.variableDeclaration("var", [t$1.variableDeclarator(refIdentifier, value.expression)]),
2753
2761
  t$1.expressionStatement(
2754
2762
  t$1.logicalExpression(
@@ -2978,6 +2986,9 @@ results)
2978
2986
  path.node.children.push(children);
2979
2987
  }
2980
2988
  if (spreadExpr) results.exprs.push(...(Array.isArray(spreadExpr) ? spreadExpr : [spreadExpr]));
2989
+ // Refs run after the element's attributes and spread are applied, so a ref's
2990
+ // own writes survive client creation the way they survive hydration.
2991
+ results.exprs.push(...refs);
2981
2992
 
2982
2993
  results.hasHydratableEvent = results.hasHydratableEvent || hasHydratableEvent;
2983
2994
  }
@@ -4852,10 +4863,12 @@ info)
4852
4863
  const hasChildren = path.node.children.length > 0,
4853
4864
  attributes = normalizeAttributes(path);
4854
4865
  let children;
4855
- // Server-components claims: ref/on* positions on server-rendered
4856
- // intrinsics collect here and emit as one guarded whole-attribute hole
4857
- // (` _bnd="..."` or "") after the loop. Evaluation is gated on the render
4858
- // context's claims flag so plain SSR never runs the expressions.
4866
+ // Server-components handler positions: ref/on* expressions on
4867
+ // server-rendered intrinsics collect here and emit as one guarded
4868
+ // whole-attribute hole after the loop, where `ssrClaim` turns attribute-slot
4869
+ // reads into `_s:on:*` / `_s:ref` markers (and drops server-local
4870
+ // functions). Evaluation is gated on the render context's claims flag so
4871
+ // plain SSR never runs the expressions.
4859
4872
  const claims = [];
4860
4873
 
4861
4874
  attributes.forEach((attribute) => {
@@ -4898,11 +4911,10 @@ info)
4898
4911
  }
4899
4912
  if (key.startsWith("prop:")) return;
4900
4913
  if (key.startsWith("on")) {
4901
- // Capture-phase variants can't ride delegation; v1 drops them as
4902
- // before. `on:x` keeps the raw name, `onXxx` lowercases — the same
4903
- // event-name derivation as the client runtime.
4904
- if (info.serverComponents && !key.startsWith("oncapture:")) {
4905
- const pos = key.startsWith("on:") ? key.slice(3) : key.slice(2).toLowerCase();
4914
+ // `onXxx` lowercases to the event name — the client runtime's own
4915
+ // derivation (`onClick` -> `click`); the position is bound under it.
4916
+ if (info.serverComponents) {
4917
+ const pos = key.slice(2).toLowerCase();
4906
4918
  if (pos) claims.push([pos, value.expression]);
4907
4919
  }
4908
4920
  return;
@@ -4938,6 +4950,30 @@ info)
4938
4950
  checkMember: true,
4939
4951
  checkTags: true
4940
4952
  });
4953
+ // Server components (principles §9.2.3): a dynamic `class`/`style`
4954
+ // is the one attribute shape the plain SSR output serializes INSIDE
4955
+ // template quotes (`class="${ssrClassName(x)}"`), where an attribute-slot
4956
+ // value read at that position — the whole value, or a name's
4957
+ // condition in object form — would be stringified instead of
4958
+ // bound. Under the option the whole attribute is a runtime hole,
4959
+ // `ssrElementAttribute("class", x)`, whose helper emits the same
4960
+ // bytes for a plain value and the position marker for a stand-in.
4961
+ // Object literals stay objects (no inlining) for the same reason.
4962
+ if (info.serverComponents && (key === "class" || key === "style")) {
4963
+ const attr = t.callExpression(registerImportMethod(path, "ssrElementAttribute"), [
4964
+ t.stringLiteral(key),
4965
+ value.expression]
4966
+ );
4967
+ results.template.push("");
4968
+ results.templateValues.push(
4969
+ isDynamicValue ?
4970
+ hoistExpression(path, results, t.arrowFunctionExpression([], attr), {
4971
+ group: true
4972
+ }) :
4973
+ attr
4974
+ );
4975
+ return;
4976
+ }
4941
4977
  let doEscape = true;
4942
4978
  let isBoolean =
4943
4979
  t.isBooleanLiteral(value) ||
@@ -5081,23 +5117,7 @@ info)
5081
5117
  }
5082
5118
  });
5083
5119
  if (claims.length) {
5084
- // Duplicate event keys were already last-wins-stripped above; `ref` is
5085
- // exempt from that pass (client semantics fire every ref), so multiple
5086
- // refs merge into an array value.
5087
- const byPos = new Map();
5088
- for (const [pos, expr] of claims) {
5089
- let list = byPos.get(pos);
5090
- if (!list) byPos.set(pos, list = []);
5091
- list.push(expr);
5092
- }
5093
- const map = t.objectExpression(
5094
- [...byPos].map(([pos, exprs]) =>
5095
- t.objectProperty(
5096
- t.stringLiteral(pos),
5097
- exprs.length === 1 ? exprs[0] : t.arrayExpression(exprs)
5098
- )
5099
- )
5100
- );
5120
+ const map = claimMap(claims);
5101
5121
  // `_$sharedConfig.context && _$sharedConfig.context.claims
5102
5122
  // ? _$ssrClaim({...}) : ""`
5103
5123
  // — the claims flag is only set inside a server component's render
@@ -5243,7 +5263,7 @@ results,
5243
5263
 
5244
5264
  function createElement(
5245
5265
  path,
5246
- { topLevel, hydratable })
5266
+ { topLevel, hydratable, serverComponents })
5247
5267
  {
5248
5268
  const tagName = getTagName(path.node),
5249
5269
  config = getConfig(path),
@@ -5296,6 +5316,21 @@ path,
5296
5316
  const node = attribute.node;
5297
5317
  return !(t.isJSXAttribute(node) && t.isJSXIdentifier(node.name) && node.name.name === "ref");
5298
5318
  });
5319
+ // Server components (principles §9.2.3): the named `ref`/`on*` attributes
5320
+ // of a spread element are handler positions like a template element's,
5321
+ // and compile to the same claim map — `{ click: expr, ref: [a, b] }`,
5322
+ // duplicate refs merged, a duplicate handler last-wins as the template
5323
+ // path strips it — keyed by the index of the source each attribute sits
5324
+ // before (`<b {...a} onClick={go} {...b}>` → `{ 1: { click: go } }`, the
5325
+ // spreads being sources 0 and 2), and handed to `ssrElement` as a thunk
5326
+ // it reads only inside a server component's render (the gate the template
5327
+ // path's `ssrClaim` guard reads), so plain SSR never evaluates the
5328
+ // expressions. The runtime settles each handler position in source order,
5329
+ // as the client's `spread(el, [a, { onClick: go }, b])` does — a spread at
5330
+ // that index or later that HAS the key owns it — and merges every ref.
5331
+ // Plain SSR output is unchanged (dropped, as a server element has no
5332
+ // handlers to run).
5333
+ const claims = [];
5299
5334
 
5300
5335
  let props;
5301
5336
  // Attributes written AFTER the last spread are markup, not a source: no
@@ -5316,7 +5351,13 @@ path,
5316
5351
  // keys.
5317
5352
  const tail = [];
5318
5353
  const skipKeys = [];
5319
- if (propAttributes.length === 1 && t.isJSXSpreadAttribute(propAttributes[0].node)) {
5354
+ if (
5355
+ propAttributes.length === 1 &&
5356
+ t.isJSXSpreadAttribute(propAttributes[0].node) &&
5357
+ // A `ref` beside the lone spread is a claim under `serverComponents`;
5358
+ // the loop below places it.
5359
+ !(serverComponents && attributes.length > 1))
5360
+ {
5320
5361
  props = [propAttributes[0].node.argument];
5321
5362
  } else {
5322
5363
  props = [];
@@ -5362,8 +5403,30 @@ path,
5362
5403
  node.name.name;
5363
5404
 
5364
5405
  if (hasChildren && key === "children") return;
5365
- if (key === "ref") return;
5366
- if (key.startsWith("prop:") || key.startsWith("on")) return;
5406
+ if (key === "ref" || key.startsWith("on")) {
5407
+ if (serverComponents && t.isJSXExpressionContainer(value)) {
5408
+ const expression = value.expression;
5409
+ const pos = key === "ref" ? "ref" : key.slice(2).toLowerCase();
5410
+ if (
5411
+ pos &&
5412
+ !(
5413
+ t.isJSXEmptyExpression(expression) ||
5414
+ t.isStringLiteral(expression) ||
5415
+ t.isNumericLiteral(expression) ||
5416
+ t.isBooleanLiteral(expression)))
5417
+
5418
+ {
5419
+ // The index of the source this attribute sits before: the
5420
+ // sources pushed so far, plus the running literal if it will
5421
+ // be pushed ahead of the next spread. The literal never
5422
+ // carries a handler key, so a spread's index is either below
5423
+ // this or at/after it — never ambiguous.
5424
+ claims.push([props.length + (runningObject.length ? 1 : 0), pos, expression]);
5425
+ }
5426
+ }
5427
+ return;
5428
+ }
5429
+ if (key.startsWith("prop:")) return;
5367
5430
  if (i > lastSpread) {
5368
5431
  const part = tailAttribute(path, tagName, key, node);
5369
5432
  if (part !== undefined) {
@@ -5440,10 +5503,65 @@ path,
5440
5503
  }
5441
5504
  args.push(registerSkip(path, skipKeys), markup);
5442
5505
  }
5506
+ if (claims.length) {
5507
+ if (!skipKeys.length) args.push(t.identifier("undefined"), t.identifier("undefined"));
5508
+ args.push(t.arrowFunctionExpression([], claimSegments(claims)));
5509
+ }
5443
5510
  const exprs = [t.callExpression(registerImportMethod(path, "ssrElement"), args)];
5444
5511
  return { exprs, template: "", declarations: [], dynamics: [], spreadElement: true };
5445
5512
  }
5446
5513
 
5514
+ /**
5515
+ * A spread element's claim map by source index (`createElement`): `{ 1: {
5516
+ * click: go }, 3: { ref: el } }`. A handler position keeps its LAST named
5517
+ * attribute only — the template path's duplicate strip, applied here to the
5518
+ * claims (a later attribute wins whatever sits between, so the earlier one
5519
+ * is never read on either side); refs merge within a segment as `claimMap`
5520
+ * merges them, and across segments at the runtime.
5521
+ */
5522
+ function claimSegments(
5523
+ claims)
5524
+ {
5525
+ const lastHandler = new Map();
5526
+ claims.forEach(([, pos], i) => {
5527
+ if (pos !== "ref") lastHandler.set(pos, i);
5528
+ });
5529
+ const segments = new Map();
5530
+ claims.forEach(([index, pos, expr], i) => {
5531
+ if (pos !== "ref" && lastHandler.get(pos) !== i) return;
5532
+ let list = segments.get(index);
5533
+ if (!list) segments.set(index, list = []);
5534
+ list.push([pos, expr]);
5535
+ });
5536
+ return t.objectExpression(
5537
+ [...segments].map(([index, list]) => t.objectProperty(t.numericLiteral(index), claimMap(list)))
5538
+ );
5539
+ }
5540
+
5541
+ /**
5542
+ * The compiled claim map of an element's handler positions, `{ click: expr,
5543
+ * ref: [a, b] }`: duplicate event keys were last-wins-stripped by the
5544
+ * template path's duplicate strip (or `claimSegments`); `ref` is exempt from
5545
+ * that pass (client semantics fire every ref), so multiple refs merge into
5546
+ * an array value.
5547
+ */
5548
+ function claimMap(claims) {
5549
+ const byPos = new Map();
5550
+ for (const [pos, expr] of claims) {
5551
+ let list = byPos.get(pos);
5552
+ if (!list) byPos.set(pos, list = []);
5553
+ list.push(expr);
5554
+ }
5555
+ return t.objectExpression(
5556
+ [...byPos].map(([pos, exprs]) =>
5557
+ t.objectProperty(
5558
+ t.stringLiteral(pos),
5559
+ exprs.length === 1 ? exprs[0] : t.arrayExpression(exprs)
5560
+ )
5561
+ )
5562
+ );
5563
+ }
5564
+
5447
5565
  /**
5448
5566
  * What an attribute after an element's last spread contributes to
5449
5567
  * `ssrElement`'s attribute string. A static is written as the template path
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@solidjs/babel-plugin",
3
3
  "description": "Babel compiler plugin for Solid templates",
4
- "version": "2.0.0-rc.10",
4
+ "version": "2.0.0-rc.13",
5
5
  "author": "Ryan Carniato",
6
6
  "license": "MIT",
7
7
  "repository": {