@solidjs/babel-plugin 2.0.0-rc.3 → 2.0.0-rc.5
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/.turbo/turbo-build.log +1 -1
- package/CHANGELOG.md +28 -0
- package/index.js +104 -22
- package/package.json +1 -1
package/.turbo/turbo-build.log
CHANGED
|
@@ -7,4 +7,4 @@ src/shared/transform.ts -> src/ssr/element.ts -> src/shared/transform.ts
|
|
|
7
7
|
src/shared/transform.ts -> src/universal/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
|
-
[32mcreated [1mindex.js[22m in [1m2.
|
|
10
|
+
[32mcreated [1mindex.js[22m in [1m2.6s[22m[39m
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,33 @@
|
|
|
1
1
|
# @solidjs/babel-plugin
|
|
2
2
|
|
|
3
|
+
## 2.0.0-rc.5
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 320f1f5: Universal text is text (#3127). The DOM and SSR generators splice static
|
|
8
|
+
text into an HTML template that a parser later unescapes, so they escape
|
|
9
|
+
static values and keep JSX entities as written. The universal generator
|
|
10
|
+
hands strings straight to the host — `createTextNode`, `setProp` — with no
|
|
11
|
+
parser downstream, so the escaping rendered literally (`{"<b>"}` showed as
|
|
12
|
+
`<b>`) and entities never decoded (`<` showed as `<`), leaving no
|
|
13
|
+
spelling that produced a literal `<` in static text under a custom
|
|
14
|
+
renderer. Universal-rendered element children now pass static values
|
|
15
|
+
through unescaped and decode JSX entities in text and string attributes,
|
|
16
|
+
matching what component children and fragment text always did. The flag
|
|
17
|
+
rides on the element, not the config, so `generate: "dynamic"` decides per
|
|
18
|
+
renderer. Applied to both compilers; the attribute half closed ten pinned
|
|
19
|
+
cross-mode parity divergences between them. Reported with the fix mapped
|
|
20
|
+
out by @antoinevanwel.
|
|
21
|
+
- 5230666: Fix hydration ids drifting after a reactive lone spread (#3105). A lone spread now passes its accessor straight to `spread()` on the client — no `mergeProps`, no memo, no hydration id — matching the server's existing pass-through fast path. The runtime resolves a function props source inside its own tracking scopes.
|
|
22
|
+
- e27dc29: `validate` now fails the compile instead of warning when a template's markup would be restructured by the browser's HTML parser (#3099). Once the validator fires the emitted positional walk is guaranteed not to match the browser-built DOM (crashed or silently misplaced bindings; desynced hydration under SSR), so warn-and-emit shipped certain breakage with the diagnostic buried in server logs. Errors now point at the offending JSX (code frame in Babel, line:col in the native compiler). `validate: false` remains the opt-out.
|
|
23
|
+
|
|
24
|
+
## 2.0.0-rc.4
|
|
25
|
+
|
|
26
|
+
### Patch Changes
|
|
27
|
+
|
|
28
|
+
- 8d249c7: Patch-channel contract hardening from the stage-2 re-audit: ordinary `patchDriver` registrations unbind with their owner (entries no longer leak past unmount); merged transitions move their held-patch stash so no patch strands; the optimistic drain shares the normal drain's per-entry error isolation and boundary routing; accessor-bearing records are excluded at admission (scan-before-trust) and records that acquire accessors demote their patches to tracked effect fallbacks; writable projection arrays emit setter row ops at their fold-commit visibility moment; row-ops/slot registrations resolve chained backings to the ultimate owner; duplicate keys match occurrence-aware instead of first-wins; the production dev-token typo (`_DX_DEV_`) is fixed; `patchDriver: true` normalizes identically in Babel and the native loader, the option is typed in `TransformOptions`, and a `dom-patch` parity tier ratchets patch-mode output across both compilers (currently byte-identical on all fixtures).
|
|
29
|
+
- b534733: Scope-wrap bare function children in hydratable mode. A function child (`<main>{() => <App/>}</main>`, including via the `children` attribute) is a deferred hole at runtime, but it never classified as `dynamic`, so neither generate reserved an id scope for it — its owner ids drifted across async retry passes on the server and desynced from the client (the #2900 hydration-id-parity class). Both compilers now treat syntactic function expressions as scope-eligible alongside dynamic values, emitting `_$scope(...)` in the ssr generate and around the matching insert accessor in the dom generate. The native compiler also unwraps TS casts in the allocate-ids predicate, matching Babel (fixes a scope-emission desync for `{call() as any}` children).
|
|
30
|
+
|
|
3
31
|
## 2.0.0-rc.3
|
|
4
32
|
|
|
5
33
|
### Minor Changes
|
package/index.js
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
var SyntaxJSX = require('@babel/plugin-syntax-jsx');
|
|
4
4
|
var t$2 = require('@babel/types');
|
|
5
|
-
var helperModuleImports = require('@babel/helper-module-imports');
|
|
6
5
|
var htmlEntities = require('html-entities');
|
|
6
|
+
var helperModuleImports = require('@babel/helper-module-imports');
|
|
7
7
|
|
|
8
8
|
function _interopNamespaceDefault(e) {
|
|
9
9
|
var n = Object.create(null);
|
|
@@ -478,6 +478,9 @@ function canReturnHydratableChild(node) {
|
|
|
478
478
|
if (t__namespace.isTSNonNullExpression(node) || t__namespace.isTSAsExpression(node) || t__namespace.isTSSatisfiesExpression(node))
|
|
479
479
|
return canReturnHydratableChild(node.expression);
|
|
480
480
|
if (t__namespace.isJSXElement(node) || t__namespace.isJSXFragment(node) || t__namespace.isCallExpression(node)) return true;
|
|
481
|
+
// A function child is a deferred hole: whatever it returns renders inside
|
|
482
|
+
// the hole, so it can always mint hydratable content.
|
|
483
|
+
if (t__namespace.isFunction(node)) return true;
|
|
481
484
|
if (t__namespace.isMemberExpression(node) || t__namespace.isOptionalMemberExpression(node)) {
|
|
482
485
|
return !node.computed && t__namespace.isIdentifier(node.property, { name: "children" });
|
|
483
486
|
}
|
|
@@ -493,6 +496,23 @@ function canChildSlotAllocateIds(node) {
|
|
|
493
496
|
return node.isJSXExpressionContainer() && canReturnHydratableChild(node.node.expression);
|
|
494
497
|
}
|
|
495
498
|
|
|
499
|
+
// A syntactic function-expression hole (`{() => ...}` / `{function () {}}`,
|
|
500
|
+
// TS casts unwrapped). Function children never classify as `dynamic` — the
|
|
501
|
+
// literal reads nothing at template time — yet at runtime they are deferred
|
|
502
|
+
// holes exactly like call-shaped values, so the scope gates treat them as
|
|
503
|
+
// scope-eligible alongside `dynamic` (solidjs/solid#3068 follow-up).
|
|
504
|
+
function isFunctionShapedHole(node) {
|
|
505
|
+
if (!node.isJSXExpressionContainer()) return false;
|
|
506
|
+
let expression = node.node.expression;
|
|
507
|
+
while (
|
|
508
|
+
t__namespace.isTSNonNullExpression(expression) ||
|
|
509
|
+
t__namespace.isTSAsExpression(expression) ||
|
|
510
|
+
t__namespace.isTSSatisfiesExpression(expression))
|
|
511
|
+
|
|
512
|
+
expression = expression.expression;
|
|
513
|
+
return t__namespace.isFunction(expression);
|
|
514
|
+
}
|
|
515
|
+
|
|
496
516
|
function wrappedByText(list, startIndex) {
|
|
497
517
|
let index = startIndex,
|
|
498
518
|
wrapped;
|
|
@@ -2408,6 +2428,8 @@ config)
|
|
|
2408
2428
|
if (!transformed) return memo;
|
|
2409
2429
|
transformed.allocatesIds =
|
|
2410
2430
|
config.hydratable && canChildSlotAllocateIds(child);
|
|
2431
|
+
transformed.functionHole =
|
|
2432
|
+
isFunctionShapedHole(child);
|
|
2411
2433
|
const i = memo.length;
|
|
2412
2434
|
if (transformed.text && i && memo[i - 1].text) {
|
|
2413
2435
|
memo[i - 1].template =
|
|
@@ -2493,7 +2515,12 @@ config)
|
|
|
2493
2515
|
// allocate hydration ids get their own owner scope (insert makes the
|
|
2494
2516
|
// outer render effect non-transparent for tagged accessors). Keyed off
|
|
2495
2517
|
// `dynamic` so both generates decide identically for the same source.
|
|
2496
|
-
|
|
2518
|
+
// Function children never classify as dynamic but are deferred holes
|
|
2519
|
+
// all the same, so they take the scope too.
|
|
2520
|
+
if (
|
|
2521
|
+
child.allocatesIds && (
|
|
2522
|
+
child.dynamic || child.functionHole))
|
|
2523
|
+
{
|
|
2497
2524
|
let expr = child.exprs[0];
|
|
2498
2525
|
// The shared transform simplifies `{sig()}` to the bare getter `sig`;
|
|
2499
2526
|
// rewrap so tagging the scope doesn't mutate the user's function.
|
|
@@ -2698,7 +2725,6 @@ attributes,
|
|
|
2698
2725
|
const filteredAttributes = [];
|
|
2699
2726
|
const spreadArgs = [];
|
|
2700
2727
|
let runningObject = [];
|
|
2701
|
-
let dynamicSpread = false;
|
|
2702
2728
|
attributes.forEach((attribute) => {
|
|
2703
2729
|
const node = attribute.node;
|
|
2704
2730
|
const key =
|
|
@@ -2717,10 +2743,9 @@ attributes,
|
|
|
2717
2743
|
runningObject = [];
|
|
2718
2744
|
}
|
|
2719
2745
|
|
|
2720
|
-
const s =
|
|
2721
|
-
isDynamic(attribute.get("argument"), {
|
|
2746
|
+
const s = isDynamic(attribute.get("argument"), {
|
|
2722
2747
|
checkMember: true
|
|
2723
|
-
})
|
|
2748
|
+
}) ?
|
|
2724
2749
|
inlineCallExpression(node.argument) :
|
|
2725
2750
|
node.argument;
|
|
2726
2751
|
|
|
@@ -2769,8 +2794,12 @@ attributes,
|
|
|
2769
2794
|
spreadArgs.push(t$1.objectExpression(runningObject));
|
|
2770
2795
|
}
|
|
2771
2796
|
|
|
2797
|
+
// A lone spread — reactive included — passes straight through: spread()
|
|
2798
|
+
// resolves a function source inside its own tracking scopes, and merging
|
|
2799
|
+
// one source would mint a memo that consumes a hydration id the SSR fast
|
|
2800
|
+
// path never allocates (#3105).
|
|
2772
2801
|
const props =
|
|
2773
|
-
spreadArgs.length === 1
|
|
2802
|
+
spreadArgs.length === 1 ?
|
|
2774
2803
|
spreadArgs[0] :
|
|
2775
2804
|
t$1.callExpression(registerImportMethod(path, "mergeProps"), spreadArgs);
|
|
2776
2805
|
|
|
@@ -3064,7 +3093,11 @@ function registerTemplate(path, results) {
|
|
|
3064
3093
|
templateWithClosingTags: results.templateWithClosingTags,
|
|
3065
3094
|
isImportNode: results.isImportNode,
|
|
3066
3095
|
isWrapped: results.isWrapped,
|
|
3067
|
-
renderer: "dom"
|
|
3096
|
+
renderer: "dom",
|
|
3097
|
+
// templates dedupe on markup, so the FIRST site carries the blame
|
|
3098
|
+
// for a validate failure (#3099) — good enough: every site with
|
|
3099
|
+
// this markup has the same problem
|
|
3100
|
+
path
|
|
3068
3101
|
});
|
|
3069
3102
|
}
|
|
3070
3103
|
}
|
|
@@ -4270,9 +4303,11 @@ results,
|
|
|
4270
4303
|
// Deferred holes that can allocate hydration ids evaluate under their
|
|
4271
4304
|
// own owner scope so retry timing can't skew sibling ids (mirrors the
|
|
4272
4305
|
// dom generate's `scope()` wrap around the matching insert accessor).
|
|
4273
|
-
// Keyed off `dynamic` so both generates decide identically.
|
|
4306
|
+
// Keyed off `dynamic` so both generates decide identically. Function
|
|
4307
|
+
// children never classify as dynamic but are deferred holes all the
|
|
4308
|
+
// same, so they take the scope too.
|
|
4274
4309
|
let expr = child.exprs[0];
|
|
4275
|
-
if (allocatesIds && child.dynamic) {
|
|
4310
|
+
if (allocatesIds && (child.dynamic || isFunctionShapedHole(node))) {
|
|
4276
4311
|
expr = t.callExpression(registerImportMethod(path, "scope"), [expr]);
|
|
4277
4312
|
}
|
|
4278
4313
|
|
|
@@ -4326,7 +4361,7 @@ path,
|
|
|
4326
4361
|
// dom generate scope()s the matching insert accessor regardless of
|
|
4327
4362
|
// spread, so skipping it here desyncs every hydration id that follows
|
|
4328
4363
|
// the hole.
|
|
4329
|
-
if (child.exprs.length && allocatesIds && child.dynamic) {
|
|
4364
|
+
if (child.exprs.length && allocatesIds && (child.dynamic || isFunctionShapedHole(path))) {
|
|
4330
4365
|
child.exprs[0] = t.callExpression(registerImportMethod(path, "scope"), [
|
|
4331
4366
|
child.exprs[0]]
|
|
4332
4367
|
);
|
|
@@ -4812,7 +4847,15 @@ results)
|
|
|
4812
4847
|
t__namespace.jsxExpressionContainer(value || t__namespace.booleanLiteral(true));
|
|
4813
4848
|
}
|
|
4814
4849
|
} else {
|
|
4815
|
-
addStaticAttr(
|
|
4850
|
+
addStaticAttr(
|
|
4851
|
+
attribute,
|
|
4852
|
+
results,
|
|
4853
|
+
initProps,
|
|
4854
|
+
elem,
|
|
4855
|
+
key,
|
|
4856
|
+
decodedAttrValue(value),
|
|
4857
|
+
hasSpread
|
|
4858
|
+
);
|
|
4816
4859
|
}
|
|
4817
4860
|
});
|
|
4818
4861
|
if (spreadExpr) results.exprs.push(spreadExpr);
|
|
@@ -4822,6 +4865,18 @@ results)
|
|
|
4822
4865
|
return initProps;
|
|
4823
4866
|
}
|
|
4824
4867
|
|
|
4868
|
+
// A JSX string attribute prints its `extra.raw` — the source text, entities
|
|
4869
|
+
// included — so `normal="Search…"` reached the host's setProp as the
|
|
4870
|
+
// six raw characters (#3127, same class as text children). The parser
|
|
4871
|
+
// already decoded the entities into `.value` (that decoded form is what the
|
|
4872
|
+
// DOM generator's template parser produces and what component props receive);
|
|
4873
|
+
// rebuild the literal so the host gets the decoded string. Only for the
|
|
4874
|
+
// attribute's own StringLiteral: an expression container's string is a JS
|
|
4875
|
+
// string, where `…` is six literal characters by JS semantics.
|
|
4876
|
+
function decodedAttrValue(value) {
|
|
4877
|
+
return t__namespace.isStringLiteral(value) ? t__namespace.stringLiteral(value.value) : value;
|
|
4878
|
+
}
|
|
4879
|
+
|
|
4825
4880
|
function addStaticAttr(
|
|
4826
4881
|
path,
|
|
4827
4882
|
results,
|
|
@@ -4864,8 +4919,11 @@ value,
|
|
|
4864
4919
|
function transformChildren(path, results) {
|
|
4865
4920
|
const filteredChildren = filterChildren(path.get("children")),
|
|
4866
4921
|
multi = checkLength(filteredChildren),
|
|
4867
|
-
childNodes = filteredChildren
|
|
4868
|
-
|
|
4922
|
+
childNodes = filteredChildren
|
|
4923
|
+
// `universal: true`: this text feeds the host's createTextNode, so the
|
|
4924
|
+
// shared text branch must not HTML-escape it and must decode entities
|
|
4925
|
+
// (#3127) — element-scoped because dynamic mode mixes renderers.
|
|
4926
|
+
.map((path) => transformNode(path, { universal: true })).
|
|
4869
4927
|
reduce((memo, child) => {
|
|
4870
4928
|
if (!child) return memo;
|
|
4871
4929
|
const i = memo.length;
|
|
@@ -5032,7 +5090,7 @@ attributes,
|
|
|
5032
5090
|
t__namespace.stringLiteral(key),
|
|
5033
5091
|
isContainer ?
|
|
5034
5092
|
node.value.expression :
|
|
5035
|
-
node.value || t__namespace.booleanLiteral(true)
|
|
5093
|
+
decodedAttrValue(node.value) || t__namespace.booleanLiteral(true)
|
|
5036
5094
|
)
|
|
5037
5095
|
);
|
|
5038
5096
|
}
|
|
@@ -5766,11 +5824,23 @@ info = {})
|
|
|
5766
5824
|
t__namespace.isJSXText(node) ||
|
|
5767
5825
|
(staticValue = getStaticExpression(path)) !== false)
|
|
5768
5826
|
{
|
|
5827
|
+
// Universal text is text (#3127): the DOM and SSR generators splice this
|
|
5828
|
+
// string into an HTML template that a parser later unescapes, so static
|
|
5829
|
+
// values are escaped and JSXText keeps its source entities. Universal
|
|
5830
|
+
// text goes straight to the host's `createTextNode` with no parser
|
|
5831
|
+
// downstream — what is written here is what renders — so static values
|
|
5832
|
+
// pass through unescaped and JSX entities decode, exactly as text
|
|
5833
|
+
// reaching a component child already did (component.ts, fragment.ts).
|
|
5834
|
+
// `info.universal` marks children of a universal-rendered element, which
|
|
5835
|
+
// in `generate: "dynamic"` is a per-element fact, not a config-wide one.
|
|
5836
|
+
const universal = info.universal || config.generate === "universal";
|
|
5769
5837
|
const text =
|
|
5770
5838
|
staticValue !== undefined ?
|
|
5771
|
-
info.doNotEscape ?
|
|
5839
|
+
info.doNotEscape || universal ?
|
|
5772
5840
|
String(staticValue) :
|
|
5773
5841
|
escapeHTML(String(staticValue)) :
|
|
5842
|
+
universal ?
|
|
5843
|
+
htmlEntities.decode(trimWhitespace(node.extra?.raw ?? "")) :
|
|
5774
5844
|
trimWhitespace(node.extra?.raw ?? "");
|
|
5775
5845
|
if (!text.length) return null;
|
|
5776
5846
|
const results = {
|
|
@@ -14445,13 +14515,22 @@ var postprocess = (path, state) => {
|
|
|
14445
14515
|
if (typeof html === "string") {
|
|
14446
14516
|
const result = isInvalidMarkup(html);
|
|
14447
14517
|
if (result) {
|
|
14518
|
+
// A compile ERROR, not a warning (#3099): once the validator has
|
|
14519
|
+
// fired, the emitted template is guaranteed not to match its own
|
|
14520
|
+
// positional walk — the browser rebuilds the DOM, and the walk
|
|
14521
|
+
// binds against nodes that moved (crash or silent wrong-node
|
|
14522
|
+
// bindings; under SSR the restructuring desyncs hydration too).
|
|
14523
|
+
// Warn-and-emit put this diagnostic in server stdout while the
|
|
14524
|
+
// browser failed with an unrelated-looking runtime crash. The
|
|
14525
|
+
// error throws from the template's registration site, so
|
|
14526
|
+
// bundlers surface it at the right file and line. `validate:
|
|
14527
|
+
// false` remains the opt-out.
|
|
14448
14528
|
const message =
|
|
14449
|
-
"
|
|
14450
|
-
|
|
14451
|
-
|
|
14452
|
-
|
|
14453
|
-
|
|
14454
|
-
// throw path.buildCodeFrameError();
|
|
14529
|
+
"The HTML provided is malformed and will yield unexpected output when evaluated by a browser.\n" +
|
|
14530
|
+
`User HTML:\n ${result.html}\n` +
|
|
14531
|
+
`Browser HTML:\n ${result.browser}\n` +
|
|
14532
|
+
`Original HTML:\n ${html}`;
|
|
14533
|
+
throw (template.path ?? path).buildCodeFrameError(message);
|
|
14455
14534
|
}
|
|
14456
14535
|
}
|
|
14457
14536
|
}
|
|
@@ -14526,6 +14605,9 @@ const config = {
|
|
|
14526
14605
|
var preprocess = (path, state) => {
|
|
14527
14606
|
const file = path.hub.file;
|
|
14528
14607
|
const merged = file.metadata.config = Object.assign({}, config, state.opts);
|
|
14608
|
+
// Boolean opt-in parity with the native loader: `patchDriver: true` means
|
|
14609
|
+
// the default import name (downstream code uses the value AS the name).
|
|
14610
|
+
if (merged.patchDriver === true) merged.patchDriver = "patchDriver";
|
|
14529
14611
|
const lib = merged.requireImportSource;
|
|
14530
14612
|
if (lib) {
|
|
14531
14613
|
const comments = file.ast.comments ?? [];
|