@solidjs/babel-plugin 2.0.0-rc.15 → 2.0.0-rc.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +1 -1
- package/CHANGELOG.md +21 -0
- package/README.md +9 -8
- package/index.js +267 -203
- 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 [1m2.
|
|
10
|
+
[32mcreated [1mindex.js[22m in [1m2.3s[22m[39m
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,26 @@
|
|
|
1
1
|
# @solidjs/babel-plugin
|
|
2
2
|
|
|
3
|
+
## 2.0.0-rc.17
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 3e72783: The removed Solid 1.x attribute forms are now compile errors on native elements in the `dom` and `ssr` generates (#4187). `on:`, `oncapture:`, `attr:`, `bool:`, `use:` and `classList` used to compile silently into literal attributes (`<div use:tip="() => {}">`, `classlist="[object Object]"`). Both compilers now reject them with the same message, which names the 2.0 replacement: a camelCase handler or a `ref` callback for `on:`, a plain attribute for `attr:`, a boolean value for `bool:`, a `ref` directive factory for `use:`, and an object or array `class` for `classList`. Components, the universal generate, `prop:` and the XML namespaces (`xlink:`, `xml:`, `xmlns:`) are unaffected. The check runs only at compile time and adds no runtime code.
|
|
8
|
+
- 6cca8b5: Dev builds now warn with `UNCOMMITTED_INPUT_OVERWRITTEN` (#4186) when a stateful DOM property binding (`value`/`checked` on an input, `value` on a textarea, `value`/`selected` on an option, `muted` on media) is about to re-write the value it wrote last time over an edit the user never committed to the bound signal — e.g. `value={props.name} onChange={commit}` with no draft signal, when another dynamic attribute in the same template changes. Runtime behavior is unchanged: the write still happens, once per element and property the warning names the fix (write the signal from `onInput`, use `defaultValue`/`defaultChecked` for an uncontrolled element, or write a rejected edit back with `e.currentTarget.value = next`).
|
|
9
|
+
|
|
10
|
+
Production output is unchanged byte for byte. In dev DOM output both compilers write stateful properties through a new `setStatefulProperty(el, name, value)` helper instead of the inline `el.name = value`; hydratable output keeps `setProperty`, and `spread`/`assign` check in the runtime. The check lives only in `@solidjs/web`'s dev build; the production entry carries a plain `node[name] = value` stub that no production-compiled module imports, so it tree-shakes away.
|
|
11
|
+
|
|
12
|
+
The "Form inputs" section of the DOM RFC, the cheatsheet and the solid-2.0 skill now say that a change to any other dynamic attribute in the same template re-runs a stateful binding, link the new code, and drop `equals: false` as a way to reject input (it is what the warning reports); write the accepted value back with `e.currentTarget.value = next` instead.
|
|
13
|
+
|
|
14
|
+
- 2ba1c4a: A valueless `prop:` attribute (`<el prop:active />`) now compiles as `prop:active={true}`, matching bare plain attributes, instead of being dropped silently (#4180).
|
|
15
|
+
|
|
16
|
+
## 2.0.0-rc.16
|
|
17
|
+
|
|
18
|
+
### Patch Changes
|
|
19
|
+
|
|
20
|
+
- 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.
|
|
21
|
+
- 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.
|
|
22
|
+
- 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.
|
|
23
|
+
|
|
3
24
|
## 2.0.0-rc.15
|
|
4
25
|
|
|
5
26
|
### 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",
|
|
@@ -2233,11 +2321,6 @@ value,
|
|
|
2233
2321
|
);
|
|
2234
2322
|
}
|
|
2235
2323
|
|
|
2236
|
-
const assignment = t$1.assignmentExpression(
|
|
2237
|
-
"=",
|
|
2238
|
-
t$1.memberExpression(elem, t$1.identifier(name)),
|
|
2239
|
-
value
|
|
2240
|
-
);
|
|
2241
2324
|
if (
|
|
2242
2325
|
(name === "value" || name === "defaultValue") && (
|
|
2243
2326
|
tagName === "input" || tagName === "textarea") &&
|
|
@@ -2246,13 +2329,20 @@ value,
|
|
|
2246
2329
|
{
|
|
2247
2330
|
// prevents undefined on text input/textarea.value/defaultValue, fallback
|
|
2248
2331
|
// to empty string (#2957)
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
|
|
2332
|
+
value = t$1.logicalExpression("??", value, t$1.stringLiteral(""));
|
|
2333
|
+
}
|
|
2334
|
+
// Dev DOM output writes stateful properties (value/checked/selected/
|
|
2335
|
+
// muted) through `setStatefulProperty` — the same `elem[name] = value`,
|
|
2336
|
+
// preceded in the dev runtime by the UNCOMMITTED_INPUT_OVERWRITTEN check
|
|
2337
|
+
// (#4186). Production output keeps the inline assignment, byte for byte.
|
|
2338
|
+
if (config.dev && config.generate === "dom" && isStatefulDOMProperty(tagName, name)) {
|
|
2339
|
+
return t$1.callExpression(registerImportMethod(path, "setStatefulProperty"), [
|
|
2340
|
+
elem,
|
|
2341
|
+
t$1.stringLiteral(name),
|
|
2342
|
+
value]
|
|
2253
2343
|
);
|
|
2254
2344
|
}
|
|
2255
|
-
return
|
|
2345
|
+
return t$1.assignmentExpression("=", t$1.memberExpression(elem, t$1.identifier(name)), value);
|
|
2256
2346
|
}
|
|
2257
2347
|
|
|
2258
2348
|
const ns = name.indexOf(":") > -1 && Namespaces[name.split(":")[0]];
|
|
@@ -2498,6 +2588,8 @@ results)
|
|
|
2498
2588
|
t$1.jsxExpressionContainer(p.value)
|
|
2499
2589
|
);
|
|
2500
2590
|
newAttr._styleProperty = true;
|
|
2591
|
+
// The split-out property is located at the `style` attribute it came from.
|
|
2592
|
+
newAttr.loc = styleObjectAttribute.node.loc;
|
|
2501
2593
|
path.
|
|
2502
2594
|
get("openingElement").
|
|
2503
2595
|
node.attributes.splice(Number(styleObjectAttribute.key) + ++i, 0, newAttr);
|
|
@@ -2548,13 +2640,16 @@ results)
|
|
|
2548
2640
|
))
|
|
2549
2641
|
{
|
|
2550
2642
|
classArrayAttribute.node.value = t$1.stringLiteral(staticClasses.join(" "));
|
|
2643
|
+
const dynamicClassAttribute = t$1.jsxAttribute(
|
|
2644
|
+
t$1.jsxIdentifier("class"),
|
|
2645
|
+
t$1.jsxExpressionContainer(dynamicClassElement)
|
|
2646
|
+
);
|
|
2647
|
+
// `sourceNames.locations`: the split-out object locates at the array
|
|
2648
|
+
// attribute it came from.
|
|
2649
|
+
dynamicClassAttribute.loc = classArrayAttribute.node.loc;
|
|
2551
2650
|
path.
|
|
2552
2651
|
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
|
-
);
|
|
2652
|
+
node.attributes.splice(Number(classArrayAttribute.key) + 1, 0, dynamicClassAttribute);
|
|
2558
2653
|
}
|
|
2559
2654
|
}
|
|
2560
2655
|
|
|
@@ -2600,6 +2695,8 @@ results)
|
|
|
2600
2695
|
t$1.jsxExpressionContainer(p.value)
|
|
2601
2696
|
);
|
|
2602
2697
|
newAttr._classProperty = true;
|
|
2698
|
+
// The split-out property is located at the `class` attribute it came from.
|
|
2699
|
+
newAttr.loc = classListAttribute.node.loc;
|
|
2603
2700
|
path.
|
|
2604
2701
|
get("openingElement").
|
|
2605
2702
|
node.attributes.splice(Number(classListAttribute.key) + ++i, 0, newAttr);
|
|
@@ -2782,7 +2879,9 @@ results)
|
|
|
2782
2879
|
reservedNameSpace &&
|
|
2783
2880
|
!t$1.isJSXExpressionContainer(value))
|
|
2784
2881
|
{
|
|
2785
|
-
|
|
2882
|
+
// A valueless `prop:x` means `prop:x={true}`, like a bare plain
|
|
2883
|
+
// attribute (#4180).
|
|
2884
|
+
node.value = value = t$1.jsxExpressionContainer(value || t$1.booleanLiteral(true));
|
|
2786
2885
|
}
|
|
2787
2886
|
if (
|
|
2788
2887
|
t$1.isJSXExpressionContainer(value) && (
|
|
@@ -3008,7 +3107,11 @@ results)
|
|
|
3008
3107
|
tagName,
|
|
3009
3108
|
checkableInput,
|
|
3010
3109
|
styleProperty: isStyleProperty,
|
|
3011
|
-
classProperty: isClassProperty
|
|
3110
|
+
classProperty: isClassProperty,
|
|
3111
|
+
// `sourceNames.locations`: the attribute's start (for a property
|
|
3112
|
+
// split out of a `style={{…}}` / `class={{…}}` object, the
|
|
3113
|
+
// object attribute's start — see the pre-passes above).
|
|
3114
|
+
loc: sourceLocation(attribute, attribute.node)
|
|
3012
3115
|
});
|
|
3013
3116
|
} else {
|
|
3014
3117
|
results.exprs.push(
|
|
@@ -3169,6 +3272,10 @@ config)
|
|
|
3169
3272
|
if (!transformed) return memo;
|
|
3170
3273
|
transformed.allocatesIds = allocatesIds;
|
|
3171
3274
|
transformed.functionHole = functionHole;
|
|
3275
|
+
// `sourceNames.locations`: where the hole is written, for its insert
|
|
3276
|
+
// (a spread child `{...items()}` is a hole too).
|
|
3277
|
+
if (child.isJSXExpressionContainer() || child.isJSXSpreadChild())
|
|
3278
|
+
transformed.loc = sourceLocation(child, child.node);
|
|
3172
3279
|
const i = memo.length;
|
|
3173
3280
|
if (transformed.text && i && memo[i - 1].text) {
|
|
3174
3281
|
memo[i - 1].template =
|
|
@@ -3252,14 +3359,11 @@ config)
|
|
|
3252
3359
|
// is inserted directly and creates no effect to name.
|
|
3253
3360
|
const accessorHole =
|
|
3254
3361
|
child.dynamic || child.functionHole;
|
|
3362
|
+
// `sourceNames.locations`: `loc` after `name`, the hole's `{` start.
|
|
3255
3363
|
const emitInsert = (args) => {
|
|
3256
3364
|
if (accessorHole && namesBindings(getConfig(path))) {
|
|
3257
3365
|
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
|
-
);
|
|
3366
|
+
args.push(bindingOptions(`${tagName}.children`, child.loc));
|
|
3263
3367
|
}
|
|
3264
3368
|
results.exprs.push(t$1.expressionStatement(t$1.callExpression(insert, args)));
|
|
3265
3369
|
};
|
|
@@ -3559,7 +3663,14 @@ attributes,
|
|
|
3559
3663
|
// `sourceNames.bindings`: the tag as written rides as spread's trailing
|
|
3560
3664
|
// argument (past the runtime-only `skip` slot); the runtime labels its
|
|
3561
3665
|
// attribute effect `<tag>.spread` and its children insert `<tag>.children`.
|
|
3562
|
-
|
|
3666
|
+
// `sourceNames.locations`: the first spread attribute's start (`{...`)
|
|
3667
|
+
// follows the name as one more string argument.
|
|
3668
|
+
if (namesBindings(config)) {
|
|
3669
|
+
args.push(t$1.identifier("undefined"), t$1.stringLiteral(tagName));
|
|
3670
|
+
const spread = attributes.find((a) => t$1.isJSXSpreadAttribute(a.node));
|
|
3671
|
+
const loc = spread && sourceLocation(spread, spread.node);
|
|
3672
|
+
if (loc !== undefined) args.push(t$1.stringLiteral(loc));
|
|
3673
|
+
}
|
|
3563
3674
|
|
|
3564
3675
|
return [
|
|
3565
3676
|
filteredAttributes,
|
|
@@ -3775,20 +3886,18 @@ ownerId)
|
|
|
3775
3886
|
// binding's `<tag>.<attribute>` as written (undoing the `prop:` the locked
|
|
3776
3887
|
// DOM property pre-pass added to `value`, `checked`, …), the merged effect
|
|
3777
3888
|
// listing all of its bindings — as a trailing `{ name }` options argument.
|
|
3889
|
+
// `sourceNames.locations` adds `loc` after it: the first binding's
|
|
3890
|
+
// attribute start (a merged effect points at the first attribute it lists).
|
|
3778
3891
|
const label = namesBindings(config) ?
|
|
3779
3892
|
[
|
|
3780
|
-
|
|
3781
|
-
|
|
3782
|
-
|
|
3783
|
-
|
|
3784
|
-
|
|
3785
|
-
|
|
3786
|
-
|
|
3787
|
-
|
|
3788
|
-
).
|
|
3789
|
-
join(", ")
|
|
3790
|
-
)
|
|
3791
|
-
)]
|
|
3893
|
+
bindingOptions(
|
|
3894
|
+
dynamics.
|
|
3895
|
+
map(
|
|
3896
|
+
(d) =>
|
|
3897
|
+
`${d.tagName}.${isLockedDOMProperty(d.tagName, d.key) ? d.key.replace(/^prop:/, "") : d.key}`
|
|
3898
|
+
).
|
|
3899
|
+
join(", "),
|
|
3900
|
+
dynamics.find((d) => d.loc !== undefined)?.loc
|
|
3792
3901
|
)] :
|
|
3793
3902
|
|
|
3794
3903
|
[];
|
|
@@ -5159,13 +5268,6 @@ info)
|
|
|
5159
5268
|
// holes evaluated. The hole never takes a hydration id.
|
|
5160
5269
|
const linkEntries =
|
|
5161
5270
|
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
5271
|
|
|
5170
5272
|
attributes.forEach((attribute) => {
|
|
5171
5273
|
if (!t.isJSXAttribute(attribute.node)) return;
|
|
@@ -5196,23 +5298,13 @@ info)
|
|
|
5196
5298
|
{
|
|
5197
5299
|
if (t.isJSXEmptyExpression(value.expression)) return;
|
|
5198
5300
|
if (key === "ref") {
|
|
5199
|
-
if (info.serverComponents) {
|
|
5200
|
-
claims.push(["ref", value.expression]);
|
|
5201
|
-
return;
|
|
5202
|
-
}
|
|
5203
5301
|
results.declarations.push(
|
|
5204
5302
|
t.variableDeclarator(path.scope.generateUidIdentifier("_ref$"), value.expression)
|
|
5205
5303
|
);
|
|
5206
5304
|
return;
|
|
5207
5305
|
}
|
|
5208
5306
|
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
|
-
}
|
|
5307
|
+
if (isEventName(key)) return;
|
|
5216
5308
|
if (ChildProperties.has(key)) {
|
|
5217
5309
|
if (key === "children" && VoidElements.has(tagName)) return;
|
|
5218
5310
|
if (info.hydratable && key === "textContent" && value && value.expression) {
|
|
@@ -5260,30 +5352,9 @@ info)
|
|
|
5260
5352
|
value.expression = t.assignmentExpression("=", temp, expression);
|
|
5261
5353
|
linkEntries.push([key, t.cloneNode(temp), !!isDynamicValue]);
|
|
5262
5354
|
}
|
|
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
5355
|
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.
|
|
5356
|
+
// The whole attribute must be absent for nullish values. Capture the
|
|
5357
|
+
// value once before testing it.
|
|
5287
5358
|
const id = path.scope.generateUidIdentifier("v$");
|
|
5288
5359
|
const attr = t.callExpression(
|
|
5289
5360
|
t.arrowFunctionExpression(
|
|
@@ -5492,28 +5563,6 @@ info)
|
|
|
5492
5563
|
results.template.push("");
|
|
5493
5564
|
results.templateValues.push(hole);
|
|
5494
5565
|
}
|
|
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
5566
|
if (!hasChildren && children) {
|
|
5518
5567
|
path.node.children.push(children);
|
|
5519
5568
|
}
|
|
@@ -5639,7 +5688,7 @@ results,
|
|
|
5639
5688
|
|
|
5640
5689
|
function createElement(
|
|
5641
5690
|
path,
|
|
5642
|
-
{ topLevel, hydratable
|
|
5691
|
+
{ topLevel, hydratable })
|
|
5643
5692
|
{
|
|
5644
5693
|
const tagName = getTagName(path.node),
|
|
5645
5694
|
config = getConfig(path),
|
|
@@ -5692,22 +5741,6 @@ path,
|
|
|
5692
5741
|
const node = attribute.node;
|
|
5693
5742
|
return !(t.isJSXAttribute(node) && t.isJSXIdentifier(node.name) && node.name.name === "ref");
|
|
5694
5743
|
});
|
|
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
5744
|
let props;
|
|
5712
5745
|
// Attributes written AFTER the last spread are markup, not a source: no
|
|
5713
5746
|
// later source can override them, so they go to `ssrElement` as its
|
|
@@ -5727,13 +5760,26 @@ path,
|
|
|
5727
5760
|
// keys.
|
|
5728
5761
|
const tail = [];
|
|
5729
5762
|
const skipKeys = [];
|
|
5730
|
-
|
|
5731
|
-
|
|
5732
|
-
|
|
5733
|
-
//
|
|
5734
|
-
// the
|
|
5735
|
-
|
|
5736
|
-
{
|
|
5763
|
+
// Plain SSR evaluates a named `ref` expression without calling the ref, as
|
|
5764
|
+
// the template path does (`_ref$ = expr`), so a ref factory's owned setup
|
|
5765
|
+
// (`ref={tooltip()}`) runs on both sides and the hydration ids after it
|
|
5766
|
+
// line up (#4081). The client evaluates it after applying the spread and
|
|
5767
|
+
// before the children; `ssrElement` calls the `attrs` thunk at that point.
|
|
5768
|
+
// Only an expression that calls something can run setup: `ref={el}` and
|
|
5769
|
+
// `ref={props.ref}` stay out of the thunk.
|
|
5770
|
+
const refs = [];
|
|
5771
|
+
for (const attribute of attributes) {
|
|
5772
|
+
const node = attribute.node;
|
|
5773
|
+
if (
|
|
5774
|
+
t.isJSXAttribute(node) &&
|
|
5775
|
+
t.isJSXIdentifier(node.name, { name: "ref" }) &&
|
|
5776
|
+
t.isJSXExpressionContainer(node.value) &&
|
|
5777
|
+
!t.isJSXEmptyExpression(node.value.expression) &&
|
|
5778
|
+
callsOutsideFunctions(node.value.expression))
|
|
5779
|
+
|
|
5780
|
+
refs.push(node.value.expression);
|
|
5781
|
+
}
|
|
5782
|
+
if (propAttributes.length === 1 && t.isJSXSpreadAttribute(propAttributes[0].node)) {
|
|
5737
5783
|
props = [propAttributes[0].node.argument];
|
|
5738
5784
|
} else {
|
|
5739
5785
|
props = [];
|
|
@@ -5779,28 +5825,7 @@ path,
|
|
|
5779
5825
|
node.name.name;
|
|
5780
5826
|
|
|
5781
5827
|
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
|
-
}
|
|
5828
|
+
if (key === "ref" || isEventName(key)) return;
|
|
5804
5829
|
if (key.startsWith("prop:")) return;
|
|
5805
5830
|
// An anchor's link-relevant attributes stay a source, never baked
|
|
5806
5831
|
// markup: `ssrElement` collects them from the walk for the render's
|
|
@@ -5869,76 +5894,52 @@ path,
|
|
|
5869
5894
|
// set) and attribute string — a literal when every part is static, else a
|
|
5870
5895
|
// thunk concatenating the parts in source order. A `false` static bakes to
|
|
5871
5896
|
// no markup but still skips its key.
|
|
5872
|
-
if (skipKeys.length) {
|
|
5897
|
+
if (skipKeys.length || refs.length) {
|
|
5873
5898
|
let markup;
|
|
5874
|
-
if (tail.every((part) => typeof part === "string"))
|
|
5875
|
-
|
|
5899
|
+
if (tail.every((part) => typeof part === "string")) {
|
|
5900
|
+
markup = t.stringLiteral(tail.join(""));
|
|
5901
|
+
// The refs run after the tail's (static) markup and return it.
|
|
5902
|
+
if (refs.length)
|
|
5903
|
+
markup = t.arrowFunctionExpression([], t.sequenceExpression([...refs, markup]));
|
|
5904
|
+
} else {
|
|
5876
5905
|
let concat;
|
|
5877
5906
|
for (const part of tail) {
|
|
5878
5907
|
const expr = typeof part === "string" ? t.stringLiteral(part) : part;
|
|
5879
5908
|
concat = concat ? t.binaryExpression("+", concat, expr) : expr;
|
|
5880
5909
|
}
|
|
5910
|
+
// The tail's getters run first, as the client applies them with the
|
|
5911
|
+
// spread before it evaluates the ref: `markup + (ref, "")`.
|
|
5912
|
+
if (refs.length)
|
|
5913
|
+
concat = t.binaryExpression(
|
|
5914
|
+
"+",
|
|
5915
|
+
concat,
|
|
5916
|
+
t.sequenceExpression([...refs, t.stringLiteral("")])
|
|
5917
|
+
);
|
|
5881
5918
|
markup = t.arrowFunctionExpression([], concat);
|
|
5882
5919
|
}
|
|
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)));
|
|
5920
|
+
args.push(skipKeys.length ? registerSkip(path, skipKeys) : t.identifier("undefined"), markup);
|
|
5888
5921
|
}
|
|
5889
5922
|
const exprs = [t.callExpression(registerImportMethod(path, "ssrElement"), args)];
|
|
5890
5923
|
return { exprs, template: "", declarations: [], dynamics: [], spreadElement: true };
|
|
5891
5924
|
}
|
|
5892
5925
|
|
|
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
|
-
}
|
|
5926
|
+
/** Does evaluating `node` call anything? A function's body does not run. */
|
|
5927
|
+
function callsOutsideFunctions(node) {
|
|
5928
|
+
if (
|
|
5929
|
+
t.isCallExpression(node) ||
|
|
5930
|
+
t.isOptionalCallExpression(node) ||
|
|
5931
|
+
t.isNewExpression(node) ||
|
|
5932
|
+
t.isTaggedTemplateExpression(node))
|
|
5919
5933
|
|
|
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
|
-
);
|
|
5934
|
+
return true;
|
|
5935
|
+
if (t.isFunction(node)) return false;
|
|
5936
|
+
for (const key of t.VISITOR_KEYS[node.type] ?? []) {
|
|
5937
|
+
const child = node[key];
|
|
5938
|
+
if (Array.isArray(child)) {
|
|
5939
|
+
for (const c of child) if (c && callsOutsideFunctions(c)) return true;
|
|
5940
|
+
} else if (child && typeof child.type === "string" && callsOutsideFunctions(child)) return true;
|
|
5941
|
+
}
|
|
5942
|
+
return false;
|
|
5942
5943
|
}
|
|
5943
5944
|
|
|
5944
5945
|
/**
|
|
@@ -6451,6 +6452,10 @@ path)
|
|
|
6451
6452
|
sourceNames(config).components && (config.generate === "dom" || config.generate === "ssr");
|
|
6452
6453
|
if (labelled) {
|
|
6453
6454
|
componentArgs.push(t__namespace.stringLiteral(tagName));
|
|
6455
|
+
// `sourceNames.locations`: the opening element's start (`<Home`) as a
|
|
6456
|
+
// fourth argument, `file:line:col`, so a diagnostic can point at the JSX.
|
|
6457
|
+
const loc = sourceLocation(path, path.node.openingElement);
|
|
6458
|
+
if (loc !== undefined) componentArgs.push(t__namespace.stringLiteral(loc));
|
|
6454
6459
|
}
|
|
6455
6460
|
// SSR's prod `createComponent` is literally `Comp(props || {})`. Since the
|
|
6456
6461
|
// compiler always emits a real `props[0]` object expression above (see the
|
|
@@ -6585,6 +6590,63 @@ config)
|
|
|
6585
6590
|
results.exprs.push(childNodes.length === 1 ? childNodes[0] : t__namespace.arrayExpression(childNodes));
|
|
6586
6591
|
}
|
|
6587
6592
|
|
|
6593
|
+
/**
|
|
6594
|
+
* Solid 1.x attribute forms that 2.0 removed (#4187). Left alone they
|
|
6595
|
+
* compile into literal attributes (`<div use:tip="() => {}">`,
|
|
6596
|
+
* `classlist="[object Object]"`) with no diagnostic, so on native elements
|
|
6597
|
+
* of the web generates (`dom` / `ssr`) they are a compile error that names
|
|
6598
|
+
* the 2.0 replacement. Components are untouched (their props are
|
|
6599
|
+
* user-defined names with no compiler semantics), as is the universal
|
|
6600
|
+
* generate (a custom renderer's `setProp` owns its own naming), and every
|
|
6601
|
+
* other namespace — `prop:`, `xlink:`, `xml:`, `xmlns:`, `svg:`, … — still
|
|
6602
|
+
* compiles as before. The message text is mirrored in the native compiler
|
|
6603
|
+
* (`packages/compiler/src/shared/removed.rs`); keep the two in sync.
|
|
6604
|
+
*/
|
|
6605
|
+
function removedAttributeMessage(tagName, key) {
|
|
6606
|
+
if (key === "classList") {
|
|
6607
|
+
return (
|
|
6608
|
+
`\`classList\` on <${tagName}> was removed in Solid 2.0. ` +
|
|
6609
|
+
"Use `class` with an object or array instead: `class={{ active: isActive() }}` " +
|
|
6610
|
+
'or `class={["base", { active: isActive() }]}`.');
|
|
6611
|
+
|
|
6612
|
+
}
|
|
6613
|
+
const colon = key.indexOf(":");
|
|
6614
|
+
if (colon < 0) return;
|
|
6615
|
+
const namespace = key.slice(0, colon);
|
|
6616
|
+
const name = key.slice(colon + 1);
|
|
6617
|
+
const removed = `\`${key}\` on <${tagName}> uses the \`${namespace}:\` namespace, which was removed in Solid 2.0.`;
|
|
6618
|
+
switch (namespace) {
|
|
6619
|
+
case "on":
|
|
6620
|
+
case "oncapture":
|
|
6621
|
+
return (
|
|
6622
|
+
`${removed} Use a camelCase handler such as \`onClick={handler}\` for Solid events, ` +
|
|
6623
|
+
"or a `ref` callback that calls `addEventListener` for listener options or custom event names.");
|
|
6624
|
+
|
|
6625
|
+
case "attr":
|
|
6626
|
+
return `${removed} Attributes are the default in 2.0: write \`${name}={value}\` (or \`prop:${name}\` to set a DOM property).`;
|
|
6627
|
+
case "bool":
|
|
6628
|
+
return `${removed} Boolean values add or remove the attribute in 2.0: write \`${name}={condition}\`.`;
|
|
6629
|
+
case "use":
|
|
6630
|
+
return (
|
|
6631
|
+
`${removed} Use a \`ref\` directive factory instead: \`ref={${name}(options)}\`, ` +
|
|
6632
|
+
`or \`ref={${name}}\` without options; arrays compose (\`ref={[a, b]}\`).`);
|
|
6633
|
+
|
|
6634
|
+
}
|
|
6635
|
+
}
|
|
6636
|
+
|
|
6637
|
+
/** Throws a code-frame error at the first removed 1.x attribute form. */
|
|
6638
|
+
function assertNoRemovedAttributes(path, tagName) {
|
|
6639
|
+
for (const attribute of path.get("openingElement").get("attributes")) {
|
|
6640
|
+
if (!attribute.isJSXAttribute()) continue;
|
|
6641
|
+
const name = attribute.node.name;
|
|
6642
|
+
const key = t__namespace.isJSXNamespacedName(name) ?
|
|
6643
|
+
`${name.namespace.name}:${name.name.name}` :
|
|
6644
|
+
name.name;
|
|
6645
|
+
const message = removedAttributeMessage(tagName, key);
|
|
6646
|
+
if (message) throw attribute.buildCodeFrameError(message);
|
|
6647
|
+
}
|
|
6648
|
+
}
|
|
6649
|
+
|
|
6588
6650
|
function isTransformConditionStatements(
|
|
6589
6651
|
expr)
|
|
6590
6652
|
{
|
|
@@ -6865,11 +6927,13 @@ info = {})
|
|
|
6865
6927
|
);
|
|
6866
6928
|
|
|
6867
6929
|
if (tagRenderer?.name === "dom" || getConfig(path).generate === "dom") {
|
|
6930
|
+
assertNoRemovedAttributes(path, tagName);
|
|
6868
6931
|
renameElementKey(path, false);
|
|
6869
6932
|
return transformElement$2(path, info);
|
|
6870
6933
|
}
|
|
6871
6934
|
|
|
6872
6935
|
if (getConfig(path).generate === "ssr") {
|
|
6936
|
+
assertNoRemovedAttributes(path, tagName);
|
|
6873
6937
|
renameElementKey(path, true);
|
|
6874
6938
|
return transformElement$1(path, info);
|
|
6875
6939
|
}
|