@solidjs/babel-plugin 2.0.0-rc.15 → 2.0.0-rc.16
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 +8 -0
- package/README.md +9 -8
- package/index.js +193 -192
- package/package.json +1 -1
package/.turbo/turbo-build.log
CHANGED
|
@@ -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
|
-
[32mcreated [1mindex.js[22m in [
|
|
10
|
+
[32mcreated [1mindex.js[22m in [1m3.3s[22m[39m
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# @solidjs/babel-plugin
|
|
2
2
|
|
|
3
|
+
## 2.0.0-rc.16
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 4a99c84: `sourceNames` gains a `locations` kind (and the plugin a `root` option): component and binding labels carry a `file:line:col` for diagnostics. `createComponent(Home, props, "Home", "src/App.tsx:12:5")` points at the `<Home` opening element (DOM and SSR output); a binding's options gain `loc` after `name` — the attribute's start for an attribute effect (a merged effect points at the first attribute it lists), the hole's `{` for an insert — and a spread passes it after the tag (`spread(el, props, false, undefined, "div", "src/App.tsx:20:12")`). The path is relative to `root` (default: the working directory) with `/` separators; line and column are 1-based, so a dev server links the location straight to the source. Like the other kinds, `locations` follows `dev` when unset, `true` turns every kind on, and the object form picks; a location is only emitted beside its label, and never when Babel has no `filename` for the module.
|
|
8
|
+
- 3f36f77: Server components: binding slots are removed. Client components in slots replace them: place a client component through a `Slot` (`<props.x />`) where the server's markup needs client behavior, or a form `action` for a mutation. Removed with them: the `BindingSlot`, `SlotOutput` and `SlotError` types, the lazy `@solidjs/web/frames/bind` entry and its `"bind"` tier, the `_s:*` position markers and the compiled `ssrClaim` hole, the effect of the `serverComponents` compiler option (`@solidjs/compiler` and `@solidjs/babel-plugin` still accept it, so existing build configs keep working, and ignore it), and the `BINDING_SLOT_POSITION` diagnostic code (`@solidjs/signals`' `DiagnosticCode`, `solid-js`'s `diagnostics.json`). Ordinary slots, their `slot:<id>` ranges and records, and `$key` are unchanged.
|
|
9
|
+
- d0ba31c: SSR evaluates a `ref` expression beside a spread, as it already does without one, so a ref factory's owned setup (`ref={tooltip()}`) runs on the server too and the hydration ids after it line up. The ref itself is still never called on the server (#4081). Under `serverComponents` the same holds outside a server component's render, where a ref expression was never evaluated on either path; inside one the claim evaluates it, once.
|
|
10
|
+
|
|
3
11
|
## 2.0.0-rc.15
|
|
4
12
|
|
|
5
13
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -129,16 +129,24 @@ Development output. With `hydratable`, emits the hydration walk validation helpe
|
|
|
129
129
|
|
|
130
130
|
### sourceNames
|
|
131
131
|
|
|
132
|
-
- Type: `boolean | { components?: boolean; bindings?: boolean }`
|
|
132
|
+
- Type: `boolean | { components?: boolean; bindings?: boolean; locations?: boolean }`
|
|
133
133
|
- Default: follows `dev` — every kind on when `dev: true`, off otherwise
|
|
134
134
|
|
|
135
135
|
Names as written in source, carried into the output so the dev and observe runtimes can label the reactive graph — in diagnostic `ownerPath`s, attribution chains, and the Performance panel tracks — even after a minifier renames everything. Unset, the option follows `dev`; `true`/`false` sets every kind (`sourceNames: false` opts a dev build out); the object form picks, and each kind it leaves unspecified follows `dev`. The production runtimes ignore the names, and production output (`dev: false`) is byte-identical whether the option is set or not. `@solidjs/compiler`'s `transform()` takes the same option with the same shape and defaults.
|
|
136
136
|
|
|
137
137
|
- `components`: emit the tag as written as a third `createComponent` argument — `<Home />` compiles to `createComponent(Home, props, "Home")`, `<Ui.Button />` to `"Ui.Button"` — so each component's owner reads `<Home>` even when a `lazy()`/HMR wrapper hides the function. Applies to DOM and SSR output; for SSR the compiler keeps the `createComponent` call it otherwise inlines to `Comp(props)`, so the server runtime labels the owner the same way (prod SSR output, without the option, is unchanged). Universal and dynamic output are unaffected.
|
|
138
138
|
- `bindings`: every compiled binding effect is named by what it writes, as a trailing options argument the dev and observe runtimes read and production ignores. An attribute effect gets `<tag>.<attribute>` as written — `<span textContent={label()} />` compiles to `effect(() => label(), v => …, { name: "span.textContent" })`, `class:active` to `div.class:active`, a `style={{ color: c() }}` property to `div.style:color` — and a template's merged effect lists all of its bindings (`"button.class, span.textContent"`). A hole's insert is named for the parent it fills: `<div>{count()}</div>` compiles to `insert(el, count, undefined, undefined, { name: "div.children" })`; a static child (a component call, a literal) creates no effect and gets no name. A spread passes the tag as its trailing argument (`spread(el, props, false, undefined, "div")`), and the runtime labels its attribute effect `div.spread` and its children insert `div.children`. DOM output only.
|
|
139
|
+
- `locations`: where the JSX is written, beside each label the other two kinds emit, as `file:line:col` — the module path relative to [`root`](#root) with `/` separators, line and column 1-based (the stack-trace convention, so a dev server links `http://localhost:5173/src/App.tsx:18:3` to the right place). A component call gets it as a fourth argument: `<Home />` compiles to `createComponent(Home, props, "Home", "src/App.tsx:12:5")`, the location of `<Home`. A binding gets `loc` after `name` in its options: an attribute effect points at the attribute (`{ name: "div.class", loc: "src/App.tsx:14:7" }`; a merged effect at the first attribute it lists; a property split out of a `style={{…}}` / `class={{…}}` object at that object attribute), a hole's insert at its `{`, and a spread passes it after the tag (`spread(el, props, false, undefined, "div", "src/App.tsx:20:12")`), the first spread attribute's start. A location is only emitted where its label is, and only when Babel knows the module's `filename` (a file outside `root` gets a `../` path). The dev and observe runtimes print it in diagnostic reports; production ignores it.
|
|
139
140
|
|
|
140
141
|
These are the JSX-level kinds — what a JSX compiler can see. Naming the primitives themselves (`createSignal(0, { name: "count" })`, `createCounter.value` inside a composed primitive) is not a JSX-transform feature and this plugin does not do it: it is `@solidjs/compiler`'s standalone `transformSourceNames` pass, which the build tool runs on every module — `.ts`, `.js`, and JSX files alike — independently of which JSX compiler handles the file. `@solidjs/vite-plugin` (its `solid: { sourceNames }` option, solid-vite-plugin #371) wires both: the JSX-level kinds through this option and primitive names through the standalone pass, for its dev and `observe` postures.
|
|
141
142
|
|
|
143
|
+
### root
|
|
144
|
+
|
|
145
|
+
- Type: `string`
|
|
146
|
+
- Default: the working directory (Babel's `cwd`)
|
|
147
|
+
|
|
148
|
+
Project root that `sourceNames.locations` makes the module path relative to — the build tool passes its project root so the location is a dev-server path. A relative `root` is resolved against the working directory.
|
|
149
|
+
|
|
142
150
|
### delegateEvents
|
|
143
151
|
|
|
144
152
|
- Type: `boolean`
|
|
@@ -248,13 +256,6 @@ const template = <div>Hello</div>;
|
|
|
248
256
|
|
|
249
257
|
Inline style attributes in templates when the value is a string or `Record<string, string>`. Disable for strong CSP configurations; styles are then set at runtime.
|
|
250
258
|
|
|
251
|
-
### serverComponents
|
|
252
|
-
|
|
253
|
-
- Type: `boolean`
|
|
254
|
-
- Default: `false`
|
|
255
|
-
|
|
256
|
-
SSR-only: keep attribute-slot positions on intrinsic elements bindable — `ref` / `onXxx` handlers 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
|
-
|
|
258
259
|
### hoistProps
|
|
259
260
|
|
|
260
261
|
- Type: `boolean`
|
package/index.js
CHANGED
|
@@ -1654,6 +1654,23 @@ attributes,
|
|
|
1654
1654
|
|
|
1655
1655
|
|
|
1656
1656
|
|
|
1657
|
+
|
|
1658
|
+
|
|
1659
|
+
|
|
1660
|
+
|
|
1661
|
+
|
|
1662
|
+
|
|
1663
|
+
|
|
1664
|
+
|
|
1665
|
+
|
|
1666
|
+
|
|
1667
|
+
|
|
1668
|
+
|
|
1669
|
+
|
|
1670
|
+
|
|
1671
|
+
|
|
1672
|
+
|
|
1673
|
+
|
|
1657
1674
|
|
|
1658
1675
|
|
|
1659
1676
|
|
|
@@ -1706,7 +1723,6 @@ const config = {
|
|
|
1706
1723
|
memoWrapper: "memo",
|
|
1707
1724
|
validate: true,
|
|
1708
1725
|
inlineStyles: true,
|
|
1709
|
-
serverComponents: false,
|
|
1710
1726
|
hoistProps: true
|
|
1711
1727
|
};
|
|
1712
1728
|
|
|
@@ -1717,8 +1733,12 @@ const config = {
|
|
|
1717
1733
|
*/
|
|
1718
1734
|
function sourceNames(config) {
|
|
1719
1735
|
const value = config.sourceNames ?? config.dev;
|
|
1720
|
-
if (typeof value === "boolean") return { components: value, bindings: value };
|
|
1721
|
-
return {
|
|
1736
|
+
if (typeof value === "boolean") return { components: value, bindings: value, locations: value };
|
|
1737
|
+
return {
|
|
1738
|
+
components: value.components ?? config.dev,
|
|
1739
|
+
bindings: value.bindings ?? config.dev,
|
|
1740
|
+
locations: value.locations ?? config.dev
|
|
1741
|
+
};
|
|
1722
1742
|
}
|
|
1723
1743
|
|
|
1724
1744
|
/**
|
|
@@ -1731,6 +1751,74 @@ function namesBindings(config) {
|
|
|
1731
1751
|
return config.generate === "dom" && sourceNames(config).bindings;
|
|
1732
1752
|
}
|
|
1733
1753
|
|
|
1754
|
+
/**
|
|
1755
|
+
* The `sourceNames.locations` string for `node`: `<file>:<line>:<column>`,
|
|
1756
|
+
* the file relative to `config.root` (default: Babel's `cwd`, the working
|
|
1757
|
+
* directory) with posix separators, line and column both 1-based — the
|
|
1758
|
+
* stack-trace / editor convention, so a browser links
|
|
1759
|
+
* `http://localhost:5173/src/App.tsx:18:3` to the right place. Babel's
|
|
1760
|
+
* `loc.start.column` is 0-based, so it is bumped by one here (the refresh
|
|
1761
|
+
* pass's `location` keeps Babel's 0-based column; that is a different
|
|
1762
|
+
* consumer). A file outside `root` yields a path starting with `..`, emitted
|
|
1763
|
+
* as-is. `undefined` when the kind is off, when Babel was given no `filename`
|
|
1764
|
+
* for the module, or when `node` is synthetic and carries no `loc`.
|
|
1765
|
+
*
|
|
1766
|
+
* `@solidjs/compiler` computes the same string with `relative_id` so both
|
|
1767
|
+
* compilers print identical output.
|
|
1768
|
+
*/
|
|
1769
|
+
function sourceLocation(path, node) {
|
|
1770
|
+
const config = getConfig(path);
|
|
1771
|
+
if (!sourceNames(config).locations) return;
|
|
1772
|
+
const start = node.loc?.start;
|
|
1773
|
+
if (!start) return;
|
|
1774
|
+
const opts = path.hub.file?.opts;
|
|
1775
|
+
const filename = opts?.filename;
|
|
1776
|
+
if (!filename) return;
|
|
1777
|
+
const cwd = opts?.cwd ?? "";
|
|
1778
|
+
const root = config.root ? resolve(cwd, config.root) : resolve(cwd, cwd);
|
|
1779
|
+
return `${relative(root, resolve(cwd, filename))}:${start.line}:${start.column + 1}`;
|
|
1780
|
+
}
|
|
1781
|
+
|
|
1782
|
+
/**
|
|
1783
|
+
* A binding's `{ name, loc? }` options literal — `name` first, then `loc`
|
|
1784
|
+
* only when there is one, so both JSX compilers print the same text.
|
|
1785
|
+
*/
|
|
1786
|
+
function bindingOptions(name, loc) {
|
|
1787
|
+
const properties = [t__namespace.objectProperty(t__namespace.identifier("name"), t__namespace.stringLiteral(name))];
|
|
1788
|
+
if (loc !== undefined)
|
|
1789
|
+
properties.push(t__namespace.objectProperty(t__namespace.identifier("loc"), t__namespace.stringLiteral(loc)));
|
|
1790
|
+
return t__namespace.objectExpression(properties);
|
|
1791
|
+
}
|
|
1792
|
+
|
|
1793
|
+
// Path arithmetic without node's `path` (this plugin bundles for any host):
|
|
1794
|
+
// the same steps as `@solidjs/compiler`'s `relative_id` — resolve both paths
|
|
1795
|
+
// against `cwd`, fold `.`/`..`, drop the common prefix.
|
|
1796
|
+
|
|
1797
|
+
/** `p` resolved against `base` (posix separators, `.`/`..` folded), as segments. */
|
|
1798
|
+
function resolve(base, p) {
|
|
1799
|
+
const posix = p.replace(/\\/g, "/");
|
|
1800
|
+
const absolute = /^(\/|[A-Za-z]:\/)/.test(posix);
|
|
1801
|
+
const joined = absolute ? posix : `${base.replace(/\\/g, "/")}/${posix}`;
|
|
1802
|
+
const segments = [];
|
|
1803
|
+
for (const segment of joined.split("/")) {
|
|
1804
|
+
if (segment === "." || segment === "" && segments.length) continue;
|
|
1805
|
+
if (segment === "..") {
|
|
1806
|
+
// Never past the root marker (`""` for `/…`, `C:` for a drive).
|
|
1807
|
+
if (segments.length > 1) segments.pop();
|
|
1808
|
+
continue;
|
|
1809
|
+
}
|
|
1810
|
+
segments.push(segment);
|
|
1811
|
+
}
|
|
1812
|
+
return segments;
|
|
1813
|
+
}
|
|
1814
|
+
|
|
1815
|
+
/** `file` relative to `root`, both resolved: `..` per root segment left, then the file's rest. */
|
|
1816
|
+
function relative(root, file) {
|
|
1817
|
+
let i = 0;
|
|
1818
|
+
while (i < root.length && i < file.length && root[i] === file[i]) i++;
|
|
1819
|
+
return [...root.slice(i).map(() => ".."), ...file.slice(i)].join("/");
|
|
1820
|
+
}
|
|
1821
|
+
|
|
1734
1822
|
const InlineElements = [
|
|
1735
1823
|
"a",
|
|
1736
1824
|
"abbr",
|
|
@@ -2498,6 +2586,8 @@ results)
|
|
|
2498
2586
|
t$1.jsxExpressionContainer(p.value)
|
|
2499
2587
|
);
|
|
2500
2588
|
newAttr._styleProperty = true;
|
|
2589
|
+
// The split-out property is located at the `style` attribute it came from.
|
|
2590
|
+
newAttr.loc = styleObjectAttribute.node.loc;
|
|
2501
2591
|
path.
|
|
2502
2592
|
get("openingElement").
|
|
2503
2593
|
node.attributes.splice(Number(styleObjectAttribute.key) + ++i, 0, newAttr);
|
|
@@ -2548,13 +2638,16 @@ results)
|
|
|
2548
2638
|
))
|
|
2549
2639
|
{
|
|
2550
2640
|
classArrayAttribute.node.value = t$1.stringLiteral(staticClasses.join(" "));
|
|
2641
|
+
const dynamicClassAttribute = t$1.jsxAttribute(
|
|
2642
|
+
t$1.jsxIdentifier("class"),
|
|
2643
|
+
t$1.jsxExpressionContainer(dynamicClassElement)
|
|
2644
|
+
);
|
|
2645
|
+
// `sourceNames.locations`: the split-out object locates at the array
|
|
2646
|
+
// attribute it came from.
|
|
2647
|
+
dynamicClassAttribute.loc = classArrayAttribute.node.loc;
|
|
2551
2648
|
path.
|
|
2552
2649
|
get("openingElement").
|
|
2553
|
-
node.attributes.splice(
|
|
2554
|
-
Number(classArrayAttribute.key) + 1,
|
|
2555
|
-
0,
|
|
2556
|
-
t$1.jsxAttribute(t$1.jsxIdentifier("class"), t$1.jsxExpressionContainer(dynamicClassElement))
|
|
2557
|
-
);
|
|
2650
|
+
node.attributes.splice(Number(classArrayAttribute.key) + 1, 0, dynamicClassAttribute);
|
|
2558
2651
|
}
|
|
2559
2652
|
}
|
|
2560
2653
|
|
|
@@ -2600,6 +2693,8 @@ results)
|
|
|
2600
2693
|
t$1.jsxExpressionContainer(p.value)
|
|
2601
2694
|
);
|
|
2602
2695
|
newAttr._classProperty = true;
|
|
2696
|
+
// The split-out property is located at the `class` attribute it came from.
|
|
2697
|
+
newAttr.loc = classListAttribute.node.loc;
|
|
2603
2698
|
path.
|
|
2604
2699
|
get("openingElement").
|
|
2605
2700
|
node.attributes.splice(Number(classListAttribute.key) + ++i, 0, newAttr);
|
|
@@ -3008,7 +3103,11 @@ results)
|
|
|
3008
3103
|
tagName,
|
|
3009
3104
|
checkableInput,
|
|
3010
3105
|
styleProperty: isStyleProperty,
|
|
3011
|
-
classProperty: isClassProperty
|
|
3106
|
+
classProperty: isClassProperty,
|
|
3107
|
+
// `sourceNames.locations`: the attribute's start (for a property
|
|
3108
|
+
// split out of a `style={{…}}` / `class={{…}}` object, the
|
|
3109
|
+
// object attribute's start — see the pre-passes above).
|
|
3110
|
+
loc: sourceLocation(attribute, attribute.node)
|
|
3012
3111
|
});
|
|
3013
3112
|
} else {
|
|
3014
3113
|
results.exprs.push(
|
|
@@ -3169,6 +3268,10 @@ config)
|
|
|
3169
3268
|
if (!transformed) return memo;
|
|
3170
3269
|
transformed.allocatesIds = allocatesIds;
|
|
3171
3270
|
transformed.functionHole = functionHole;
|
|
3271
|
+
// `sourceNames.locations`: where the hole is written, for its insert
|
|
3272
|
+
// (a spread child `{...items()}` is a hole too).
|
|
3273
|
+
if (child.isJSXExpressionContainer() || child.isJSXSpreadChild())
|
|
3274
|
+
transformed.loc = sourceLocation(child, child.node);
|
|
3172
3275
|
const i = memo.length;
|
|
3173
3276
|
if (transformed.text && i && memo[i - 1].text) {
|
|
3174
3277
|
memo[i - 1].template =
|
|
@@ -3252,14 +3355,11 @@ config)
|
|
|
3252
3355
|
// is inserted directly and creates no effect to name.
|
|
3253
3356
|
const accessorHole =
|
|
3254
3357
|
child.dynamic || child.functionHole;
|
|
3358
|
+
// `sourceNames.locations`: `loc` after `name`, the hole's `{` start.
|
|
3255
3359
|
const emitInsert = (args) => {
|
|
3256
3360
|
if (accessorHole && namesBindings(getConfig(path))) {
|
|
3257
3361
|
while (args.length < 4) args.push(t$1.identifier("undefined"));
|
|
3258
|
-
args.push(
|
|
3259
|
-
t$1.objectExpression([
|
|
3260
|
-
t$1.objectProperty(t$1.identifier("name"), t$1.stringLiteral(`${tagName}.children`))]
|
|
3261
|
-
)
|
|
3262
|
-
);
|
|
3362
|
+
args.push(bindingOptions(`${tagName}.children`, child.loc));
|
|
3263
3363
|
}
|
|
3264
3364
|
results.exprs.push(t$1.expressionStatement(t$1.callExpression(insert, args)));
|
|
3265
3365
|
};
|
|
@@ -3559,7 +3659,14 @@ attributes,
|
|
|
3559
3659
|
// `sourceNames.bindings`: the tag as written rides as spread's trailing
|
|
3560
3660
|
// argument (past the runtime-only `skip` slot); the runtime labels its
|
|
3561
3661
|
// attribute effect `<tag>.spread` and its children insert `<tag>.children`.
|
|
3562
|
-
|
|
3662
|
+
// `sourceNames.locations`: the first spread attribute's start (`{...`)
|
|
3663
|
+
// follows the name as one more string argument.
|
|
3664
|
+
if (namesBindings(config)) {
|
|
3665
|
+
args.push(t$1.identifier("undefined"), t$1.stringLiteral(tagName));
|
|
3666
|
+
const spread = attributes.find((a) => t$1.isJSXSpreadAttribute(a.node));
|
|
3667
|
+
const loc = spread && sourceLocation(spread, spread.node);
|
|
3668
|
+
if (loc !== undefined) args.push(t$1.stringLiteral(loc));
|
|
3669
|
+
}
|
|
3563
3670
|
|
|
3564
3671
|
return [
|
|
3565
3672
|
filteredAttributes,
|
|
@@ -3775,20 +3882,18 @@ ownerId)
|
|
|
3775
3882
|
// binding's `<tag>.<attribute>` as written (undoing the `prop:` the locked
|
|
3776
3883
|
// DOM property pre-pass added to `value`, `checked`, …), the merged effect
|
|
3777
3884
|
// listing all of its bindings — as a trailing `{ name }` options argument.
|
|
3885
|
+
// `sourceNames.locations` adds `loc` after it: the first binding's
|
|
3886
|
+
// attribute start (a merged effect points at the first attribute it lists).
|
|
3778
3887
|
const label = namesBindings(config) ?
|
|
3779
3888
|
[
|
|
3780
|
-
|
|
3781
|
-
|
|
3782
|
-
|
|
3783
|
-
|
|
3784
|
-
|
|
3785
|
-
|
|
3786
|
-
|
|
3787
|
-
|
|
3788
|
-
).
|
|
3789
|
-
join(", ")
|
|
3790
|
-
)
|
|
3791
|
-
)]
|
|
3889
|
+
bindingOptions(
|
|
3890
|
+
dynamics.
|
|
3891
|
+
map(
|
|
3892
|
+
(d) =>
|
|
3893
|
+
`${d.tagName}.${isLockedDOMProperty(d.tagName, d.key) ? d.key.replace(/^prop:/, "") : d.key}`
|
|
3894
|
+
).
|
|
3895
|
+
join(", "),
|
|
3896
|
+
dynamics.find((d) => d.loc !== undefined)?.loc
|
|
3792
3897
|
)] :
|
|
3793
3898
|
|
|
3794
3899
|
[];
|
|
@@ -5159,13 +5264,6 @@ info)
|
|
|
5159
5264
|
// holes evaluated. The hole never takes a hydration id.
|
|
5160
5265
|
const linkEntries =
|
|
5161
5266
|
tagName === "a" && isLinkClaimCandidate(attributes) ? [] : null;
|
|
5162
|
-
// Server-components handler positions: ref/on* expressions on
|
|
5163
|
-
// server-rendered intrinsics collect here and emit as one guarded
|
|
5164
|
-
// whole-attribute hole after the loop, where `ssrClaim` turns attribute-slot
|
|
5165
|
-
// reads into `_s:on:*` / `_s:ref` markers (and drops server-local
|
|
5166
|
-
// functions). Evaluation is gated on the render context's claims flag so
|
|
5167
|
-
// plain SSR never runs the expressions.
|
|
5168
|
-
const claims = [];
|
|
5169
5267
|
|
|
5170
5268
|
attributes.forEach((attribute) => {
|
|
5171
5269
|
if (!t.isJSXAttribute(attribute.node)) return;
|
|
@@ -5196,23 +5294,13 @@ info)
|
|
|
5196
5294
|
{
|
|
5197
5295
|
if (t.isJSXEmptyExpression(value.expression)) return;
|
|
5198
5296
|
if (key === "ref") {
|
|
5199
|
-
if (info.serverComponents) {
|
|
5200
|
-
claims.push(["ref", value.expression]);
|
|
5201
|
-
return;
|
|
5202
|
-
}
|
|
5203
5297
|
results.declarations.push(
|
|
5204
5298
|
t.variableDeclarator(path.scope.generateUidIdentifier("_ref$"), value.expression)
|
|
5205
5299
|
);
|
|
5206
5300
|
return;
|
|
5207
5301
|
}
|
|
5208
5302
|
if (key.startsWith("prop:")) return;
|
|
5209
|
-
if (isEventName(key))
|
|
5210
|
-
// `onXxx` lowercases to the event name — the client runtime's own
|
|
5211
|
-
// derivation (`onClick` -> `click`); the position is bound under it.
|
|
5212
|
-
if (info.serverComponents)
|
|
5213
|
-
claims.push([toEventName(key), value.expression]);
|
|
5214
|
-
return;
|
|
5215
|
-
}
|
|
5303
|
+
if (isEventName(key)) return;
|
|
5216
5304
|
if (ChildProperties.has(key)) {
|
|
5217
5305
|
if (key === "children" && VoidElements.has(tagName)) return;
|
|
5218
5306
|
if (info.hydratable && key === "textContent" && value && value.expression) {
|
|
@@ -5260,30 +5348,9 @@ info)
|
|
|
5260
5348
|
value.expression = t.assignmentExpression("=", temp, expression);
|
|
5261
5349
|
linkEntries.push([key, t.cloneNode(temp), !!isDynamicValue]);
|
|
5262
5350
|
}
|
|
5263
|
-
// Server components (principles §9.2.3) need a whole-attribute
|
|
5264
|
-
// serializer that can emit a position marker for a binding-slot
|
|
5265
|
-
// value. The ordinary class/style serializers cannot bind slots.
|
|
5266
|
-
// Object literals stay objects (no inlining) so a slot used as a
|
|
5267
|
-
// property's value can also emit its marker.
|
|
5268
|
-
if (info.serverComponents && (key === "class" || key === "style")) {
|
|
5269
|
-
const attr = t.callExpression(registerImportMethod(path, "ssrElementAttribute"), [
|
|
5270
|
-
t.stringLiteral(key),
|
|
5271
|
-
value.expression]
|
|
5272
|
-
);
|
|
5273
|
-
results.template.push("");
|
|
5274
|
-
results.templateValues.push(
|
|
5275
|
-
isDynamicValue ?
|
|
5276
|
-
hoistExpression(path, results, t.arrowFunctionExpression([], attr), {
|
|
5277
|
-
group: true
|
|
5278
|
-
}) :
|
|
5279
|
-
attr
|
|
5280
|
-
);
|
|
5281
|
-
return;
|
|
5282
|
-
}
|
|
5283
5351
|
if ((key === "class" || key === "style") && !t.isObjectExpression(value.expression)) {
|
|
5284
|
-
// The whole attribute must be absent for nullish values.
|
|
5285
|
-
//
|
|
5286
|
-
// without serverComponents. Capture the value once before testing it.
|
|
5352
|
+
// The whole attribute must be absent for nullish values. Capture the
|
|
5353
|
+
// value once before testing it.
|
|
5287
5354
|
const id = path.scope.generateUidIdentifier("v$");
|
|
5288
5355
|
const attr = t.callExpression(
|
|
5289
5356
|
t.arrowFunctionExpression(
|
|
@@ -5492,28 +5559,6 @@ info)
|
|
|
5492
5559
|
results.template.push("");
|
|
5493
5560
|
results.templateValues.push(hole);
|
|
5494
5561
|
}
|
|
5495
|
-
if (claims.length) {
|
|
5496
|
-
const map = claimMap(claims);
|
|
5497
|
-
// `_$sharedConfig.context && _$sharedConfig.context.claims
|
|
5498
|
-
// ? _$ssrClaim({...}) : ""`
|
|
5499
|
-
// — the claims flag is only set inside a server component's render
|
|
5500
|
-
// scope (and cleared inside suppressed fill windows), so ordinary SSR
|
|
5501
|
-
// pays the property reads and never evaluates the expressions.
|
|
5502
|
-
const sharedConfigId = registerImportMethod(path, "sharedConfig");
|
|
5503
|
-
const contextRead = () =>
|
|
5504
|
-
t.memberExpression(t.cloneNode(sharedConfigId), t.identifier("context"));
|
|
5505
|
-
const guarded = t.conditionalExpression(
|
|
5506
|
-
t.logicalExpression(
|
|
5507
|
-
"&&",
|
|
5508
|
-
contextRead(),
|
|
5509
|
-
t.memberExpression(contextRead(), t.identifier("claims"))
|
|
5510
|
-
),
|
|
5511
|
-
t.callExpression(registerImportMethod(path, "ssrClaim"), [map]),
|
|
5512
|
-
t.stringLiteral("")
|
|
5513
|
-
);
|
|
5514
|
-
results.template.push("");
|
|
5515
|
-
results.templateValues.push(hoistExpression(path, results, guarded));
|
|
5516
|
-
}
|
|
5517
5562
|
if (!hasChildren && children) {
|
|
5518
5563
|
path.node.children.push(children);
|
|
5519
5564
|
}
|
|
@@ -5639,7 +5684,7 @@ results,
|
|
|
5639
5684
|
|
|
5640
5685
|
function createElement(
|
|
5641
5686
|
path,
|
|
5642
|
-
{ topLevel, hydratable
|
|
5687
|
+
{ topLevel, hydratable })
|
|
5643
5688
|
{
|
|
5644
5689
|
const tagName = getTagName(path.node),
|
|
5645
5690
|
config = getConfig(path),
|
|
@@ -5692,22 +5737,6 @@ path,
|
|
|
5692
5737
|
const node = attribute.node;
|
|
5693
5738
|
return !(t.isJSXAttribute(node) && t.isJSXIdentifier(node.name) && node.name.name === "ref");
|
|
5694
5739
|
});
|
|
5695
|
-
// Server components (principles §9.2.3): the named `ref`/`on*` attributes
|
|
5696
|
-
// of a spread element are handler positions like a template element's,
|
|
5697
|
-
// and compile to the same claim map — `{ click: expr, ref: [a, b] }`,
|
|
5698
|
-
// duplicate refs merged, a duplicate handler last-wins as the template
|
|
5699
|
-
// path strips it — keyed by the index of the source each attribute sits
|
|
5700
|
-
// before (`<b {...a} onClick={go} {...b}>` → `{ 1: { click: go } }`, the
|
|
5701
|
-
// spreads being sources 0 and 2), and handed to `ssrElement` as a thunk
|
|
5702
|
-
// it reads only inside a server component's render (the gate the template
|
|
5703
|
-
// path's `ssrClaim` guard reads), so plain SSR never evaluates the
|
|
5704
|
-
// expressions. The runtime settles each handler position in source order,
|
|
5705
|
-
// as the client's `spread(el, [a, { onClick: go }, b])` does — a spread at
|
|
5706
|
-
// that index or later that HAS the key owns it — and merges every ref.
|
|
5707
|
-
// Plain SSR output is unchanged (dropped, as a server element has no
|
|
5708
|
-
// handlers to run).
|
|
5709
|
-
const claims = [];
|
|
5710
|
-
|
|
5711
5740
|
let props;
|
|
5712
5741
|
// Attributes written AFTER the last spread are markup, not a source: no
|
|
5713
5742
|
// later source can override them, so they go to `ssrElement` as its
|
|
@@ -5727,13 +5756,26 @@ path,
|
|
|
5727
5756
|
// keys.
|
|
5728
5757
|
const tail = [];
|
|
5729
5758
|
const skipKeys = [];
|
|
5730
|
-
|
|
5731
|
-
|
|
5732
|
-
|
|
5733
|
-
//
|
|
5734
|
-
// the
|
|
5735
|
-
|
|
5736
|
-
{
|
|
5759
|
+
// Plain SSR evaluates a named `ref` expression without calling the ref, as
|
|
5760
|
+
// the template path does (`_ref$ = expr`), so a ref factory's owned setup
|
|
5761
|
+
// (`ref={tooltip()}`) runs on both sides and the hydration ids after it
|
|
5762
|
+
// line up (#4081). The client evaluates it after applying the spread and
|
|
5763
|
+
// before the children; `ssrElement` calls the `attrs` thunk at that point.
|
|
5764
|
+
// Only an expression that calls something can run setup: `ref={el}` and
|
|
5765
|
+
// `ref={props.ref}` stay out of the thunk.
|
|
5766
|
+
const refs = [];
|
|
5767
|
+
for (const attribute of attributes) {
|
|
5768
|
+
const node = attribute.node;
|
|
5769
|
+
if (
|
|
5770
|
+
t.isJSXAttribute(node) &&
|
|
5771
|
+
t.isJSXIdentifier(node.name, { name: "ref" }) &&
|
|
5772
|
+
t.isJSXExpressionContainer(node.value) &&
|
|
5773
|
+
!t.isJSXEmptyExpression(node.value.expression) &&
|
|
5774
|
+
callsOutsideFunctions(node.value.expression))
|
|
5775
|
+
|
|
5776
|
+
refs.push(node.value.expression);
|
|
5777
|
+
}
|
|
5778
|
+
if (propAttributes.length === 1 && t.isJSXSpreadAttribute(propAttributes[0].node)) {
|
|
5737
5779
|
props = [propAttributes[0].node.argument];
|
|
5738
5780
|
} else {
|
|
5739
5781
|
props = [];
|
|
@@ -5779,28 +5821,7 @@ path,
|
|
|
5779
5821
|
node.name.name;
|
|
5780
5822
|
|
|
5781
5823
|
if (hasChildren && key === "children") return;
|
|
5782
|
-
if (key === "ref" || isEventName(key))
|
|
5783
|
-
if (serverComponents && t.isJSXExpressionContainer(value)) {
|
|
5784
|
-
const expression = value.expression;
|
|
5785
|
-
const pos = key === "ref" ? "ref" : toEventName(key);
|
|
5786
|
-
if (
|
|
5787
|
-
!(
|
|
5788
|
-
t.isJSXEmptyExpression(expression) ||
|
|
5789
|
-
t.isStringLiteral(expression) ||
|
|
5790
|
-
t.isNumericLiteral(expression) ||
|
|
5791
|
-
t.isBooleanLiteral(expression)))
|
|
5792
|
-
|
|
5793
|
-
{
|
|
5794
|
-
// The index of the source this attribute sits before: the
|
|
5795
|
-
// sources pushed so far, plus the running literal if it will
|
|
5796
|
-
// be pushed ahead of the next spread. The literal never
|
|
5797
|
-
// carries a handler key, so a spread's index is either below
|
|
5798
|
-
// this or at/after it — never ambiguous.
|
|
5799
|
-
claims.push([props.length + (runningObject.length ? 1 : 0), pos, expression]);
|
|
5800
|
-
}
|
|
5801
|
-
}
|
|
5802
|
-
return;
|
|
5803
|
-
}
|
|
5824
|
+
if (key === "ref" || isEventName(key)) return;
|
|
5804
5825
|
if (key.startsWith("prop:")) return;
|
|
5805
5826
|
// An anchor's link-relevant attributes stay a source, never baked
|
|
5806
5827
|
// markup: `ssrElement` collects them from the walk for the render's
|
|
@@ -5869,76 +5890,52 @@ path,
|
|
|
5869
5890
|
// set) and attribute string — a literal when every part is static, else a
|
|
5870
5891
|
// thunk concatenating the parts in source order. A `false` static bakes to
|
|
5871
5892
|
// no markup but still skips its key.
|
|
5872
|
-
if (skipKeys.length) {
|
|
5893
|
+
if (skipKeys.length || refs.length) {
|
|
5873
5894
|
let markup;
|
|
5874
|
-
if (tail.every((part) => typeof part === "string"))
|
|
5875
|
-
|
|
5895
|
+
if (tail.every((part) => typeof part === "string")) {
|
|
5896
|
+
markup = t.stringLiteral(tail.join(""));
|
|
5897
|
+
// The refs run after the tail's (static) markup and return it.
|
|
5898
|
+
if (refs.length)
|
|
5899
|
+
markup = t.arrowFunctionExpression([], t.sequenceExpression([...refs, markup]));
|
|
5900
|
+
} else {
|
|
5876
5901
|
let concat;
|
|
5877
5902
|
for (const part of tail) {
|
|
5878
5903
|
const expr = typeof part === "string" ? t.stringLiteral(part) : part;
|
|
5879
5904
|
concat = concat ? t.binaryExpression("+", concat, expr) : expr;
|
|
5880
5905
|
}
|
|
5906
|
+
// The tail's getters run first, as the client applies them with the
|
|
5907
|
+
// spread before it evaluates the ref: `markup + (ref, "")`.
|
|
5908
|
+
if (refs.length)
|
|
5909
|
+
concat = t.binaryExpression(
|
|
5910
|
+
"+",
|
|
5911
|
+
concat,
|
|
5912
|
+
t.sequenceExpression([...refs, t.stringLiteral("")])
|
|
5913
|
+
);
|
|
5881
5914
|
markup = t.arrowFunctionExpression([], concat);
|
|
5882
5915
|
}
|
|
5883
|
-
args.push(registerSkip(path, skipKeys), markup);
|
|
5884
|
-
}
|
|
5885
|
-
if (claims.length) {
|
|
5886
|
-
if (!skipKeys.length) args.push(t.identifier("undefined"), t.identifier("undefined"));
|
|
5887
|
-
args.push(t.arrowFunctionExpression([], claimSegments(claims)));
|
|
5916
|
+
args.push(skipKeys.length ? registerSkip(path, skipKeys) : t.identifier("undefined"), markup);
|
|
5888
5917
|
}
|
|
5889
5918
|
const exprs = [t.callExpression(registerImportMethod(path, "ssrElement"), args)];
|
|
5890
5919
|
return { exprs, template: "", declarations: [], dynamics: [], spreadElement: true };
|
|
5891
5920
|
}
|
|
5892
5921
|
|
|
5893
|
-
/**
|
|
5894
|
-
|
|
5895
|
-
|
|
5896
|
-
|
|
5897
|
-
|
|
5898
|
-
|
|
5899
|
-
|
|
5900
|
-
*/
|
|
5901
|
-
function claimSegments(
|
|
5902
|
-
claims)
|
|
5903
|
-
{
|
|
5904
|
-
const lastHandler = new Map();
|
|
5905
|
-
claims.forEach(([, pos], i) => {
|
|
5906
|
-
if (pos !== "ref") lastHandler.set(pos, i);
|
|
5907
|
-
});
|
|
5908
|
-
const segments = new Map();
|
|
5909
|
-
claims.forEach(([index, pos, expr], i) => {
|
|
5910
|
-
if (pos !== "ref" && lastHandler.get(pos) !== i) return;
|
|
5911
|
-
let list = segments.get(index);
|
|
5912
|
-
if (!list) segments.set(index, list = []);
|
|
5913
|
-
list.push([pos, expr]);
|
|
5914
|
-
});
|
|
5915
|
-
return t.objectExpression(
|
|
5916
|
-
[...segments].map(([index, list]) => t.objectProperty(t.numericLiteral(index), claimMap(list)))
|
|
5917
|
-
);
|
|
5918
|
-
}
|
|
5922
|
+
/** Does evaluating `node` call anything? A function's body does not run. */
|
|
5923
|
+
function callsOutsideFunctions(node) {
|
|
5924
|
+
if (
|
|
5925
|
+
t.isCallExpression(node) ||
|
|
5926
|
+
t.isOptionalCallExpression(node) ||
|
|
5927
|
+
t.isNewExpression(node) ||
|
|
5928
|
+
t.isTaggedTemplateExpression(node))
|
|
5919
5929
|
|
|
5920
|
-
|
|
5921
|
-
|
|
5922
|
-
|
|
5923
|
-
|
|
5924
|
-
|
|
5925
|
-
|
|
5926
|
-
|
|
5927
|
-
|
|
5928
|
-
|
|
5929
|
-
for (const [pos, expr] of claims) {
|
|
5930
|
-
let list = byPos.get(pos);
|
|
5931
|
-
if (!list) byPos.set(pos, list = []);
|
|
5932
|
-
list.push(expr);
|
|
5933
|
-
}
|
|
5934
|
-
return t.objectExpression(
|
|
5935
|
-
[...byPos].map(([pos, exprs]) =>
|
|
5936
|
-
t.objectProperty(
|
|
5937
|
-
t.stringLiteral(pos),
|
|
5938
|
-
exprs.length === 1 ? exprs[0] : t.arrayExpression(exprs)
|
|
5939
|
-
)
|
|
5940
|
-
)
|
|
5941
|
-
);
|
|
5930
|
+
return true;
|
|
5931
|
+
if (t.isFunction(node)) return false;
|
|
5932
|
+
for (const key of t.VISITOR_KEYS[node.type] ?? []) {
|
|
5933
|
+
const child = node[key];
|
|
5934
|
+
if (Array.isArray(child)) {
|
|
5935
|
+
for (const c of child) if (c && callsOutsideFunctions(c)) return true;
|
|
5936
|
+
} else if (child && typeof child.type === "string" && callsOutsideFunctions(child)) return true;
|
|
5937
|
+
}
|
|
5938
|
+
return false;
|
|
5942
5939
|
}
|
|
5943
5940
|
|
|
5944
5941
|
/**
|
|
@@ -6451,6 +6448,10 @@ path)
|
|
|
6451
6448
|
sourceNames(config).components && (config.generate === "dom" || config.generate === "ssr");
|
|
6452
6449
|
if (labelled) {
|
|
6453
6450
|
componentArgs.push(t__namespace.stringLiteral(tagName));
|
|
6451
|
+
// `sourceNames.locations`: the opening element's start (`<Home`) as a
|
|
6452
|
+
// fourth argument, `file:line:col`, so a diagnostic can point at the JSX.
|
|
6453
|
+
const loc = sourceLocation(path, path.node.openingElement);
|
|
6454
|
+
if (loc !== undefined) componentArgs.push(t__namespace.stringLiteral(loc));
|
|
6454
6455
|
}
|
|
6455
6456
|
// SSR's prod `createComponent` is literally `Comp(props || {})`. Since the
|
|
6456
6457
|
// compiler always emits a real `props[0]` object expression above (see the
|