@dom-expressions/babel-plugin-jsx 0.50.0-next.15 → 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 +44 -0
- package/index.js +247 -66
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,49 @@
|
|
|
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
|
+
|
|
17
|
+
## 0.50.0-next.16
|
|
18
|
+
|
|
19
|
+
### Patch Changes
|
|
20
|
+
|
|
21
|
+
- 04849df: Preserve JS value semantics for wrapped `&&` conditions (#532). The dom generate's condition wrap used to emit `memo(() => !!left)() && right`, collapsing every falsy left value to `false` — visibly wrong for component props (`undefined` became `false`, breaking `== null` checks) and a hydration mismatch against the untransformed server output (`{0 && <div/>}` rendered "0" on the server, nothing on the client). `left && right` is exactly `left ? right : left`, so the wrap now emits `memo(() => !!left)() ? right : left`: branching still keys off the memoized truthiness (truthy-value churn never re-creates the right side) while the alternate returns the raw left, matching the server for free. Statically boolean lefts (comparisons, `!x`) keep the plain `memo(() => left)() && right` form — the memo's value is the expression's value, so it's already exact with no second evaluation. Ported identically to the Rust jsx-compiler.
|
|
22
|
+
- 248f784: fix(compiler): key the hole `scope()` wrap off the transform's `dynamic` flag instead of the transformed expression shape. The dom generate simplifies `{sig()}` to the bare getter `sig`, which the old `isDeferredChildSlotExpression` predicate didn't count as deferred while the matching ssr arrow was — so the server scope-wrapped the hole and the client didn't, shifting every sibling hydration id after it. Bare getters are re-wrapped as `() => sig()` on the dom side so `scope()` doesn't tag the user's function.
|
|
23
|
+
- c2a542b: Fix hydration key mismatches when async holes defer past eager siblings
|
|
24
|
+
(solidjs/solid#2801 bug 2). Dynamic element children that can allocate
|
|
25
|
+
hydration ids (conditionals, component-children access, call expressions)
|
|
26
|
+
are now compiled with their own id scope on both generates: the dom and ssr
|
|
27
|
+
generates wrap the hole expression in a new `scope()` runtime helper using a
|
|
28
|
+
shared predicate, so marking cannot desync.
|
|
29
|
+
|
|
30
|
+
On the client, `scope(fn)` tags the accessor and `insert()` makes the outer
|
|
31
|
+
render effect non-transparent (its own id scope) for tagged accessors; the
|
|
32
|
+
inner unwrapping effect stays transparent so content ids keep a fixed depth.
|
|
33
|
+
On the server, `scope` (framework-provided via rxcore as `ssrScope`) reserves
|
|
34
|
+
one id slot at registration and evaluates the hole — including async retries
|
|
35
|
+
— under that reserved id with a zeroed child counter, so retry timing can no
|
|
36
|
+
longer shift sibling ids. The ssr generate's `orderedInsert` sibling
|
|
37
|
+
thunk-wrapping is removed; it is superseded by hole scopes.
|
|
38
|
+
|
|
39
|
+
Hole content ids gain one nesting level (e.g. `_hk=10` instead of `_hk=1`)
|
|
40
|
+
identically on both sides. rxcore implementations must provide an `ssrScope`
|
|
41
|
+
export and honor a `scope: true` effect option (mapped to a non-transparent
|
|
42
|
+
render effect).
|
|
43
|
+
|
|
44
|
+
- bb7470e: Give every dynamic child slot its own insertion marker when a parent hosts more than one (solidjs/solid#2830). Adjacent expression slots used to share a marker (`null` at the tail, a shared following sibling, or one reused `<!>` between text), which collapsed them into a single `$$SLOT` ownership region: a node migrating between adjacent slots was destroyed by the slot it left, arrays exchanging members could throw `NotFoundError`, and a slot emptied via `[]` refilled at the wrong position. Slots in multi-slot parents now ride the immediately following static sibling or get a dedicated `<!>` placeholder — the same per-slot geometry hydratable output has always produced, which is why these shapes already worked after hydration. Zero runtime changes; single-slot parents compile byte-identically to before.
|
|
45
|
+
- 668264f: Universal JSX now passes compile-time static host props to `createElement(tag, staticProps)` so custom renderers can configure nodes before children are inserted. Dynamic props and elements with spreads continue to use the existing `setProp` / `spread` paths.
|
|
46
|
+
|
|
3
47
|
## 0.50.0-next.15
|
|
4
48
|
|
|
5
49
|
### Patch Changes
|
package/index.js
CHANGED
|
@@ -444,6 +444,34 @@ function toEventName(name) {
|
|
|
444
444
|
return name.slice(2).toLowerCase();
|
|
445
445
|
}
|
|
446
446
|
|
|
447
|
+
// === Hole-scope predicates (shared by dom + ssr generates) ===
|
|
448
|
+
//
|
|
449
|
+
// A dynamic element child ("hole") whose evaluation can allocate hydration
|
|
450
|
+
// ids (create hydratable elements, run components, register memos) gets its
|
|
451
|
+
// own owner scope so server and client allocate identical ids regardless of
|
|
452
|
+
// WHEN the hole evaluates (eager, deferred by async, or re-run). Both
|
|
453
|
+
// generates must agree exactly on which holes qualify, so the predicates
|
|
454
|
+
// live here.
|
|
455
|
+
|
|
456
|
+
function canReturnHydratableChild(node) {
|
|
457
|
+
if (t__namespace.isTSNonNullExpression(node) || t__namespace.isTSAsExpression(node) || t__namespace.isTSSatisfiesExpression(node))
|
|
458
|
+
return canReturnHydratableChild(node.expression);
|
|
459
|
+
if (t__namespace.isJSXElement(node) || t__namespace.isJSXFragment(node) || t__namespace.isCallExpression(node)) return true;
|
|
460
|
+
if (t__namespace.isMemberExpression(node) || t__namespace.isOptionalMemberExpression(node)) {
|
|
461
|
+
return !node.computed && t__namespace.isIdentifier(node.property, { name: "children" });
|
|
462
|
+
}
|
|
463
|
+
if (t__namespace.isConditionalExpression(node)) {
|
|
464
|
+
return canReturnHydratableChild(node.consequent) || canReturnHydratableChild(node.alternate);
|
|
465
|
+
}
|
|
466
|
+
return t__namespace.isLogicalExpression(node) && canReturnHydratableChild(node.right);
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
function canChildSlotAllocateIds(node) {
|
|
470
|
+
if (node.isJSXElement() || node.isJSXFragment()) return true;
|
|
471
|
+
if (node.isJSXSpreadChild()) return true;
|
|
472
|
+
return node.isJSXExpressionContainer() && canReturnHydratableChild(node.node.expression);
|
|
473
|
+
}
|
|
474
|
+
|
|
447
475
|
function wrappedByText(list, startIndex) {
|
|
448
476
|
let index = startIndex,
|
|
449
477
|
wrapped;
|
|
@@ -467,6 +495,30 @@ function wrappedByText(list, startIndex) {
|
|
|
467
495
|
return false;
|
|
468
496
|
}
|
|
469
497
|
|
|
498
|
+
const BOOLEAN_BINARY_OPS = new Set([
|
|
499
|
+
"==",
|
|
500
|
+
"!=",
|
|
501
|
+
"===",
|
|
502
|
+
"!==",
|
|
503
|
+
"<",
|
|
504
|
+
">",
|
|
505
|
+
"<=",
|
|
506
|
+
">=",
|
|
507
|
+
"instanceof",
|
|
508
|
+
"in"]
|
|
509
|
+
);
|
|
510
|
+
|
|
511
|
+
// Statically guaranteed to evaluate to a boolean: the memoized value IS the
|
|
512
|
+
// expression's value (`false` is the only falsy boolean), so no `!!` coercion
|
|
513
|
+
// is needed and the `&&` wrap can keep the logical form instead of the
|
|
514
|
+
// value-preserving ternary (no second evaluation of the left).
|
|
515
|
+
function isBooleanExpression(node) {
|
|
516
|
+
return (
|
|
517
|
+
t__namespace.isBinaryExpression(node) && BOOLEAN_BINARY_OPS.has(node.operator) ||
|
|
518
|
+
t__namespace.isUnaryExpression(node) && node.operator === "!");
|
|
519
|
+
|
|
520
|
+
}
|
|
521
|
+
|
|
470
522
|
|
|
471
523
|
|
|
472
524
|
|
|
@@ -481,8 +533,12 @@ path,
|
|
|
481
533
|
inline)
|
|
482
534
|
{
|
|
483
535
|
const config = getConfig(path);
|
|
484
|
-
|
|
485
|
-
|
|
536
|
+
let expr = path.node;
|
|
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;
|
|
486
542
|
let dTest,
|
|
487
543
|
cond,
|
|
488
544
|
id;
|
|
@@ -497,10 +553,12 @@ inline)
|
|
|
497
553
|
dTest = isDynamic(path.get("test"), { checkMember: true });
|
|
498
554
|
if (dTest) {
|
|
499
555
|
cond = expr.test;
|
|
500
|
-
if (!
|
|
556
|
+
if (!isBooleanExpression(cond))
|
|
501
557
|
cond = t__namespace.unaryExpression("!", t__namespace.unaryExpression("!", cond, true), true);
|
|
502
558
|
id = inline ?
|
|
559
|
+
memo ?
|
|
503
560
|
t__namespace.callExpression(memo, [t__namespace.arrowFunctionExpression([], cond)]) :
|
|
561
|
+
t__namespace.arrowFunctionExpression([], cond) :
|
|
504
562
|
path.scope.generateUidIdentifier("_c$");
|
|
505
563
|
expr.test = t__namespace.callExpression(id, []);
|
|
506
564
|
if (t__namespace.isConditionalExpression(expr.consequent) || t__namespace.isLogicalExpression(expr.consequent)) {
|
|
@@ -523,12 +581,32 @@ inline)
|
|
|
523
581
|
}));
|
|
524
582
|
if (dTest) {
|
|
525
583
|
cond = nextPath.node.left;
|
|
526
|
-
|
|
527
|
-
|
|
584
|
+
// `left && right` is exactly `left ? right : left`. Branch on the
|
|
585
|
+
// memoized truthiness (so truthy-value churn never re-creates the right
|
|
586
|
+
// side) but return the raw left in the alternate so the expression keeps
|
|
587
|
+
// JS value semantics — `0`/`""`/`undefined` flow through instead of
|
|
588
|
+
// collapsing to `false`, matching the untransformed ssr output (#532).
|
|
589
|
+
// Statically boolean lefts skip the ternary: the memo's value is the
|
|
590
|
+
// expression's value, so the logical form is already exact and the left
|
|
591
|
+
// never evaluates twice.
|
|
592
|
+
const boolLeft = isBooleanExpression(cond);
|
|
593
|
+
if (!boolLeft) cond = t__namespace.unaryExpression("!", t__namespace.unaryExpression("!", cond, true), true);
|
|
528
594
|
id = inline ?
|
|
595
|
+
memo ?
|
|
529
596
|
t__namespace.callExpression(memo, [t__namespace.arrowFunctionExpression([], cond)]) :
|
|
597
|
+
t__namespace.arrowFunctionExpression([], cond) :
|
|
530
598
|
path.scope.generateUidIdentifier("_c$");
|
|
531
|
-
|
|
599
|
+
if (boolLeft) {
|
|
600
|
+
nextPath.node.left = t__namespace.callExpression(id, []);
|
|
601
|
+
} else {
|
|
602
|
+
const alternate = t__namespace.cloneNode(nextPath.node.left, true);
|
|
603
|
+
nextPath.replaceWith(
|
|
604
|
+
t__namespace.conditionalExpression(t__namespace.callExpression(id, []), nextPath.node.right, alternate)
|
|
605
|
+
);
|
|
606
|
+
// replaceWith swaps the node out of the tree; when the `&&` was the
|
|
607
|
+
// top-level expression the local reference is stale.
|
|
608
|
+
expr = path.node;
|
|
609
|
+
}
|
|
532
610
|
}
|
|
533
611
|
}
|
|
534
612
|
if (dTest && !inline && cond && id) {
|
|
@@ -536,7 +614,7 @@ inline)
|
|
|
536
614
|
t__namespace.variableDeclaration("var", [
|
|
537
615
|
t__namespace.variableDeclarator(
|
|
538
616
|
id,
|
|
539
|
-
|
|
617
|
+
memo ?
|
|
540
618
|
t__namespace.callExpression(memo, [t__namespace.arrowFunctionExpression([], cond)]) :
|
|
541
619
|
t__namespace.arrowFunctionExpression([], cond)
|
|
542
620
|
)]
|
|
@@ -2136,9 +2214,35 @@ results)
|
|
|
2136
2214
|
results.hasHydratableEvent = results.hasHydratableEvent || hasHydratableEvent;
|
|
2137
2215
|
}
|
|
2138
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
|
+
|
|
2139
2236
|
function findLastElement(children, hydratable) {
|
|
2140
2237
|
let lastElement = -1,
|
|
2141
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;
|
|
2142
2246
|
for (let i = children.length - 1; i >= 0; i--) {
|
|
2143
2247
|
const node = children[i].node;
|
|
2144
2248
|
if (
|
|
@@ -2152,6 +2256,9 @@ function findLastElement(children, hydratable) {
|
|
|
2152
2256
|
lastElement = i;
|
|
2153
2257
|
break;
|
|
2154
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;
|
|
2155
2262
|
}
|
|
2156
2263
|
return lastElement;
|
|
2157
2264
|
}
|
|
@@ -2163,7 +2270,6 @@ config)
|
|
|
2163
2270
|
{
|
|
2164
2271
|
let tempPath = results.id && results.id.name || "",
|
|
2165
2272
|
tagName = getTagName(path.node),
|
|
2166
|
-
nextPlaceholder,
|
|
2167
2273
|
childPostExprs = [],
|
|
2168
2274
|
i = 0;
|
|
2169
2275
|
const filteredChildren = filterChildren(path.get("children")),
|
|
@@ -2181,6 +2287,8 @@ config)
|
|
|
2181
2287
|
skipId: !results.id || !detectExpressions(filteredChildren, index, config)
|
|
2182
2288
|
});
|
|
2183
2289
|
if (!transformed) return memo;
|
|
2290
|
+
transformed.allocatesIds =
|
|
2291
|
+
config.hydratable && canChildSlotAllocateIds(child);
|
|
2184
2292
|
const i = memo.length;
|
|
2185
2293
|
if (transformed.text && i && memo[i - 1].text) {
|
|
2186
2294
|
memo[i - 1].template =
|
|
@@ -2193,6 +2301,13 @@ config)
|
|
|
2193
2301
|
[]
|
|
2194
2302
|
);
|
|
2195
2303
|
|
|
2304
|
+
// Dynamic slots under this parent (children compiled to `insert()` calls).
|
|
2305
|
+
// With two or more, slots may not share an insertion marker: the marker is
|
|
2306
|
+
// also the runtime's ownership tag ($$SLOT), and adoption only re-tags when
|
|
2307
|
+
// the marker is truthy — shared or null markers let one slot's cleanup
|
|
2308
|
+
// destroy a node that migrated to its neighbor (solidjs/solid#2830).
|
|
2309
|
+
const dynamicSlots = childNodes.reduce((n, c) => c && !c.id && c.exprs.length ? n + 1 : n, 0);
|
|
2310
|
+
|
|
2196
2311
|
childNodes.forEach((child, index) => {
|
|
2197
2312
|
if (!child) return;
|
|
2198
2313
|
if (child.tagName && child.renderer !== "dom") {
|
|
@@ -2245,23 +2360,47 @@ config)
|
|
|
2245
2360
|
results.isImportNode = results.isImportNode || child.isImportNode;
|
|
2246
2361
|
results.isWrapped = results.isWrapped || child.isWrapped;
|
|
2247
2362
|
tempPath = child.id.name;
|
|
2248
|
-
nextPlaceholder = null;
|
|
2249
2363
|
i++;
|
|
2250
2364
|
} else if (child.exprs.length) {
|
|
2251
2365
|
let insert = registerImportMethod(path, "insert", getRendererConfig(path, "dom").moduleName);
|
|
2252
2366
|
const multi = checkLength(filteredChildren),
|
|
2253
|
-
markers = config.hydratable && multi
|
|
2367
|
+
markers = config.hydratable && multi,
|
|
2368
|
+
// CSR counterpart of the hydratable per-slot markers: when this parent
|
|
2369
|
+
// hosts multiple dynamic slots, each gets its own truthy marker — the
|
|
2370
|
+
// immediately following sibling when it has a reference, otherwise a
|
|
2371
|
+
// dedicated `<!>` placeholder.
|
|
2372
|
+
perSlot = !markers && dynamicSlots > 1;
|
|
2373
|
+
// Mirror of the ssr generate's `scope()` wrap: deferred holes that can
|
|
2374
|
+
// allocate hydration ids get their own owner scope (insert makes the
|
|
2375
|
+
// outer render effect non-transparent for tagged accessors). Keyed off
|
|
2376
|
+
// `dynamic` so both generates decide identically for the same source.
|
|
2377
|
+
if (child.allocatesIds && child.dynamic) {
|
|
2378
|
+
let expr = child.exprs[0];
|
|
2379
|
+
// The shared transform simplifies `{sig()}` to the bare getter `sig`;
|
|
2380
|
+
// rewrap so tagging the scope doesn't mutate the user's function.
|
|
2381
|
+
if (!t$1.isFunction(expr) && !(t$1.isCallExpression(expr) && t$1.isFunction(expr.callee))) {
|
|
2382
|
+
expr = t$1.arrowFunctionExpression([], t$1.callExpression(expr, []));
|
|
2383
|
+
}
|
|
2384
|
+
child.exprs[0] = t$1.callExpression(
|
|
2385
|
+
registerImportMethod(path, "scope", getRendererConfig(path, "dom").moduleName),
|
|
2386
|
+
[expr]
|
|
2387
|
+
);
|
|
2388
|
+
}
|
|
2254
2389
|
// boxed by textNodes
|
|
2255
|
-
if (markers || wrappedByText(childNodes, index)) {
|
|
2390
|
+
if (markers || perSlot || wrappedByText(childNodes, index)) {
|
|
2256
2391
|
let exprId;
|
|
2257
2392
|
let contentId;
|
|
2258
2393
|
if (markers) tempPath = createPlaceholder(path, results, tempPath, i++, "$")[0].name;
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2394
|
+
// Ride the immediately following sibling's reference when it exists —
|
|
2395
|
+
// unless the slot is boxed by text, where a placeholder is structurally
|
|
2396
|
+
// required to keep the surrounding template text nodes from merging.
|
|
2397
|
+
if (perSlot && !wrappedByText(childNodes, index)) {
|
|
2398
|
+
exprId = childNodes[index + 1] && childNodes[index + 1].id || undefined;
|
|
2399
|
+
}
|
|
2400
|
+
if (!exprId) {
|
|
2262
2401
|
[exprId, contentId] = createPlaceholder(path, results, tempPath, i++, markers ? "/" : "");
|
|
2402
|
+
tempPath = exprId.name;
|
|
2263
2403
|
}
|
|
2264
|
-
if (!markers) nextPlaceholder = exprId;
|
|
2265
2404
|
const args = contentId ?
|
|
2266
2405
|
[
|
|
2267
2406
|
results.id,
|
|
@@ -2275,7 +2414,6 @@ config)
|
|
|
2275
2414
|
exprId];
|
|
2276
2415
|
|
|
2277
2416
|
results.exprs.push(t$1.expressionStatement(t$1.callExpression(insert, args)));
|
|
2278
|
-
tempPath = exprId.name;
|
|
2279
2417
|
} else if (multi) {
|
|
2280
2418
|
results.exprs.push(
|
|
2281
2419
|
t$1.expressionStatement(
|
|
@@ -2293,7 +2431,7 @@ config)
|
|
|
2293
2431
|
)
|
|
2294
2432
|
);
|
|
2295
2433
|
}
|
|
2296
|
-
}
|
|
2434
|
+
}
|
|
2297
2435
|
});
|
|
2298
2436
|
results.postExprs.unshift(...childPostExprs);
|
|
2299
2437
|
}
|
|
@@ -2382,6 +2520,15 @@ config)
|
|
|
2382
2520
|
t$1.isJSXSpreadAttribute(attr) ||
|
|
2383
2521
|
t$1.isJSXIdentifier(attr.name) &&
|
|
2384
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)) ||
|
|
2385
2532
|
t$1.isJSXNamespacedName(attr.name) && attr.name.namespace.name === "prop" ||
|
|
2386
2533
|
t$1.isJSXExpressionContainer(attr.value) &&
|
|
2387
2534
|
!(
|
|
@@ -2653,6 +2800,8 @@ function wrapDynamics$1(path, dynamics) {
|
|
|
2653
2800
|
if (!dynamics.length) return;
|
|
2654
2801
|
const config = getConfig(path);
|
|
2655
2802
|
|
|
2803
|
+
// dynamics are only queued when effectWrapper is configured (element.ts
|
|
2804
|
+
// guards every push), so the name is always a string here
|
|
2656
2805
|
const effectWrapperId = registerImportMethod(path, config.effectWrapper, undefined);
|
|
2657
2806
|
|
|
2658
2807
|
if (dynamics.length === 1) {
|
|
@@ -2782,7 +2931,6 @@ const t = t__namespace;
|
|
|
2782
2931
|
|
|
2783
2932
|
|
|
2784
2933
|
|
|
2785
|
-
|
|
2786
2934
|
|
|
2787
2935
|
|
|
2788
2936
|
function appendToTemplate(template, value) {
|
|
@@ -2961,8 +3109,7 @@ info = {})
|
|
|
2961
3109
|
hoistExpression(
|
|
2962
3110
|
path,
|
|
2963
3111
|
results,
|
|
2964
|
-
t.callExpression(registerImportMethod(path, "ssrHydrationKey"), [])
|
|
2965
|
-
{ }
|
|
3112
|
+
t.callExpression(registerImportMethod(path, "ssrHydrationKey"), [])
|
|
2966
3113
|
)
|
|
2967
3114
|
);
|
|
2968
3115
|
}
|
|
@@ -3505,29 +3652,6 @@ quasis)
|
|
|
3505
3652
|
});
|
|
3506
3653
|
}
|
|
3507
3654
|
|
|
3508
|
-
function canReturnHydratableChild(node) {
|
|
3509
|
-
if (t.isTSNonNullExpression(node) || t.isTSAsExpression(node) || t.isTSSatisfiesExpression(node))
|
|
3510
|
-
return canReturnHydratableChild(node.expression);
|
|
3511
|
-
if (t.isJSXElement(node) || t.isJSXFragment(node) || t.isCallExpression(node)) return true;
|
|
3512
|
-
if (t.isMemberExpression(node) || t.isOptionalMemberExpression(node)) {
|
|
3513
|
-
return !node.computed && t.isIdentifier(node.property, { name: "children" });
|
|
3514
|
-
}
|
|
3515
|
-
if (t.isConditionalExpression(node)) {
|
|
3516
|
-
return canReturnHydratableChild(node.consequent) || canReturnHydratableChild(node.alternate);
|
|
3517
|
-
}
|
|
3518
|
-
return t.isLogicalExpression(node) && canReturnHydratableChild(node.right);
|
|
3519
|
-
}
|
|
3520
|
-
|
|
3521
|
-
function canChildSlotAllocateIds(node) {
|
|
3522
|
-
if (node.isJSXElement() || node.isJSXFragment()) return true;
|
|
3523
|
-
if (node.isJSXSpreadChild()) return true;
|
|
3524
|
-
return node.isJSXExpressionContainer() && canReturnHydratableChild(node.node.expression);
|
|
3525
|
-
}
|
|
3526
|
-
|
|
3527
|
-
function isDeferredChildSlotExpression(node) {
|
|
3528
|
-
return t.isFunction(node) || t.isCallExpression(node) && t.isFunction(node.callee);
|
|
3529
|
-
}
|
|
3530
|
-
|
|
3531
3655
|
function transformChildren$1(
|
|
3532
3656
|
path,
|
|
3533
3657
|
results,
|
|
@@ -3538,14 +3662,13 @@ results,
|
|
|
3538
3662
|
const filteredChildren = filterChildren(path.get("children"));
|
|
3539
3663
|
const multi = checkLength(filteredChildren),
|
|
3540
3664
|
markers = hydratable && multi;
|
|
3541
|
-
let orderedInsert = false;
|
|
3542
3665
|
filteredChildren.forEach((node) => {
|
|
3543
3666
|
if (node.isJSXFragment()) {
|
|
3544
3667
|
throw new Error(
|
|
3545
3668
|
`Fragments can only be used top level in JSX. Not used under a <${tagName}>.`
|
|
3546
3669
|
);
|
|
3547
3670
|
}
|
|
3548
|
-
const allocatesIds = canChildSlotAllocateIds(node);
|
|
3671
|
+
const allocatesIds = hydratable && canChildSlotAllocateIds(node);
|
|
3549
3672
|
const child = transformNode(node, { doNotEscape, parentResults: results });
|
|
3550
3673
|
if (!child) return;
|
|
3551
3674
|
appendToTemplate(results.template, child.template);
|
|
@@ -3570,14 +3693,14 @@ results,
|
|
|
3570
3693
|
_groupableTextContent ?
|
|
3571
3694
|
{ group: true } :
|
|
3572
3695
|
undefined;
|
|
3573
|
-
|
|
3574
|
-
|
|
3575
|
-
|
|
3576
|
-
|
|
3577
|
-
|
|
3578
|
-
|
|
3579
|
-
|
|
3580
|
-
|
|
3696
|
+
// Deferred holes that can allocate hydration ids evaluate under their
|
|
3697
|
+
// own owner scope so retry timing can't skew sibling ids (mirrors the
|
|
3698
|
+
// dom generate's `scope()` wrap around the matching insert accessor).
|
|
3699
|
+
// Keyed off `dynamic` so both generates decide identically.
|
|
3700
|
+
let expr = child.exprs[0];
|
|
3701
|
+
if (allocatesIds && child.dynamic) {
|
|
3702
|
+
expr = t.callExpression(registerImportMethod(path, "scope"), [expr]);
|
|
3703
|
+
}
|
|
3581
3704
|
|
|
3582
3705
|
// boxed by textNodes
|
|
3583
3706
|
if (markers && !child.spreadElement) {
|
|
@@ -3615,12 +3738,25 @@ path,
|
|
|
3615
3738
|
`Fragments can only be used top level in JSX. Not used under a <${tagName}>.`
|
|
3616
3739
|
);
|
|
3617
3740
|
}
|
|
3741
|
+
const allocatesIds = hydratable && canChildSlotAllocateIds(path);
|
|
3618
3742
|
const child = transformNode(path);
|
|
3619
3743
|
if (!child) return memo;
|
|
3620
3744
|
if (markers && child.exprs.length && !child.spreadElement)
|
|
3621
3745
|
memo.push(t.stringLiteral("<!--$-->"));
|
|
3622
3746
|
if (child.exprs.length && !doNotEscape && !child.spreadElement)
|
|
3623
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
|
+
}
|
|
3624
3760
|
memo.push(
|
|
3625
3761
|
getCreateTemplate(config, path, child)(path, child, false)
|
|
3626
3762
|
);
|
|
@@ -3689,7 +3825,19 @@ path,
|
|
|
3689
3825
|
if (runningObject.length || !props.length) props.push(t.objectExpression(runningObject));
|
|
3690
3826
|
|
|
3691
3827
|
if (props.length > 1 || dynamicSpread) {
|
|
3692
|
-
|
|
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];
|
|
3693
3841
|
}
|
|
3694
3842
|
}
|
|
3695
3843
|
|
|
@@ -3751,8 +3899,9 @@ wrap)
|
|
|
3751
3899
|
const inner = result.wontEscape ?
|
|
3752
3900
|
result.exprs[0] :
|
|
3753
3901
|
wrapFragmentChildWithEscape(path, result.exprs[0]);
|
|
3754
|
-
return t__namespace.callExpression(
|
|
3755
|
-
|
|
3902
|
+
return t__namespace.callExpression(
|
|
3903
|
+
registerImportMethod(path, getConfig(path).memoWrapper, undefined),
|
|
3904
|
+
[inner]
|
|
3756
3905
|
);
|
|
3757
3906
|
}
|
|
3758
3907
|
return result.exprs[0];
|
|
@@ -3905,6 +4054,9 @@ info = {})
|
|
|
3905
4054
|
renderer: "universal"
|
|
3906
4055
|
};
|
|
3907
4056
|
|
|
4057
|
+
const initProps = transformAttributes(path, results);
|
|
4058
|
+
const createElementArgs = [t__namespace.stringLiteral(tagName)];
|
|
4059
|
+
if (initProps.length) createElementArgs.push(t__namespace.objectExpression(initProps));
|
|
3908
4060
|
results.declarations.push(
|
|
3909
4061
|
t__namespace.variableDeclarator(
|
|
3910
4062
|
results.id,
|
|
@@ -3914,12 +4066,10 @@ info = {})
|
|
|
3914
4066
|
"createElement",
|
|
3915
4067
|
getRendererConfig(path, "universal").moduleName
|
|
3916
4068
|
),
|
|
3917
|
-
|
|
4069
|
+
createElementArgs
|
|
3918
4070
|
)
|
|
3919
4071
|
)
|
|
3920
4072
|
);
|
|
3921
|
-
|
|
3922
|
-
transformAttributes(path, results);
|
|
3923
4073
|
transformChildren(path, results);
|
|
3924
4074
|
|
|
3925
4075
|
return results;
|
|
@@ -3931,12 +4081,14 @@ results)
|
|
|
3931
4081
|
{
|
|
3932
4082
|
let children, spreadExpr;
|
|
3933
4083
|
let attributes = path.get("openingElement").get("attributes");
|
|
4084
|
+
const initProps = [];
|
|
3934
4085
|
const elem = results.id,
|
|
3935
4086
|
hasChildren = path.node.children.length > 0,
|
|
3936
|
-
config = getConfig(path)
|
|
4087
|
+
config = getConfig(path),
|
|
4088
|
+
hasSpread = attributes.some((attribute) => t__namespace.isJSXSpreadAttribute(attribute.node));
|
|
3937
4089
|
|
|
3938
4090
|
// preprocess spreads
|
|
3939
|
-
if (
|
|
4091
|
+
if (hasSpread) {
|
|
3940
4092
|
[attributes, spreadExpr] = processSpreads(path, attributes, {
|
|
3941
4093
|
elem,
|
|
3942
4094
|
hasChildren,
|
|
@@ -4065,20 +4217,47 @@ results)
|
|
|
4065
4217
|
{
|
|
4066
4218
|
results.dynamics.push({ elem, key, value: value.expression });
|
|
4067
4219
|
} else {
|
|
4068
|
-
|
|
4069
|
-
|
|
4220
|
+
addStaticAttr(
|
|
4221
|
+
attribute,
|
|
4222
|
+
results,
|
|
4223
|
+
initProps,
|
|
4224
|
+
elem,
|
|
4225
|
+
key,
|
|
4226
|
+
value.expression,
|
|
4227
|
+
hasSpread
|
|
4070
4228
|
);
|
|
4071
4229
|
}
|
|
4072
4230
|
} else {
|
|
4073
|
-
results
|
|
4074
|
-
t__namespace.expressionStatement(setAttr(attribute, elem, key, value))
|
|
4075
|
-
);
|
|
4231
|
+
addStaticAttr(attribute, results, initProps, elem, key, value, hasSpread);
|
|
4076
4232
|
}
|
|
4077
4233
|
});
|
|
4078
4234
|
if (spreadExpr) results.exprs.push(spreadExpr);
|
|
4079
4235
|
if (!hasChildren && children) {
|
|
4080
4236
|
path.node.children.push(children);
|
|
4081
4237
|
}
|
|
4238
|
+
return initProps;
|
|
4239
|
+
}
|
|
4240
|
+
|
|
4241
|
+
function addStaticAttr(
|
|
4242
|
+
path,
|
|
4243
|
+
results,
|
|
4244
|
+
initProps,
|
|
4245
|
+
elem,
|
|
4246
|
+
key,
|
|
4247
|
+
value,
|
|
4248
|
+
hasSpread)
|
|
4249
|
+
{
|
|
4250
|
+
if (!value) value = t__namespace.booleanLiteral(true);
|
|
4251
|
+
if (hasSpread) {
|
|
4252
|
+
results.exprs.push(t__namespace.expressionStatement(setAttr(path, elem, key, value)));
|
|
4253
|
+
} else {
|
|
4254
|
+
initProps.push(
|
|
4255
|
+
t__namespace.objectProperty(
|
|
4256
|
+
t__namespace.isValidIdentifier(key) ? t__namespace.identifier(key) : t__namespace.stringLiteral(key),
|
|
4257
|
+
value
|
|
4258
|
+
)
|
|
4259
|
+
);
|
|
4260
|
+
}
|
|
4082
4261
|
}
|
|
4083
4262
|
|
|
4084
4263
|
function setAttr(
|
|
@@ -4349,6 +4528,8 @@ function wrapDynamics(path, dynamics) {
|
|
|
4349
4528
|
if (!dynamics.length) return;
|
|
4350
4529
|
const config = getConfig(path);
|
|
4351
4530
|
|
|
4531
|
+
// dynamics are only queued when effectWrapper is configured (element.ts
|
|
4532
|
+
// guards every push), so the name is always a string here
|
|
4352
4533
|
const effectWrapperId = registerImportMethod(path, config.effectWrapper, undefined);
|
|
4353
4534
|
|
|
4354
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": {
|