@dom-expressions/babel-plugin-jsx 0.50.0-next.16 → 0.50.0-next.17
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/CHANGELOG.md +14 -0
- package/index.js +81 -5
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# babel-plugin-jsx-dom-expressions
|
|
2
2
|
|
|
3
|
+
## 0.50.0-next.17
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 203c9d5: Fix two option-handling bugs surfaced by the compiler parity sweep:
|
|
8
|
+
- `memoWrapper: false` no longer crashes when a conditional or logical expression is transformed (`transformCondition` registered an import under the falsy wrapper name). Conditions now compile memo-less: hoisted conditions keep a plain `var _c$ = () => !!cond` thunk and inline conditions collapse to an immediately-invoked thunk.
|
|
9
|
+
- `inlineStyles: false` no longer silently drops a literal `style` on a child element whose position otherwise allocates no element reference (e.g. `<svg><rect style="fill:red"/><g/></svg>` lost the style entirely). `detectExpressions` now accounts for the style-to-IIFE rewrite, so the element gets a reference and the style compiles to the expected effect.
|
|
10
|
+
|
|
11
|
+
The `effectWrapper`/`memoWrapper` config types now admit `false` alongside the import-name string.
|
|
12
|
+
|
|
13
|
+
- bb7b2fd: Fix omitLastClosingTag corrupting templates when per-slot insertion markers follow the last static element. An element trailed by two or more dynamic slots now keeps its closing tag, so the trailing `<!>` placeholders parse as its siblings instead of being swallowed as children of the still-open element (which crashed the template walk with "Cannot read properties of null (reading 'nextSibling')").
|
|
14
|
+
- 4686501: Fix hydration id drift for spread elements with dynamic children. The ssr generate's spread-element path (`ssrElement`) never applied the hole id `scope()` wrap that `transformChildren` applies on the template path — while the dom generate scope-wraps the matching insert accessor regardless of spread. For a shape like `<a {...props}>{children()}</a>`, the client reserved one hydration id for the hole and the server did not, so every hydration key allocated after the hole drifted and the following siblings were left unclaimed (duplicated DOM, "unclaimed server-rendered node" warnings). `createElement` now wraps dynamic, id-allocating children holes in `scope()` exactly like the template path.
|
|
15
|
+
- 241ff76: Fix a spread element with dynamic props being left unclaimed on hydration. `mergeProps` with a function source creates a memo, which consumes a hydration child id. The ssr generate evaluated `mergeProps(...)` in `ssrElement`'s argument position — before the element's own hydration key was allocated — while the client claims the element (`getNextElement`) before applying the spread. The element's id shifted by one on the server and the client re-created it instead of claiming (later siblings re-synced, hiding the drift; a `<title>` rendered this way duplicated on every hydration). The ssr generate now defers the merge behind a thunk when hydratable and `ssrElement` allocates the hydration key before resolving function props, matching the client's allocation order.
|
|
16
|
+
|
|
3
17
|
## 0.50.0-next.16
|
|
4
18
|
|
|
5
19
|
### Patch Changes
|
package/index.js
CHANGED
|
@@ -534,7 +534,11 @@ inline)
|
|
|
534
534
|
{
|
|
535
535
|
const config = getConfig(path);
|
|
536
536
|
let expr = path.node;
|
|
537
|
-
|
|
537
|
+
// memoWrapper: false compiles memo-less — inline memos collapse to plain
|
|
538
|
+
// thunks and the hoisted declaration keeps its bare arrow.
|
|
539
|
+
const memo = config.memoWrapper ?
|
|
540
|
+
registerImportMethod(path, config.memoWrapper, undefined) :
|
|
541
|
+
undefined;
|
|
538
542
|
let dTest,
|
|
539
543
|
cond,
|
|
540
544
|
id;
|
|
@@ -552,7 +556,9 @@ inline)
|
|
|
552
556
|
if (!isBooleanExpression(cond))
|
|
553
557
|
cond = t__namespace.unaryExpression("!", t__namespace.unaryExpression("!", cond, true), true);
|
|
554
558
|
id = inline ?
|
|
559
|
+
memo ?
|
|
555
560
|
t__namespace.callExpression(memo, [t__namespace.arrowFunctionExpression([], cond)]) :
|
|
561
|
+
t__namespace.arrowFunctionExpression([], cond) :
|
|
556
562
|
path.scope.generateUidIdentifier("_c$");
|
|
557
563
|
expr.test = t__namespace.callExpression(id, []);
|
|
558
564
|
if (t__namespace.isConditionalExpression(expr.consequent) || t__namespace.isLogicalExpression(expr.consequent)) {
|
|
@@ -586,7 +592,9 @@ inline)
|
|
|
586
592
|
const boolLeft = isBooleanExpression(cond);
|
|
587
593
|
if (!boolLeft) cond = t__namespace.unaryExpression("!", t__namespace.unaryExpression("!", cond, true), true);
|
|
588
594
|
id = inline ?
|
|
595
|
+
memo ?
|
|
589
596
|
t__namespace.callExpression(memo, [t__namespace.arrowFunctionExpression([], cond)]) :
|
|
597
|
+
t__namespace.arrowFunctionExpression([], cond) :
|
|
590
598
|
path.scope.generateUidIdentifier("_c$");
|
|
591
599
|
if (boolLeft) {
|
|
592
600
|
nextPath.node.left = t__namespace.callExpression(id, []);
|
|
@@ -606,7 +614,7 @@ inline)
|
|
|
606
614
|
t__namespace.variableDeclaration("var", [
|
|
607
615
|
t__namespace.variableDeclarator(
|
|
608
616
|
id,
|
|
609
|
-
|
|
617
|
+
memo ?
|
|
610
618
|
t__namespace.callExpression(memo, [t__namespace.arrowFunctionExpression([], cond)]) :
|
|
611
619
|
t__namespace.arrowFunctionExpression([], cond)
|
|
612
620
|
)]
|
|
@@ -2206,9 +2214,35 @@ results)
|
|
|
2206
2214
|
results.hasHydratableEvent = results.hasHydratableEvent || hasHydratableEvent;
|
|
2207
2215
|
}
|
|
2208
2216
|
|
|
2217
|
+
// Children that compile to `insert()` calls and contribute no markup of their
|
|
2218
|
+
// own: dynamic expression containers, components, and spread children. Mirrors
|
|
2219
|
+
// the `!child.id && child.exprs.length` count in transformChildren.
|
|
2220
|
+
function countDynamicSlots(children) {
|
|
2221
|
+
let count = 0;
|
|
2222
|
+
for (const child of children) {
|
|
2223
|
+
const node = child.node;
|
|
2224
|
+
if (
|
|
2225
|
+
t$1.isJSXText(node) ||
|
|
2226
|
+
t$1.isJSXExpressionContainer(node) &&
|
|
2227
|
+
getStaticExpression(child) !== false ||
|
|
2228
|
+
t$1.isJSXElement(node) && !isComponent(getTagName(node)))
|
|
2229
|
+
|
|
2230
|
+
continue;
|
|
2231
|
+
count++;
|
|
2232
|
+
}
|
|
2233
|
+
return count;
|
|
2234
|
+
}
|
|
2235
|
+
|
|
2209
2236
|
function findLastElement(children, hydratable) {
|
|
2210
2237
|
let lastElement = -1,
|
|
2211
2238
|
tagName;
|
|
2239
|
+
// Counterpart of transformChildren's per-slot markers: with two or more
|
|
2240
|
+
// dynamic slots under this parent (CSR only), a trailing dynamic child
|
|
2241
|
+
// appends a dedicated `<!>` placeholder to the template, so an earlier
|
|
2242
|
+
// element may not omit its closing tag — the still-open element would
|
|
2243
|
+
// swallow the placeholder as a child while the generated
|
|
2244
|
+
// firstChild/nextSibling walk expects it as a sibling.
|
|
2245
|
+
const perSlotMarkers = !hydratable && countDynamicSlots(children) > 1;
|
|
2212
2246
|
for (let i = children.length - 1; i >= 0; i--) {
|
|
2213
2247
|
const node = children[i].node;
|
|
2214
2248
|
if (
|
|
@@ -2222,6 +2256,9 @@ function findLastElement(children, hydratable) {
|
|
|
2222
2256
|
lastElement = i;
|
|
2223
2257
|
break;
|
|
2224
2258
|
}
|
|
2259
|
+
// This trailing dynamic slot will emit a per-slot placeholder after any
|
|
2260
|
+
// preceding element's markup: nothing here may omit its closing tag.
|
|
2261
|
+
if (perSlotMarkers) break;
|
|
2225
2262
|
}
|
|
2226
2263
|
return lastElement;
|
|
2227
2264
|
}
|
|
@@ -2483,6 +2520,15 @@ config)
|
|
|
2483
2520
|
t$1.isJSXSpreadAttribute(attr) ||
|
|
2484
2521
|
t$1.isJSXIdentifier(attr.name) &&
|
|
2485
2522
|
["textContent", "innerHTML", "innerText"].includes(attr.name.name) ||
|
|
2523
|
+
// inlineStyles: false rewrites every style value into an IIFE
|
|
2524
|
+
// expression, so even literal styles compile to dynamic
|
|
2525
|
+
// bindings that need an element reference.
|
|
2526
|
+
!config.inlineStyles &&
|
|
2527
|
+
t$1.isJSXIdentifier(attr.name) &&
|
|
2528
|
+
attr.name.name === "style" && (
|
|
2529
|
+
t$1.isStringLiteral(attr.value) ||
|
|
2530
|
+
t$1.isJSXExpressionContainer(attr.value) &&
|
|
2531
|
+
!t$1.isJSXEmptyExpression(attr.value.expression)) ||
|
|
2486
2532
|
t$1.isJSXNamespacedName(attr.name) && attr.name.namespace.name === "prop" ||
|
|
2487
2533
|
t$1.isJSXExpressionContainer(attr.value) &&
|
|
2488
2534
|
!(
|
|
@@ -2754,6 +2800,8 @@ function wrapDynamics$1(path, dynamics) {
|
|
|
2754
2800
|
if (!dynamics.length) return;
|
|
2755
2801
|
const config = getConfig(path);
|
|
2756
2802
|
|
|
2803
|
+
// dynamics are only queued when effectWrapper is configured (element.ts
|
|
2804
|
+
// guards every push), so the name is always a string here
|
|
2757
2805
|
const effectWrapperId = registerImportMethod(path, config.effectWrapper, undefined);
|
|
2758
2806
|
|
|
2759
2807
|
if (dynamics.length === 1) {
|
|
@@ -3690,12 +3738,25 @@ path,
|
|
|
3690
3738
|
`Fragments can only be used top level in JSX. Not used under a <${tagName}>.`
|
|
3691
3739
|
);
|
|
3692
3740
|
}
|
|
3741
|
+
const allocatesIds = hydratable && canChildSlotAllocateIds(path);
|
|
3693
3742
|
const child = transformNode(path);
|
|
3694
3743
|
if (!child) return memo;
|
|
3695
3744
|
if (markers && child.exprs.length && !child.spreadElement)
|
|
3696
3745
|
memo.push(t.stringLiteral("<!--$-->"));
|
|
3697
3746
|
if (child.exprs.length && !doNotEscape && !child.spreadElement)
|
|
3698
3747
|
child.exprs[0] = escapeExpression(path, child.exprs[0]);
|
|
3748
|
+
// Deferred holes that can allocate hydration ids evaluate under their
|
|
3749
|
+
// own owner scope, exactly like `transformChildren` does for the
|
|
3750
|
+
// template path. Spread elements render through `ssrElement` instead
|
|
3751
|
+
// of a template, but their children holes still need the wrap — the
|
|
3752
|
+
// dom generate scope()s the matching insert accessor regardless of
|
|
3753
|
+
// spread, so skipping it here desyncs every hydration id that follows
|
|
3754
|
+
// the hole.
|
|
3755
|
+
if (child.exprs.length && allocatesIds && child.dynamic) {
|
|
3756
|
+
child.exprs[0] = t.callExpression(registerImportMethod(path, "scope"), [
|
|
3757
|
+
child.exprs[0]]
|
|
3758
|
+
);
|
|
3759
|
+
}
|
|
3699
3760
|
memo.push(
|
|
3700
3761
|
getCreateTemplate(config, path, child)(path, child, false)
|
|
3701
3762
|
);
|
|
@@ -3764,7 +3825,19 @@ path,
|
|
|
3764
3825
|
if (runningObject.length || !props.length) props.push(t.objectExpression(runningObject));
|
|
3765
3826
|
|
|
3766
3827
|
if (props.length > 1 || dynamicSpread) {
|
|
3767
|
-
|
|
3828
|
+
let merged = t.callExpression(
|
|
3829
|
+
registerImportMethod(path, "mergeProps"),
|
|
3830
|
+
props
|
|
3831
|
+
);
|
|
3832
|
+
// Defer the merge behind a thunk when hydratable: `mergeProps` with a
|
|
3833
|
+
// function source creates a memo, which consumes a hydration child id.
|
|
3834
|
+
// Evaluated in argument position it would run before `ssrElement`
|
|
3835
|
+
// allocates the element's own id, while the client claims the element
|
|
3836
|
+
// (getNextElement) before applying the spread — shifting the element's
|
|
3837
|
+
// id by one and leaving it unclaimed. `ssrElement` resolves function
|
|
3838
|
+
// props after allocating the hydration key, matching the client order.
|
|
3839
|
+
if (hydratable) merged = t.arrowFunctionExpression([], merged);
|
|
3840
|
+
props = [merged];
|
|
3768
3841
|
}
|
|
3769
3842
|
}
|
|
3770
3843
|
|
|
@@ -3826,8 +3899,9 @@ wrap)
|
|
|
3826
3899
|
const inner = result.wontEscape ?
|
|
3827
3900
|
result.exprs[0] :
|
|
3828
3901
|
wrapFragmentChildWithEscape(path, result.exprs[0]);
|
|
3829
|
-
return t__namespace.callExpression(
|
|
3830
|
-
|
|
3902
|
+
return t__namespace.callExpression(
|
|
3903
|
+
registerImportMethod(path, getConfig(path).memoWrapper, undefined),
|
|
3904
|
+
[inner]
|
|
3831
3905
|
);
|
|
3832
3906
|
}
|
|
3833
3907
|
return result.exprs[0];
|
|
@@ -4454,6 +4528,8 @@ function wrapDynamics(path, dynamics) {
|
|
|
4454
4528
|
if (!dynamics.length) return;
|
|
4455
4529
|
const config = getConfig(path);
|
|
4456
4530
|
|
|
4531
|
+
// dynamics are only queued when effectWrapper is configured (element.ts
|
|
4532
|
+
// guards every push), so the name is always a string here
|
|
4457
4533
|
const effectWrapperId = registerImportMethod(path, config.effectWrapper, undefined);
|
|
4458
4534
|
|
|
4459
4535
|
if (dynamics.length === 1) {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dom-expressions/babel-plugin-jsx",
|
|
3
3
|
"description": "A JSX to DOM plugin that wraps expressions for fine grained change detection",
|
|
4
|
-
"version": "0.50.0-next.
|
|
4
|
+
"version": "0.50.0-next.17",
|
|
5
5
|
"author": "Ryan Carniato",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"repository": {
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
"@rollup/plugin-babel": "7.0.0",
|
|
29
29
|
"@types/babel__core": "^7.20.5",
|
|
30
30
|
"@types/babel__traverse": "^7.28.0",
|
|
31
|
-
"@dom-expressions/runtime": "0.50.0-next.
|
|
31
|
+
"@dom-expressions/runtime": "0.50.0-next.17"
|
|
32
32
|
},
|
|
33
33
|
"gitHead": "eb463c653325e24824422cff6bfed0d35113ef33",
|
|
34
34
|
"scripts": {
|