@solidjs/babel-plugin 2.0.0-rc.14 → 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 +24 -0
- package/README.md +9 -8
- package/index.js +535 -224
- 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 [1m3.
|
|
10
|
+
[32mcreated [1mindex.js[22m in [1m3.3s[22m[39m
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,29 @@
|
|
|
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
|
+
|
|
11
|
+
## 2.0.0-rc.15
|
|
12
|
+
|
|
13
|
+
### Patch Changes
|
|
14
|
+
|
|
15
|
+
- 617d195: Emit element claims after the element's initial attributes are applied (#3923), at parity across both compilers.
|
|
16
|
+
|
|
17
|
+
A fully static `a[href]` / `form[action]` is claimed at the end of its creation statements (after static writes and refs). An element with dynamic bindings captures its owner at creation (`var _o$ = _$getOwner()`) and is claimed as the last statement of its binding effect's callback — `_$claimElement(_el$, _o$)` — in both the single-binding and the multi-binding shapes, so the claim observes the applied values on the first run whether that run is synchronous or lands from a held flush. An element carrying a spread emits no compiled claim: the `spread` runtime claims it after the first application.
|
|
18
|
+
|
|
19
|
+
- ca15970: Non-hydratable client builds now remove the `value` attribute when a checkbox or radio input binds a nullish `value` or `defaultValue`, matching the runtime fix from #4009, so a checked box submits `"on"` instead of `""`. Both compilers route these bindings through `setProperty` when the input's static `type` is `checkbox` or `radio`, or when its `type` cannot be known at compile time (an expression, `prop:type`, `attr:type`). Text inputs, inputs without a `type`, and textareas still clear to `""` inline (#2957).
|
|
20
|
+
- 43570f8: Reapply a select's bound value when its options arrive after the value was written.
|
|
21
|
+
- 22f942d: SSR: a router can mark links in server HTML (#3878). Element claims never fire during SSR, so plain anchors arrived without `aria-current="page"` / `data-active` — late on hydrated pages, never on pages that do not hydrate. Both compilers now give every candidate `<a href>` in SSR output one hole after its attributes, `ssrLinkClaim(attrs)`, where a render's **link handler** writes the anchor's link state and `""` otherwise. A static anchor's hole is one eager call over a hoisted attributes object (no allocation per render); a dynamic anchor's joins the element's attribute group and reuses the raw value its `href` hole evaluated. Anchors the compiler can rule out — a static non-empty `target`, a `download`, a `rel` naming `external`, an author-written `aria-current`, an empty href or a non-HTTP scheme — get no hole at all. The hole creates no owner and takes no hydration id. A spread anchor's link attributes are collected by `ssrElement` from its winning sources (an anchor's trailing link attributes stay a source instead of baked tail markup).
|
|
22
|
+
|
|
23
|
+
New server API (`@experimental`): `setLinkClaim(handler)` sets the handler of the render the caller belongs to — found through the owner chain like `getHydrationWriter()`, so concurrent renders never share one — and returns `false` outside a render; `undefined` clears it. The handler, `(attrs) => string`, receives the anchor's link-relevant attributes (`href`, `target`, `rel`, `download`, `link`, `aria-current`, raw values as written) and returns its attribute markup. An author-written `aria-current` always wins. Server components inline in the document render under the page's handler; a server component's own frame stream has none unless its render sets one.
|
|
24
|
+
|
|
25
|
+
Trust marker: the first `setLinkClaim` of a render writes the `links` hydration record once (`_$HY.r.links = 1`, with the shell's hydration script on the document face, a data record on a frame stream). `hasServerLinkState()` (client; `false` on the server) reads it non-destructively, so a link consumer can tell "this page's anchors carry the server's link state" from "no handler ran" and trust the HTML at hydration instead of resolving every anchor's URL at claim. Without a handler the HTML and the hydration ids are byte-for-byte what they were.
|
|
26
|
+
|
|
3
27
|
## 2.0.0-rc.14
|
|
4
28
|
|
|
5
29
|
### 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
|
@@ -46,6 +46,21 @@ const ChildProperties = /*#__PURE__*/new Set([
|
|
|
46
46
|
"children"]
|
|
47
47
|
);
|
|
48
48
|
|
|
49
|
+
// The attributes a link handler decides an anchor's link state on
|
|
50
|
+
// (solidjs/solid#3878): the SSR compilers carry the ones present on a
|
|
51
|
+
// candidate `<a href>` into its `ssrLinkClaim` hole, and `ssrElement`
|
|
52
|
+
// collects the same set from a spread anchor's winning sources. `link` is
|
|
53
|
+
// the explicit-links opt-in marker; `aria-current` is the author's, and
|
|
54
|
+
// wins.
|
|
55
|
+
const LinkAttributes = /*#__PURE__*/new Set([
|
|
56
|
+
"href",
|
|
57
|
+
"target",
|
|
58
|
+
"rel",
|
|
59
|
+
"download",
|
|
60
|
+
"link",
|
|
61
|
+
"aria-current"]
|
|
62
|
+
);
|
|
63
|
+
|
|
49
64
|
// list of Element events that will be delegated
|
|
50
65
|
const DelegatedEvents = /*#__PURE__*/new Set([
|
|
51
66
|
"beforeinput",
|
|
@@ -1639,6 +1654,23 @@ attributes,
|
|
|
1639
1654
|
|
|
1640
1655
|
|
|
1641
1656
|
|
|
1657
|
+
|
|
1658
|
+
|
|
1659
|
+
|
|
1660
|
+
|
|
1661
|
+
|
|
1662
|
+
|
|
1663
|
+
|
|
1664
|
+
|
|
1665
|
+
|
|
1666
|
+
|
|
1667
|
+
|
|
1668
|
+
|
|
1669
|
+
|
|
1670
|
+
|
|
1671
|
+
|
|
1672
|
+
|
|
1673
|
+
|
|
1642
1674
|
|
|
1643
1675
|
|
|
1644
1676
|
|
|
@@ -1691,7 +1723,6 @@ const config = {
|
|
|
1691
1723
|
memoWrapper: "memo",
|
|
1692
1724
|
validate: true,
|
|
1693
1725
|
inlineStyles: true,
|
|
1694
|
-
serverComponents: false,
|
|
1695
1726
|
hoistProps: true
|
|
1696
1727
|
};
|
|
1697
1728
|
|
|
@@ -1702,8 +1733,12 @@ const config = {
|
|
|
1702
1733
|
*/
|
|
1703
1734
|
function sourceNames(config) {
|
|
1704
1735
|
const value = config.sourceNames ?? config.dev;
|
|
1705
|
-
if (typeof value === "boolean") return { components: value, bindings: value };
|
|
1706
|
-
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
|
+
};
|
|
1707
1742
|
}
|
|
1708
1743
|
|
|
1709
1744
|
/**
|
|
@@ -1716,6 +1751,74 @@ function namesBindings(config) {
|
|
|
1716
1751
|
return config.generate === "dom" && sourceNames(config).bindings;
|
|
1717
1752
|
}
|
|
1718
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
|
+
|
|
1719
1822
|
const InlineElements = [
|
|
1720
1823
|
"a",
|
|
1721
1824
|
"abbr",
|
|
@@ -1841,6 +1944,7 @@ const t$1 = t__namespace;
|
|
|
1841
1944
|
|
|
1842
1945
|
|
|
1843
1946
|
|
|
1947
|
+
|
|
1844
1948
|
|
|
1845
1949
|
|
|
1846
1950
|
function isNamedAttribute(
|
|
@@ -1977,6 +2081,7 @@ info = {})
|
|
|
1977
2081
|
declarations: [],
|
|
1978
2082
|
exprs: [],
|
|
1979
2083
|
dynamics: [],
|
|
2084
|
+
claims: [],
|
|
1980
2085
|
postExprs: [],
|
|
1981
2086
|
isImportNode,
|
|
1982
2087
|
isWrapped,
|
|
@@ -2054,13 +2159,25 @@ info = {})
|
|
|
2054
2159
|
if (!info.skipId) {
|
|
2055
2160
|
results.id = path.scope.generateUidIdentifier("el$");
|
|
2056
2161
|
}
|
|
2057
|
-
// Claim contract: a[href] / form[action] elements are claimed
|
|
2058
|
-
//
|
|
2059
|
-
//
|
|
2060
|
-
//
|
|
2061
|
-
//
|
|
2062
|
-
//
|
|
2063
|
-
|
|
2162
|
+
// Claim contract (#3923): a[href] / form[action] elements are claimed ONCE,
|
|
2163
|
+
// after their initial attributes are applied, so registered consumers
|
|
2164
|
+
// (e.g. a router's link-state layer) see the element they will manage.
|
|
2165
|
+
// Decided before the attributes are lowered — processSpreads folds a
|
|
2166
|
+
// spread's attributes away — and emitted after: a fully static element
|
|
2167
|
+
// (attributes in the template, writes at creation) is claimed at creation,
|
|
2168
|
+
// after those writes and its refs; an element with dynamic bindings at the
|
|
2169
|
+
// tail of the template root's binding effect, whose first run applies
|
|
2170
|
+
// them (wrapDynamics); a spread element by the spread runtime after the
|
|
2171
|
+
// spread's first application — no compiled claim. detectExpressions forces
|
|
2172
|
+
// the id chain, so claim targets always have one.
|
|
2173
|
+
const claimTarget =
|
|
2174
|
+
!!results.id &&
|
|
2175
|
+
isClaimTarget(path.node) &&
|
|
2176
|
+
!path.node.openingElement.attributes.some((attr) => t$1.isJSXSpreadAttribute(attr));
|
|
2177
|
+
transformAttributes$1(path, results);
|
|
2178
|
+
if (claimTarget) {
|
|
2179
|
+
if (results.dynamics.length) results.claims.push(results.id);else
|
|
2180
|
+
|
|
2064
2181
|
results.exprs.push(
|
|
2065
2182
|
t$1.expressionStatement(
|
|
2066
2183
|
t$1.callExpression(
|
|
@@ -2070,7 +2187,6 @@ info = {})
|
|
|
2070
2187
|
)
|
|
2071
2188
|
);
|
|
2072
2189
|
}
|
|
2073
|
-
transformAttributes$1(path, results);
|
|
2074
2190
|
if (config.contextToCustomElements && (tagName === "slot" || hasCustomElement)) {
|
|
2075
2191
|
contextToCustomElement(path, results);
|
|
2076
2192
|
}
|
|
@@ -2112,7 +2228,7 @@ path,
|
|
|
2112
2228
|
elem,
|
|
2113
2229
|
name,
|
|
2114
2230
|
value,
|
|
2115
|
-
{ dynamic, prevId, tagName, styleProperty, classProperty } = {})
|
|
2231
|
+
{ dynamic, prevId, tagName, styleProperty, classProperty, checkableInput } = {})
|
|
2116
2232
|
{
|
|
2117
2233
|
// pull out namespace
|
|
2118
2234
|
const config = getConfig(path);
|
|
@@ -2183,8 +2299,21 @@ value,
|
|
|
2183
2299
|
// Locked/stateful DOM properties (value/checked/...) must go through the
|
|
2184
2300
|
// runtime in hydratable builds so the hydration claim pass can adopt
|
|
2185
2301
|
// pre-hydration user state instead of overwriting it (#3182). setProperty
|
|
2186
|
-
// carries the select-
|
|
2187
|
-
|
|
2302
|
+
// carries the select-value retry and input/textarea nullish special cases.
|
|
2303
|
+
// `<select value>` always goes through it: a microtask only sees options
|
|
2304
|
+
// inserted in the same turn, so options that arrive later never get
|
|
2305
|
+
// selected (#3928). So does a non-literal value/defaultValue on an input
|
|
2306
|
+
// that may be a checkbox or radio: a nullish value must remove the
|
|
2307
|
+
// attribute so the checked box submits its native "on" (#4009, #4017),
|
|
2308
|
+
// and setProperty already tests the live `type` for that.
|
|
2309
|
+
if (
|
|
2310
|
+
config.hydratable && (isLocked || namespace !== "prop") ||
|
|
2311
|
+
name === "value" && tagName === "select" ||
|
|
2312
|
+
checkableInput && (
|
|
2313
|
+
name === "value" || name === "defaultValue") &&
|
|
2314
|
+
!t$1.isStringLiteral(value) &&
|
|
2315
|
+
!t$1.isNumericLiteral(value))
|
|
2316
|
+
{
|
|
2188
2317
|
return t$1.callExpression(registerImportMethod(path, "setProperty"), [
|
|
2189
2318
|
elem,
|
|
2190
2319
|
t$1.stringLiteral(name),
|
|
@@ -2197,24 +2326,14 @@ value,
|
|
|
2197
2326
|
t$1.memberExpression(elem, t$1.identifier(name)),
|
|
2198
2327
|
value
|
|
2199
2328
|
);
|
|
2200
|
-
// handle select/options... TODO: consider other ways in the future
|
|
2201
|
-
// TODO: there may be a race condition here
|
|
2202
|
-
if (name === "value" && tagName === "select") {
|
|
2203
|
-
return t$1.logicalExpression(
|
|
2204
|
-
"||",
|
|
2205
|
-
t$1.callExpression(t$1.identifier("queueMicrotask"), [
|
|
2206
|
-
t$1.arrowFunctionExpression([], assignment)]
|
|
2207
|
-
),
|
|
2208
|
-
assignment
|
|
2209
|
-
);
|
|
2210
|
-
}
|
|
2211
2329
|
if (
|
|
2212
2330
|
(name === "value" || name === "defaultValue") && (
|
|
2213
2331
|
tagName === "input" || tagName === "textarea") &&
|
|
2214
2332
|
!t$1.isStringLiteral(value) &&
|
|
2215
2333
|
!t$1.isNumericLiteral(value))
|
|
2216
2334
|
{
|
|
2217
|
-
// prevents undefined on input/textarea.value/defaultValue, fallback
|
|
2335
|
+
// prevents undefined on text input/textarea.value/defaultValue, fallback
|
|
2336
|
+
// to empty string (#2957)
|
|
2218
2337
|
return t$1.assignmentExpression(
|
|
2219
2338
|
"=",
|
|
2220
2339
|
t$1.memberExpression(elem, t$1.identifier(name)),
|
|
@@ -2255,6 +2374,35 @@ handler)
|
|
|
2255
2374
|
return t$1.isFunction(handler);
|
|
2256
2375
|
}
|
|
2257
2376
|
|
|
2377
|
+
/**
|
|
2378
|
+
* Whether an `<input>` may be a checkbox or radio, whose nullish
|
|
2379
|
+
* value/defaultValue removes the `value` attribute (restoring the native "on"
|
|
2380
|
+
* submission value) instead of clearing it to "" like a text field (#4017).
|
|
2381
|
+
* Only a plain `type` written as a string literal is resolved here; a missing
|
|
2382
|
+
* `type` is a text input. Anything else (an expression, `prop:type`,
|
|
2383
|
+
* `attr:type`, a spread) is unknown and treated as possibly checkable, which
|
|
2384
|
+
* stays correct because setProperty tests the live `type`. Mirrored by the
|
|
2385
|
+
* native compiler's `input_may_be_checkable` (dom/attrs.rs).
|
|
2386
|
+
*/
|
|
2387
|
+
function inputMayBeCheckable(node) {
|
|
2388
|
+
let checkable = false;
|
|
2389
|
+
for (const attribute of node.openingElement.attributes) {
|
|
2390
|
+
if (t$1.isJSXSpreadAttribute(attribute)) return true;
|
|
2391
|
+
const name = attribute.name;
|
|
2392
|
+
if (t$1.isJSXNamespacedName(name)) {
|
|
2393
|
+
if (name.name.name === "type") return true;
|
|
2394
|
+
continue;
|
|
2395
|
+
}
|
|
2396
|
+
if (name.name !== "type") continue;
|
|
2397
|
+
let value = attribute.value;
|
|
2398
|
+
if (t$1.isJSXExpressionContainer(value)) value = value.expression;
|
|
2399
|
+
if (value == null) checkable = false;else
|
|
2400
|
+
if (t$1.isStringLiteral(value)) checkable = /^(checkbox|radio)$/i.test(value.value);else
|
|
2401
|
+
return true;
|
|
2402
|
+
}
|
|
2403
|
+
return checkable;
|
|
2404
|
+
}
|
|
2405
|
+
|
|
2258
2406
|
function transformAttributes$1(
|
|
2259
2407
|
path,
|
|
2260
2408
|
results)
|
|
@@ -2267,7 +2415,8 @@ results)
|
|
|
2267
2415
|
const tagName = getTagName(path.node),
|
|
2268
2416
|
hasChildren = path.node.children.length > 0,
|
|
2269
2417
|
hasSpread = attributes.some((attribute) => t$1.isJSXSpreadAttribute(attribute.node)),
|
|
2270
|
-
config = getConfig(path)
|
|
2418
|
+
config = getConfig(path),
|
|
2419
|
+
checkableInput = tagName === "input" && inputMayBeCheckable(path.node);
|
|
2271
2420
|
const refs = [];
|
|
2272
2421
|
|
|
2273
2422
|
// preprocess spreads
|
|
@@ -2437,6 +2586,8 @@ results)
|
|
|
2437
2586
|
t$1.jsxExpressionContainer(p.value)
|
|
2438
2587
|
);
|
|
2439
2588
|
newAttr._styleProperty = true;
|
|
2589
|
+
// The split-out property is located at the `style` attribute it came from.
|
|
2590
|
+
newAttr.loc = styleObjectAttribute.node.loc;
|
|
2440
2591
|
path.
|
|
2441
2592
|
get("openingElement").
|
|
2442
2593
|
node.attributes.splice(Number(styleObjectAttribute.key) + ++i, 0, newAttr);
|
|
@@ -2487,13 +2638,16 @@ results)
|
|
|
2487
2638
|
))
|
|
2488
2639
|
{
|
|
2489
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;
|
|
2490
2648
|
path.
|
|
2491
2649
|
get("openingElement").
|
|
2492
|
-
node.attributes.splice(
|
|
2493
|
-
Number(classArrayAttribute.key) + 1,
|
|
2494
|
-
0,
|
|
2495
|
-
t$1.jsxAttribute(t$1.jsxIdentifier("class"), t$1.jsxExpressionContainer(dynamicClassElement))
|
|
2496
|
-
);
|
|
2650
|
+
node.attributes.splice(Number(classArrayAttribute.key) + 1, 0, dynamicClassAttribute);
|
|
2497
2651
|
}
|
|
2498
2652
|
}
|
|
2499
2653
|
|
|
@@ -2539,6 +2693,8 @@ results)
|
|
|
2539
2693
|
t$1.jsxExpressionContainer(p.value)
|
|
2540
2694
|
);
|
|
2541
2695
|
newAttr._classProperty = true;
|
|
2696
|
+
// The split-out property is located at the `class` attribute it came from.
|
|
2697
|
+
newAttr.loc = classListAttribute.node.loc;
|
|
2542
2698
|
path.
|
|
2543
2699
|
get("openingElement").
|
|
2544
2700
|
node.attributes.splice(Number(classListAttribute.key) + ++i, 0, newAttr);
|
|
@@ -2945,14 +3101,20 @@ results)
|
|
|
2945
3101
|
key,
|
|
2946
3102
|
value: value.expression,
|
|
2947
3103
|
tagName,
|
|
3104
|
+
checkableInput,
|
|
2948
3105
|
styleProperty: isStyleProperty,
|
|
2949
|
-
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)
|
|
2950
3111
|
});
|
|
2951
3112
|
} else {
|
|
2952
3113
|
results.exprs.push(
|
|
2953
3114
|
t$1.expressionStatement(
|
|
2954
3115
|
setAttr$1(attribute, elem, key, value.expression, {
|
|
2955
3116
|
tagName,
|
|
3117
|
+
checkableInput,
|
|
2956
3118
|
styleProperty: isStyleProperty,
|
|
2957
3119
|
classProperty: isClassProperty
|
|
2958
3120
|
})
|
|
@@ -3001,7 +3163,10 @@ results)
|
|
|
3001
3163
|
{
|
|
3002
3164
|
results.exprs.push(
|
|
3003
3165
|
t$1.expressionStatement(
|
|
3004
|
-
setAttr$1(attribute, elem, key, staticValue, {
|
|
3166
|
+
setAttr$1(attribute, elem, key, staticValue, {
|
|
3167
|
+
tagName,
|
|
3168
|
+
checkableInput
|
|
3169
|
+
})
|
|
3005
3170
|
)
|
|
3006
3171
|
);
|
|
3007
3172
|
} else {
|
|
@@ -3103,6 +3268,10 @@ config)
|
|
|
3103
3268
|
if (!transformed) return memo;
|
|
3104
3269
|
transformed.allocatesIds = allocatesIds;
|
|
3105
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);
|
|
3106
3275
|
const i = memo.length;
|
|
3107
3276
|
if (transformed.text && i && memo[i - 1].text) {
|
|
3108
3277
|
memo[i - 1].template =
|
|
@@ -3168,6 +3337,7 @@ config)
|
|
|
3168
3337
|
results.declarations.push(...child.declarations);
|
|
3169
3338
|
results.exprs.push(...child.exprs);
|
|
3170
3339
|
results.dynamics.push(...child.dynamics);
|
|
3340
|
+
if (child.claims) results.claims.push(...child.claims);
|
|
3171
3341
|
childPostExprs.push(...(child.postExprs || []));
|
|
3172
3342
|
results.hasHydratableEvent =
|
|
3173
3343
|
results.hasHydratableEvent || child.hasHydratableEvent;
|
|
@@ -3185,14 +3355,11 @@ config)
|
|
|
3185
3355
|
// is inserted directly and creates no effect to name.
|
|
3186
3356
|
const accessorHole =
|
|
3187
3357
|
child.dynamic || child.functionHole;
|
|
3358
|
+
// `sourceNames.locations`: `loc` after `name`, the hole's `{` start.
|
|
3188
3359
|
const emitInsert = (args) => {
|
|
3189
3360
|
if (accessorHole && namesBindings(getConfig(path))) {
|
|
3190
3361
|
while (args.length < 4) args.push(t$1.identifier("undefined"));
|
|
3191
|
-
args.push(
|
|
3192
|
-
t$1.objectExpression([
|
|
3193
|
-
t$1.objectProperty(t$1.identifier("name"), t$1.stringLiteral(`${tagName}.children`))]
|
|
3194
|
-
)
|
|
3195
|
-
);
|
|
3362
|
+
args.push(bindingOptions(`${tagName}.children`, child.loc));
|
|
3196
3363
|
}
|
|
3197
3364
|
results.exprs.push(t$1.expressionStatement(t$1.callExpression(insert, args)));
|
|
3198
3365
|
};
|
|
@@ -3492,7 +3659,14 @@ attributes,
|
|
|
3492
3659
|
// `sourceNames.bindings`: the tag as written rides as spread's trailing
|
|
3493
3660
|
// argument (past the runtime-only `skip` slot); the runtime labels its
|
|
3494
3661
|
// attribute effect `<tag>.spread` and its children insert `<tag>.children`.
|
|
3495
|
-
|
|
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
|
+
}
|
|
3496
3670
|
|
|
3497
3671
|
return [
|
|
3498
3672
|
filteredAttributes,
|
|
@@ -3512,6 +3686,23 @@ wrap)
|
|
|
3512
3686
|
{
|
|
3513
3687
|
const config = getConfig(path);
|
|
3514
3688
|
if (result.id) {
|
|
3689
|
+
// Claim targets with dynamic bindings are claimed at the tail of the
|
|
3690
|
+
// binding effect's first run, under the owner the template is created
|
|
3691
|
+
// with: the effect's callback runs ownerless when a held mount lands, so
|
|
3692
|
+
// the owner is captured here, at creation, and handed to the claim.
|
|
3693
|
+
let ownerId;
|
|
3694
|
+
if (result.claims?.length) {
|
|
3695
|
+
ownerId = path.scope.generateUidIdentifier("o$");
|
|
3696
|
+
result.declarations.push(
|
|
3697
|
+
t__namespace.variableDeclarator(
|
|
3698
|
+
ownerId,
|
|
3699
|
+
t__namespace.callExpression(
|
|
3700
|
+
registerImportMethod(path, "getOwner", getRendererConfig(path, "dom").moduleName),
|
|
3701
|
+
[]
|
|
3702
|
+
)
|
|
3703
|
+
)
|
|
3704
|
+
);
|
|
3705
|
+
}
|
|
3515
3706
|
registerTemplate(path, result);
|
|
3516
3707
|
const decl = result.decl;
|
|
3517
3708
|
if (
|
|
@@ -3520,7 +3711,7 @@ wrap)
|
|
|
3520
3711
|
{
|
|
3521
3712
|
return decl.declarations[0].init;
|
|
3522
3713
|
} else {
|
|
3523
|
-
const dynamicsStmt = wrapDynamics$1(path, result.dynamics);
|
|
3714
|
+
const dynamicsStmt = wrapDynamics$1(path, result.dynamics, result.claims, ownerId);
|
|
3524
3715
|
const stmts = [
|
|
3525
3716
|
decl,
|
|
3526
3717
|
...result.exprs,
|
|
@@ -3662,31 +3853,47 @@ function wrapReadShallow(path, key, value) {
|
|
|
3662
3853
|
);
|
|
3663
3854
|
}
|
|
3664
3855
|
|
|
3665
|
-
function wrapDynamics$1(
|
|
3856
|
+
function wrapDynamics$1(
|
|
3857
|
+
path,
|
|
3858
|
+
dynamics,
|
|
3859
|
+
claims = [],
|
|
3860
|
+
ownerId)
|
|
3861
|
+
{
|
|
3666
3862
|
if (!dynamics.length) return;
|
|
3667
3863
|
const config = getConfig(path);
|
|
3668
3864
|
|
|
3669
3865
|
// dynamics are only queued when effectWrapper is configured (element.ts
|
|
3670
3866
|
// guards every push), so the name is always a string here
|
|
3671
3867
|
const effectWrapperId = registerImportMethod(path, config.effectWrapper, undefined);
|
|
3868
|
+
// The claim contract's mount claims for this root's claim targets with
|
|
3869
|
+
// dynamic bindings (element.ts): the tail of the effect body, so the first
|
|
3870
|
+
// run claims after it applied the initial attributes; `claimElement` is
|
|
3871
|
+
// once-per-element, so reruns cost one check. `ownerId` is the owner
|
|
3872
|
+
// captured at creation (createTemplate) the handlers run under.
|
|
3873
|
+
const claimStatements = claims.map((id) =>
|
|
3874
|
+
t__namespace.expressionStatement(
|
|
3875
|
+
t__namespace.callExpression(
|
|
3876
|
+
registerImportMethod(path, "claimElement", getRendererConfig(path, "dom").moduleName),
|
|
3877
|
+
[id, ownerId]
|
|
3878
|
+
)
|
|
3879
|
+
)
|
|
3880
|
+
);
|
|
3672
3881
|
// `sourceNames.bindings`: the effect is named by what it writes — each
|
|
3673
3882
|
// binding's `<tag>.<attribute>` as written (undoing the `prop:` the locked
|
|
3674
3883
|
// DOM property pre-pass added to `value`, `checked`, …), the merged effect
|
|
3675
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).
|
|
3676
3887
|
const label = namesBindings(config) ?
|
|
3677
3888
|
[
|
|
3678
|
-
|
|
3679
|
-
|
|
3680
|
-
|
|
3681
|
-
|
|
3682
|
-
|
|
3683
|
-
|
|
3684
|
-
|
|
3685
|
-
|
|
3686
|
-
).
|
|
3687
|
-
join(", ")
|
|
3688
|
-
)
|
|
3689
|
-
)]
|
|
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
|
|
3690
3897
|
)] :
|
|
3691
3898
|
|
|
3692
3899
|
[];
|
|
@@ -3713,12 +3920,14 @@ function wrapDynamics$1(path, dynamics) {
|
|
|
3713
3920
|
t__namespace.expressionStatement(
|
|
3714
3921
|
setAttr$1(path, dynamics[0].elem, dynamics[0].key, newValue, {
|
|
3715
3922
|
tagName: dynamics[0].tagName,
|
|
3923
|
+
checkableInput: dynamics[0].checkableInput,
|
|
3716
3924
|
dynamic: true,
|
|
3717
3925
|
prevId: prevValue,
|
|
3718
3926
|
styleProperty: dynamics[0].styleProperty,
|
|
3719
3927
|
classProperty: dynamics[0].classProperty
|
|
3720
3928
|
})
|
|
3721
|
-
)
|
|
3929
|
+
),
|
|
3930
|
+
...claimStatements]
|
|
3722
3931
|
)
|
|
3723
3932
|
),
|
|
3724
3933
|
...label]
|
|
@@ -3732,7 +3941,9 @@ function wrapDynamics$1(path, dynamics) {
|
|
|
3732
3941
|
const statements = [];
|
|
3733
3942
|
const properties = [];
|
|
3734
3943
|
|
|
3735
|
-
dynamics.forEach((
|
|
3944
|
+
dynamics.forEach((binding, index) => {
|
|
3945
|
+
const { elem, key, tagName, styleProperty, classProperty, checkableInput } = binding;
|
|
3946
|
+
let value = binding.value;
|
|
3736
3947
|
const propIdent = t__namespace.identifier(getNumberedId(index));
|
|
3737
3948
|
const propMember = t__namespace.memberExpression(prevId, propIdent);
|
|
3738
3949
|
const optionalPropMember = t__namespace.optionalMemberExpression(prevId, propIdent, false, true);
|
|
@@ -3750,7 +3961,8 @@ function wrapDynamics$1(path, dynamics) {
|
|
|
3750
3961
|
setAttr$1(path, elem, key, propIdent, {
|
|
3751
3962
|
tagName,
|
|
3752
3963
|
dynamic: true,
|
|
3753
|
-
prevId: optionalPropMember
|
|
3964
|
+
prevId: optionalPropMember,
|
|
3965
|
+
checkableInput
|
|
3754
3966
|
})
|
|
3755
3967
|
)
|
|
3756
3968
|
);
|
|
@@ -3770,7 +3982,8 @@ function wrapDynamics$1(path, dynamics) {
|
|
|
3770
3982
|
tagName,
|
|
3771
3983
|
dynamic: true,
|
|
3772
3984
|
styleProperty,
|
|
3773
|
-
classProperty
|
|
3985
|
+
classProperty,
|
|
3986
|
+
checkableInput
|
|
3774
3987
|
})
|
|
3775
3988
|
)
|
|
3776
3989
|
)
|
|
@@ -3783,7 +3996,7 @@ function wrapDynamics$1(path, dynamics) {
|
|
|
3783
3996
|
t__namespace.arrowFunctionExpression([], t__namespace.objectExpression(values)),
|
|
3784
3997
|
t__namespace.arrowFunctionExpression(
|
|
3785
3998
|
[t__namespace.objectPattern(properties.map((id) => t__namespace.objectProperty(id, id, false, true))), prevId],
|
|
3786
|
-
t__namespace.blockStatement(statements)
|
|
3999
|
+
t__namespace.blockStatement([...statements, ...claimStatements])
|
|
3787
4000
|
),
|
|
3788
4001
|
...label]
|
|
3789
4002
|
)
|
|
@@ -3969,6 +4182,45 @@ function appendSkips(path, skips) {
|
|
|
3969
4182
|
);
|
|
3970
4183
|
}
|
|
3971
4184
|
|
|
4185
|
+
/**
|
|
4186
|
+
* The link-attributes object of a static anchor's `ssrLinkClaim` hole
|
|
4187
|
+
* (ssr/element.ts `transformAttributes`): `{ href: "/about" }`, the anchor's
|
|
4188
|
+
* link-relevant attributes as the literals they were written as. Hoisted to
|
|
4189
|
+
* the module like a template — one object per distinct attribute set,
|
|
4190
|
+
* shared by every anchor that writes the same one — so the hole allocates
|
|
4191
|
+
* nothing per render.
|
|
4192
|
+
*/
|
|
4193
|
+
function registerLinkAttrs(path, entries) {
|
|
4194
|
+
const data = path.scope.getProgramParent().data;
|
|
4195
|
+
const records = data.ssrLinkAttrs || (data.ssrLinkAttrs = []);
|
|
4196
|
+
const key = entries.map(([name, value]) => `${name}=${value}`).join("\0");
|
|
4197
|
+
const found = records.find((r) => r.key === key);
|
|
4198
|
+
if (found) return found.id;
|
|
4199
|
+
const id = path.scope.generateUidIdentifier("lk$");
|
|
4200
|
+
records.push({
|
|
4201
|
+
key,
|
|
4202
|
+
id,
|
|
4203
|
+
object: t__namespace.objectExpression(
|
|
4204
|
+
entries.map(([name, value]) => t__namespace.objectProperty(linkAttrsKey(name), t__namespace.stringLiteral(value)))
|
|
4205
|
+
)
|
|
4206
|
+
});
|
|
4207
|
+
return id;
|
|
4208
|
+
}
|
|
4209
|
+
|
|
4210
|
+
/** The key of a link-attributes object property: `href`, or `"aria-current"`. */
|
|
4211
|
+
function linkAttrsKey(name) {
|
|
4212
|
+
return t__namespace.isValidIdentifier(name) ? t__namespace.identifier(name) : t__namespace.stringLiteral(name);
|
|
4213
|
+
}
|
|
4214
|
+
|
|
4215
|
+
function appendLinkAttrs(path, records) {
|
|
4216
|
+
path.node.body.unshift(
|
|
4217
|
+
t__namespace.variableDeclaration(
|
|
4218
|
+
"var",
|
|
4219
|
+
records.map((r) => t__namespace.variableDeclarator(r.id, r.object))
|
|
4220
|
+
)
|
|
4221
|
+
);
|
|
4222
|
+
}
|
|
4223
|
+
|
|
3972
4224
|
/**
|
|
3973
4225
|
* Hoisted props shapes (SSR, #3511).
|
|
3974
4226
|
*
|
|
@@ -4905,6 +5157,93 @@ function normalizeAttributes(path) {
|
|
|
4905
5157
|
return attributes;
|
|
4906
5158
|
}
|
|
4907
5159
|
|
|
5160
|
+
/**
|
|
5161
|
+
* The compile-time value of a JSX attribute, as the SSR static branch sees
|
|
5162
|
+
* it: `null` for a bare attribute (`<a link>`), the literal for a string /
|
|
5163
|
+
* number / boolean (quoted or in a container), `undefined` when the value is
|
|
5164
|
+
* an expression the compiler cannot see through (dynamic, or an empty
|
|
5165
|
+
* container the attribute drops out for).
|
|
5166
|
+
*/
|
|
5167
|
+
function staticAttributeValue(
|
|
5168
|
+
value)
|
|
5169
|
+
{
|
|
5170
|
+
if (value == null) return null;
|
|
5171
|
+
if (t.isStringLiteral(value)) return value.value;
|
|
5172
|
+
if (!t.isJSXExpressionContainer(value)) return undefined;
|
|
5173
|
+
const expression = value.expression;
|
|
5174
|
+
if (t.isStringLiteral(expression) || t.isNumericLiteral(expression)) return expression.value;
|
|
5175
|
+
if (t.isBooleanLiteral(expression)) return expression.value;
|
|
5176
|
+
return undefined;
|
|
5177
|
+
}
|
|
5178
|
+
|
|
5179
|
+
// A URL scheme at the start of an href. `http:`/`https:` (and a
|
|
5180
|
+
// protocol-relative `//host`) may still be this origin — only the handler,
|
|
5181
|
+
// which knows the request, can say — so only another scheme rules an
|
|
5182
|
+
// anchor out at compile time.
|
|
5183
|
+
const HREF_SCHEME = /^([a-zA-Z][a-zA-Z0-9+.-]*):/;
|
|
5184
|
+
|
|
5185
|
+
/**
|
|
5186
|
+
* Whether an anchor (the template path's, no spread) gets the `ssrLinkClaim`
|
|
5187
|
+
* hole (solidjs/solid#3878): it has an `href`, and nothing the compiler can
|
|
5188
|
+
* see rules it out as the current page — the anchors a link handler would
|
|
5189
|
+
* answer `""` for however the request looks. A static non-empty `target`,
|
|
5190
|
+
* a `download`, a `rel` naming `external`, an author-written `aria-current`
|
|
5191
|
+
* (hers to keep), an empty href or one with a non-HTTP scheme (`mailto:`,
|
|
5192
|
+
* `tel:`, `javascript:` …) get no hole and no per-render call; a dynamic
|
|
5193
|
+
* value in any of these positions is the handler's to judge at runtime.
|
|
5194
|
+
* Mirrors the DOM transform's `isClaimTarget` (`a[href]`), narrowed.
|
|
5195
|
+
*/
|
|
5196
|
+
function isLinkClaimCandidate(attributes) {
|
|
5197
|
+
let href = false;
|
|
5198
|
+
for (const attribute of attributes) {
|
|
5199
|
+
const node = attribute.node;
|
|
5200
|
+
if (!t.isJSXAttribute(node) || !t.isJSXIdentifier(node.name)) continue;
|
|
5201
|
+
const key = node.name.name;
|
|
5202
|
+
if (!LinkAttributes.has(key)) continue;
|
|
5203
|
+
if (t.isJSXExpressionContainer(node.value) && t.isJSXEmptyExpression(node.value.expression))
|
|
5204
|
+
continue;
|
|
5205
|
+
const value = staticAttributeValue(node.value);
|
|
5206
|
+
if (key === "href") {
|
|
5207
|
+
if (value === false) continue;
|
|
5208
|
+
href = true;
|
|
5209
|
+
if (value === undefined) continue;
|
|
5210
|
+
// bare `href`, `href=""`, `href={true}`: an empty href is no link
|
|
5211
|
+
if (value === null || value === true || value === "") return false;
|
|
5212
|
+
const scheme = typeof value === "string" ? HREF_SCHEME.exec(value) : null;
|
|
5213
|
+
if (scheme && scheme[1].toLowerCase() !== "http" && scheme[1].toLowerCase() !== "https")
|
|
5214
|
+
return false;
|
|
5215
|
+
continue;
|
|
5216
|
+
}
|
|
5217
|
+
// A dynamic value is the handler's to judge; `={false}` omits the attribute.
|
|
5218
|
+
if (value === undefined || value === false) continue;
|
|
5219
|
+
switch (key) {
|
|
5220
|
+
case "target":
|
|
5221
|
+
// A bare `target`, `target=""` or `target={true}` is the empty
|
|
5222
|
+
// target — still a link.
|
|
5223
|
+
if (value !== null && value !== true && value !== "") return false;
|
|
5224
|
+
break;
|
|
5225
|
+
case "rel":
|
|
5226
|
+
if (typeof value === "string" && value.split(/\s+/).includes("external")) return false;
|
|
5227
|
+
break;
|
|
5228
|
+
case "download":
|
|
5229
|
+
case "aria-current":
|
|
5230
|
+
// Present in any form: a download, or the author's own current marker.
|
|
5231
|
+
return false;
|
|
5232
|
+
// `link`: the explicit-links opt-in marker — the handler reads it.
|
|
5233
|
+
}
|
|
5234
|
+
}
|
|
5235
|
+
return href;
|
|
5236
|
+
}
|
|
5237
|
+
|
|
5238
|
+
/**
|
|
5239
|
+
* The attribute entries of a candidate anchor's `ssrLinkClaim` hole,
|
|
5240
|
+
* collected by `transformAttributes` as it writes each attribute: a static
|
|
5241
|
+
* value as the string it serializes to, a dynamic value as the function-local
|
|
5242
|
+
* temp its attribute hole assigns (`_lv$N`), flagged when that hole is a
|
|
5243
|
+
* thunk (`isDynamic`) so the link hole is one too and reads the temp after it.
|
|
5244
|
+
*/
|
|
5245
|
+
|
|
5246
|
+
|
|
4908
5247
|
function transformAttributes(
|
|
4909
5248
|
path,
|
|
4910
5249
|
results,
|
|
@@ -4915,13 +5254,16 @@ info)
|
|
|
4915
5254
|
const hasChildren = path.node.children.length > 0,
|
|
4916
5255
|
attributes = normalizeAttributes(path);
|
|
4917
5256
|
let children;
|
|
4918
|
-
//
|
|
4919
|
-
//
|
|
4920
|
-
//
|
|
4921
|
-
//
|
|
4922
|
-
//
|
|
4923
|
-
//
|
|
4924
|
-
|
|
5257
|
+
// Link claims (solidjs/solid#3878): a candidate anchor gets one hole after
|
|
5258
|
+
// its attributes — `ssrLinkClaim(attrs)` — where a render's link handler
|
|
5259
|
+
// writes the anchor's link state (`aria-current="page"`, `data-active`)
|
|
5260
|
+
// into the server HTML, and `""` otherwise. `attrs` is the anchor's
|
|
5261
|
+
// link-relevant attributes: a static anchor's are a hoisted object, so the
|
|
5262
|
+
// hole is one eager call with no allocation per render; a dynamic one's
|
|
5263
|
+
// join the element's attribute group and reuse the values its attribute
|
|
5264
|
+
// holes evaluated. The hole never takes a hydration id.
|
|
5265
|
+
const linkEntries =
|
|
5266
|
+
tagName === "a" && isLinkClaimCandidate(attributes) ? [] : null;
|
|
4925
5267
|
|
|
4926
5268
|
attributes.forEach((attribute) => {
|
|
4927
5269
|
if (!t.isJSXAttribute(attribute.node)) return;
|
|
@@ -4952,23 +5294,13 @@ info)
|
|
|
4952
5294
|
{
|
|
4953
5295
|
if (t.isJSXEmptyExpression(value.expression)) return;
|
|
4954
5296
|
if (key === "ref") {
|
|
4955
|
-
if (info.serverComponents) {
|
|
4956
|
-
claims.push(["ref", value.expression]);
|
|
4957
|
-
return;
|
|
4958
|
-
}
|
|
4959
5297
|
results.declarations.push(
|
|
4960
5298
|
t.variableDeclarator(path.scope.generateUidIdentifier("_ref$"), value.expression)
|
|
4961
5299
|
);
|
|
4962
5300
|
return;
|
|
4963
5301
|
}
|
|
4964
5302
|
if (key.startsWith("prop:")) return;
|
|
4965
|
-
if (isEventName(key))
|
|
4966
|
-
// `onXxx` lowercases to the event name — the client runtime's own
|
|
4967
|
-
// derivation (`onClick` -> `click`); the position is bound under it.
|
|
4968
|
-
if (info.serverComponents)
|
|
4969
|
-
claims.push([toEventName(key), value.expression]);
|
|
4970
|
-
return;
|
|
4971
|
-
}
|
|
5303
|
+
if (isEventName(key)) return;
|
|
4972
5304
|
if (ChildProperties.has(key)) {
|
|
4973
5305
|
if (key === "children" && VoidElements.has(tagName)) return;
|
|
4974
5306
|
if (info.hydratable && key === "textContent" && value && value.expression) {
|
|
@@ -5000,30 +5332,25 @@ info)
|
|
|
5000
5332
|
checkMember: true,
|
|
5001
5333
|
checkTags: true
|
|
5002
5334
|
});
|
|
5003
|
-
//
|
|
5004
|
-
//
|
|
5005
|
-
//
|
|
5006
|
-
//
|
|
5007
|
-
//
|
|
5008
|
-
|
|
5009
|
-
|
|
5010
|
-
|
|
5011
|
-
|
|
5012
|
-
|
|
5013
|
-
|
|
5014
|
-
|
|
5015
|
-
|
|
5016
|
-
|
|
5017
|
-
|
|
5018
|
-
}) :
|
|
5019
|
-
attr
|
|
5020
|
-
);
|
|
5021
|
-
return;
|
|
5335
|
+
// A candidate anchor's dynamic link attribute: the hole evaluates the
|
|
5336
|
+
// raw value ONCE into a function-local temp (`_$escape(_lv$ = expr,
|
|
5337
|
+
// true)`) that the link hole reads after it — the handler sees the
|
|
5338
|
+
// value as written, not attribute-escaped. A template literal keeps
|
|
5339
|
+
// its inline-quoted slot: the whole value is escaped at runtime
|
|
5340
|
+
// instead of its parts, the same bytes (escaping is per character),
|
|
5341
|
+
// and a template literal is never nullish.
|
|
5342
|
+
let linkTemplate = false;
|
|
5343
|
+
if (linkEntries !== null && LinkAttributes.has(key)) {
|
|
5344
|
+
const expression = value.expression;
|
|
5345
|
+
linkTemplate = t.isTemplateLiteral(expression);
|
|
5346
|
+
const temp = path.scope.generateUidIdentifier("lv$");
|
|
5347
|
+
path.scope.push({ id: temp, kind: "var" });
|
|
5348
|
+
value.expression = t.assignmentExpression("=", temp, expression);
|
|
5349
|
+
linkEntries.push([key, t.cloneNode(temp), !!isDynamicValue]);
|
|
5022
5350
|
}
|
|
5023
5351
|
if ((key === "class" || key === "style") && !t.isObjectExpression(value.expression)) {
|
|
5024
|
-
// The whole attribute must be absent for nullish values.
|
|
5025
|
-
//
|
|
5026
|
-
// 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.
|
|
5027
5354
|
const id = path.scope.generateUidIdentifier("v$");
|
|
5028
5355
|
const attr = t.callExpression(
|
|
5029
5356
|
t.arrowFunctionExpression(
|
|
@@ -5141,7 +5468,7 @@ info)
|
|
|
5141
5468
|
);
|
|
5142
5469
|
const expression = value.expression;
|
|
5143
5470
|
|
|
5144
|
-
if (!(doEscape || isBoolean) || t.isLiteral(expression)) {
|
|
5471
|
+
if (!(doEscape || isBoolean) || t.isLiteral(expression) || linkTemplate) {
|
|
5145
5472
|
if (isBoolean) {
|
|
5146
5473
|
t.isBooleanLiteral(expression) &&
|
|
5147
5474
|
expression.value === true &&
|
|
@@ -5170,6 +5497,16 @@ info)
|
|
|
5170
5497
|
const isBoolean = !!booleanLiteral;
|
|
5171
5498
|
if (booleanLiteral && !booleanLiteral.value) return;
|
|
5172
5499
|
appendToTemplate(results.template, ` ${key}`);
|
|
5500
|
+
// A candidate anchor's static link attribute travels as the string the
|
|
5501
|
+
// attribute carries: a bare attribute's is `""`.
|
|
5502
|
+
if (linkEntries !== null && LinkAttributes.has(key))
|
|
5503
|
+
linkEntries.push([
|
|
5504
|
+
key,
|
|
5505
|
+
!staticValue || isBoolean ?
|
|
5506
|
+
"" :
|
|
5507
|
+
String(staticValue.value),
|
|
5508
|
+
false]
|
|
5509
|
+
);
|
|
5173
5510
|
if (!staticValue) return;
|
|
5174
5511
|
let text = isBoolean ?
|
|
5175
5512
|
"" :
|
|
@@ -5188,27 +5525,39 @@ info)
|
|
|
5188
5525
|
);
|
|
5189
5526
|
}
|
|
5190
5527
|
});
|
|
5191
|
-
if (
|
|
5192
|
-
const
|
|
5193
|
-
|
|
5194
|
-
|
|
5195
|
-
|
|
5196
|
-
|
|
5197
|
-
|
|
5198
|
-
|
|
5199
|
-
|
|
5200
|
-
|
|
5201
|
-
|
|
5202
|
-
|
|
5203
|
-
|
|
5204
|
-
|
|
5205
|
-
|
|
5206
|
-
|
|
5207
|
-
t.callExpression(
|
|
5208
|
-
t.
|
|
5209
|
-
|
|
5528
|
+
if (linkEntries !== null) {
|
|
5529
|
+
const ssrLinkClaim = registerImportMethod(path, "ssrLinkClaim");
|
|
5530
|
+
let hole;
|
|
5531
|
+
if (linkEntries.every(([, value]) => typeof value === "string")) {
|
|
5532
|
+
// Every link attribute static: `_$ssrLinkClaim(_lk$N)` over the hoisted
|
|
5533
|
+
// object, evaluated in argument position like a hydration key.
|
|
5534
|
+
hole = t.callExpression(ssrLinkClaim, [
|
|
5535
|
+
registerLinkAttrs(
|
|
5536
|
+
path,
|
|
5537
|
+
linkEntries.map(([key, value]) => [key, value])
|
|
5538
|
+
)]
|
|
5539
|
+
);
|
|
5540
|
+
} else {
|
|
5541
|
+
// `_$ssrLinkClaim({ href: _lv$, rel: "noopener" })`, built per render.
|
|
5542
|
+
// A thunk when any captured attribute hole is one — it must run after
|
|
5543
|
+
// them — and groupable, so it joins the element's attribute group.
|
|
5544
|
+
hole = t.callExpression(ssrLinkClaim, [
|
|
5545
|
+
t.objectExpression(
|
|
5546
|
+
linkEntries.map(([key, value]) =>
|
|
5547
|
+
t.objectProperty(
|
|
5548
|
+
linkAttrsKey(key),
|
|
5549
|
+
typeof value === "string" ? t.stringLiteral(value) : value
|
|
5550
|
+
)
|
|
5551
|
+
)
|
|
5552
|
+
)]
|
|
5553
|
+
);
|
|
5554
|
+
if (linkEntries.some(([,, dynamic]) => dynamic))
|
|
5555
|
+
hole = hoistExpression(path, results, t.arrowFunctionExpression([], hole), {
|
|
5556
|
+
group: true
|
|
5557
|
+
});
|
|
5558
|
+
}
|
|
5210
5559
|
results.template.push("");
|
|
5211
|
-
results.templateValues.push(
|
|
5560
|
+
results.templateValues.push(hole);
|
|
5212
5561
|
}
|
|
5213
5562
|
if (!hasChildren && children) {
|
|
5214
5563
|
path.node.children.push(children);
|
|
@@ -5335,7 +5684,7 @@ results,
|
|
|
5335
5684
|
|
|
5336
5685
|
function createElement(
|
|
5337
5686
|
path,
|
|
5338
|
-
{ topLevel, hydratable
|
|
5687
|
+
{ topLevel, hydratable })
|
|
5339
5688
|
{
|
|
5340
5689
|
const tagName = getTagName(path.node),
|
|
5341
5690
|
config = getConfig(path),
|
|
@@ -5388,22 +5737,6 @@ path,
|
|
|
5388
5737
|
const node = attribute.node;
|
|
5389
5738
|
return !(t.isJSXAttribute(node) && t.isJSXIdentifier(node.name) && node.name.name === "ref");
|
|
5390
5739
|
});
|
|
5391
|
-
// Server components (principles §9.2.3): the named `ref`/`on*` attributes
|
|
5392
|
-
// of a spread element are handler positions like a template element's,
|
|
5393
|
-
// and compile to the same claim map — `{ click: expr, ref: [a, b] }`,
|
|
5394
|
-
// duplicate refs merged, a duplicate handler last-wins as the template
|
|
5395
|
-
// path strips it — keyed by the index of the source each attribute sits
|
|
5396
|
-
// before (`<b {...a} onClick={go} {...b}>` → `{ 1: { click: go } }`, the
|
|
5397
|
-
// spreads being sources 0 and 2), and handed to `ssrElement` as a thunk
|
|
5398
|
-
// it reads only inside a server component's render (the gate the template
|
|
5399
|
-
// path's `ssrClaim` guard reads), so plain SSR never evaluates the
|
|
5400
|
-
// expressions. The runtime settles each handler position in source order,
|
|
5401
|
-
// as the client's `spread(el, [a, { onClick: go }, b])` does — a spread at
|
|
5402
|
-
// that index or later that HAS the key owns it — and merges every ref.
|
|
5403
|
-
// Plain SSR output is unchanged (dropped, as a server element has no
|
|
5404
|
-
// handlers to run).
|
|
5405
|
-
const claims = [];
|
|
5406
|
-
|
|
5407
5740
|
let props;
|
|
5408
5741
|
// Attributes written AFTER the last spread are markup, not a source: no
|
|
5409
5742
|
// later source can override them, so they go to `ssrElement` as its
|
|
@@ -5423,13 +5756,26 @@ path,
|
|
|
5423
5756
|
// keys.
|
|
5424
5757
|
const tail = [];
|
|
5425
5758
|
const skipKeys = [];
|
|
5426
|
-
|
|
5427
|
-
|
|
5428
|
-
|
|
5429
|
-
//
|
|
5430
|
-
// the
|
|
5431
|
-
|
|
5432
|
-
{
|
|
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)) {
|
|
5433
5779
|
props = [propAttributes[0].node.argument];
|
|
5434
5780
|
} else {
|
|
5435
5781
|
props = [];
|
|
@@ -5475,30 +5821,13 @@ path,
|
|
|
5475
5821
|
node.name.name;
|
|
5476
5822
|
|
|
5477
5823
|
if (hasChildren && key === "children") return;
|
|
5478
|
-
if (key === "ref" || isEventName(key))
|
|
5479
|
-
if (serverComponents && t.isJSXExpressionContainer(value)) {
|
|
5480
|
-
const expression = value.expression;
|
|
5481
|
-
const pos = key === "ref" ? "ref" : toEventName(key);
|
|
5482
|
-
if (
|
|
5483
|
-
!(
|
|
5484
|
-
t.isJSXEmptyExpression(expression) ||
|
|
5485
|
-
t.isStringLiteral(expression) ||
|
|
5486
|
-
t.isNumericLiteral(expression) ||
|
|
5487
|
-
t.isBooleanLiteral(expression)))
|
|
5488
|
-
|
|
5489
|
-
{
|
|
5490
|
-
// The index of the source this attribute sits before: the
|
|
5491
|
-
// sources pushed so far, plus the running literal if it will
|
|
5492
|
-
// be pushed ahead of the next spread. The literal never
|
|
5493
|
-
// carries a handler key, so a spread's index is either below
|
|
5494
|
-
// this or at/after it — never ambiguous.
|
|
5495
|
-
claims.push([props.length + (runningObject.length ? 1 : 0), pos, expression]);
|
|
5496
|
-
}
|
|
5497
|
-
}
|
|
5498
|
-
return;
|
|
5499
|
-
}
|
|
5824
|
+
if (key === "ref" || isEventName(key)) return;
|
|
5500
5825
|
if (key.startsWith("prop:")) return;
|
|
5501
|
-
|
|
5826
|
+
// An anchor's link-relevant attributes stay a source, never baked
|
|
5827
|
+
// markup: `ssrElement` collects them from the walk for the render's
|
|
5828
|
+
// link handler (solidjs/solid#3878), and a baked tail is invisible to
|
|
5829
|
+
// it.
|
|
5830
|
+
if (i > lastSpread && !(tagName === "a" && LinkAttributes.has(key))) {
|
|
5502
5831
|
const part = tailAttribute(path, tagName, key, node);
|
|
5503
5832
|
if (part !== undefined) {
|
|
5504
5833
|
if (typeof part === "string" && typeof tail[tail.length - 1] === "string")
|
|
@@ -5561,76 +5890,52 @@ path,
|
|
|
5561
5890
|
// set) and attribute string — a literal when every part is static, else a
|
|
5562
5891
|
// thunk concatenating the parts in source order. A `false` static bakes to
|
|
5563
5892
|
// no markup but still skips its key.
|
|
5564
|
-
if (skipKeys.length) {
|
|
5893
|
+
if (skipKeys.length || refs.length) {
|
|
5565
5894
|
let markup;
|
|
5566
|
-
if (tail.every((part) => typeof part === "string"))
|
|
5567
|
-
|
|
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 {
|
|
5568
5901
|
let concat;
|
|
5569
5902
|
for (const part of tail) {
|
|
5570
5903
|
const expr = typeof part === "string" ? t.stringLiteral(part) : part;
|
|
5571
5904
|
concat = concat ? t.binaryExpression("+", concat, expr) : expr;
|
|
5572
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
|
+
);
|
|
5573
5914
|
markup = t.arrowFunctionExpression([], concat);
|
|
5574
5915
|
}
|
|
5575
|
-
args.push(registerSkip(path, skipKeys), markup);
|
|
5576
|
-
}
|
|
5577
|
-
if (claims.length) {
|
|
5578
|
-
if (!skipKeys.length) args.push(t.identifier("undefined"), t.identifier("undefined"));
|
|
5579
|
-
args.push(t.arrowFunctionExpression([], claimSegments(claims)));
|
|
5916
|
+
args.push(skipKeys.length ? registerSkip(path, skipKeys) : t.identifier("undefined"), markup);
|
|
5580
5917
|
}
|
|
5581
5918
|
const exprs = [t.callExpression(registerImportMethod(path, "ssrElement"), args)];
|
|
5582
5919
|
return { exprs, template: "", declarations: [], dynamics: [], spreadElement: true };
|
|
5583
5920
|
}
|
|
5584
5921
|
|
|
5585
|
-
/**
|
|
5586
|
-
|
|
5587
|
-
|
|
5588
|
-
|
|
5589
|
-
|
|
5590
|
-
|
|
5591
|
-
|
|
5592
|
-
*/
|
|
5593
|
-
function claimSegments(
|
|
5594
|
-
claims)
|
|
5595
|
-
{
|
|
5596
|
-
const lastHandler = new Map();
|
|
5597
|
-
claims.forEach(([, pos], i) => {
|
|
5598
|
-
if (pos !== "ref") lastHandler.set(pos, i);
|
|
5599
|
-
});
|
|
5600
|
-
const segments = new Map();
|
|
5601
|
-
claims.forEach(([index, pos, expr], i) => {
|
|
5602
|
-
if (pos !== "ref" && lastHandler.get(pos) !== i) return;
|
|
5603
|
-
let list = segments.get(index);
|
|
5604
|
-
if (!list) segments.set(index, list = []);
|
|
5605
|
-
list.push([pos, expr]);
|
|
5606
|
-
});
|
|
5607
|
-
return t.objectExpression(
|
|
5608
|
-
[...segments].map(([index, list]) => t.objectProperty(t.numericLiteral(index), claimMap(list)))
|
|
5609
|
-
);
|
|
5610
|
-
}
|
|
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))
|
|
5611
5929
|
|
|
5612
|
-
|
|
5613
|
-
|
|
5614
|
-
|
|
5615
|
-
|
|
5616
|
-
|
|
5617
|
-
|
|
5618
|
-
|
|
5619
|
-
|
|
5620
|
-
|
|
5621
|
-
for (const [pos, expr] of claims) {
|
|
5622
|
-
let list = byPos.get(pos);
|
|
5623
|
-
if (!list) byPos.set(pos, list = []);
|
|
5624
|
-
list.push(expr);
|
|
5625
|
-
}
|
|
5626
|
-
return t.objectExpression(
|
|
5627
|
-
[...byPos].map(([pos, exprs]) =>
|
|
5628
|
-
t.objectProperty(
|
|
5629
|
-
t.stringLiteral(pos),
|
|
5630
|
-
exprs.length === 1 ? exprs[0] : t.arrayExpression(exprs)
|
|
5631
|
-
)
|
|
5632
|
-
)
|
|
5633
|
-
);
|
|
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;
|
|
5634
5939
|
}
|
|
5635
5940
|
|
|
5636
5941
|
/**
|
|
@@ -6143,6 +6448,10 @@ path)
|
|
|
6143
6448
|
sourceNames(config).components && (config.generate === "dom" || config.generate === "ssr");
|
|
6144
6449
|
if (labelled) {
|
|
6145
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));
|
|
6146
6455
|
}
|
|
6147
6456
|
// SSR's prod `createComponent` is literally `Comp(props || {})`. Since the
|
|
6148
6457
|
// compiler always emits a real `props[0]` object expression above (see the
|
|
@@ -15167,8 +15476,10 @@ var postprocess = (path, state) => {
|
|
|
15167
15476
|
if (hoisted) path.node.body.unshift(...hoisted);
|
|
15168
15477
|
}
|
|
15169
15478
|
// Spread-element skip predicates sit between the templates and the hoisted
|
|
15170
|
-
// props shapes (unshifted after them, before the templates unshift)
|
|
15479
|
+
// props shapes (unshifted after them, before the templates unshift); the
|
|
15480
|
+
// static anchors' link-attributes objects right after the templates.
|
|
15171
15481
|
if (data.ssrSkips?.length) appendSkips(path, data.ssrSkips);
|
|
15482
|
+
if (data.ssrLinkAttrs?.length) appendLinkAttrs(path, data.ssrLinkAttrs);
|
|
15172
15483
|
|
|
15173
15484
|
if (data.events) {
|
|
15174
15485
|
path.node.body.push(
|