@solidjs/babel-plugin 2.0.0-rc.13 → 2.0.0-rc.14
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 +17 -0
- package/README.md +2 -2
- package/index.js +137 -64
- 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.2s[22m[39m
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,22 @@
|
|
|
1
1
|
# @solidjs/babel-plugin
|
|
2
2
|
|
|
3
|
+
## 2.0.0-rc.14
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 59fb244: SSR `hoistProps` skips TypeScript type positions when collecting captures, so on authored TSX (types parsed, stripped later) a `typeof x` query or a type named like a value no longer captures that value, or forces the literal fallback when it is reassigned (#3828).
|
|
8
|
+
- 3c1f809: Preserve `/* istanbul ignore … */` and `/* c8 ignore … */` block comments written as JSX children on the generated component `children` getter. Line comments are not carried.
|
|
9
|
+
- 98b916d: Fix compiled SSR output that renders `style=""` or `class=""` for `null` and `undefined`. Babel and the native compiler now omit these attributes on elements without a spread.
|
|
10
|
+
- 98d35b9: Fix computed keys in compiled SSR `style` objects (builds without `serverComponents`). A computed key's name got no `;`, so `style={{ color: "red", [k]: v }}` rendered `color:redtop:1px` and the browser dropped both declarations. Hydration keeps the server's inline style, so they stayed dropped. A nullish first value also no longer leaves a leading `;`. Both compilers now pass a multi-entry object to a new `ssrStyleProperties(name, value, …)` helper, which writes the separator only between the entries it writes; a single entry keeps `ssrStyleProperty`.
|
|
11
|
+
- 4f67697: **Breaking:** only `on` followed by an uppercase letter (`onClick`, `onPointerDown`) is an event handler. Lowercase `on*` names (`onclick`, `onmouseover`) are plain attributes everywhere:
|
|
12
|
+
- Both compilers compile `onclick={expr}` like any other attribute (`setAttribute`, reactive when `expr` is dynamic) instead of binding a delegated or native event, and SSR renders it as an escaped attribute instead of dropping it. A leftover 1.x `on:click={fn}` is likewise a plain namespaced attribute (it previously compiled to `addEventListener(":click", fn)`).
|
|
13
|
+
- `@solidjs/web` `spread`/`assign` set lowercase `on*` keys as attributes, the server spread walk renders them, and `useHead` applies lowercase `on*` attributes (camelCase handler names stay skipped). `ssrAttribute` escapes a function value instead of interpolating its source raw.
|
|
14
|
+
- `@solidjs/html` and `@solidjs/h` elements wrap a function passed to a lowercase `on*` in a getter like any other attribute; only `onXxx` and `ref` are exempt.
|
|
15
|
+
- `@solidjs/html` components follow `@solidjs/h`'s rule: every zero-argument function prop, `onXxx` handlers and `ref` included, is a getter, so a component handler must declare its event argument (`onClick=${e => …}`).
|
|
16
|
+
- New dev-only check `LOWERCASE_EVENT_ATTRIBUTE` (added to the `DiagnosticCode` union): warns once per attribute name when a function is set on a lowercase `on*` (or `on:`) attribute, naming the camelCase handler to use.
|
|
17
|
+
|
|
18
|
+
- 7513131: Preserve the source location of captured references in SSR props constructors so source maps point to the authored expression, including dynamic TSRX tags.
|
|
19
|
+
|
|
3
20
|
## 2.0.0-rc.13
|
|
4
21
|
|
|
5
22
|
## 2.0.0-rc.12
|
package/README.md
CHANGED
|
@@ -253,7 +253,7 @@ Inline style attributes in templates when the value is a string or `Record<strin
|
|
|
253
253
|
- Type: `boolean`
|
|
254
254
|
- Default: `false`
|
|
255
255
|
|
|
256
|
-
SSR-only: keep attribute-slot positions on intrinsic elements bindable — `ref` / `
|
|
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
257
|
|
|
258
258
|
### hoistProps
|
|
259
259
|
|
|
@@ -329,7 +329,7 @@ const Parent = () => {
|
|
|
329
329
|
|
|
330
330
|
### on(eventName)
|
|
331
331
|
|
|
332
|
-
CamelCase attributes such as `onClick` are event handlers. The compiler delegates events that bubble or compose, otherwise it uses Level 1 `on_____` properties.
|
|
332
|
+
CamelCase attributes such as `onClick` are event handlers. The compiler delegates events that bubble or compose, otherwise it uses Level 1 `on_____` properties. Only `on` followed by an uppercase letter is an event: lowercase names such as `onclick` are plain attributes, compiled (and server-rendered) like any other attribute.
|
|
333
333
|
|
|
334
334
|
Pass an array to bind a value: the second item is the first argument to the handler, the event is second.
|
|
335
335
|
|
package/index.js
CHANGED
|
@@ -427,13 +427,38 @@ path)
|
|
|
427
427
|
|
|
428
428
|
}
|
|
429
429
|
|
|
430
|
+
const coverageIgnoreCommentData = "solid.coverageIgnoreComments";
|
|
431
|
+
|
|
432
|
+
function getCoverageIgnoreComments(path) {
|
|
433
|
+
return path.getData(coverageIgnoreCommentData);
|
|
434
|
+
}
|
|
435
|
+
|
|
430
436
|
// remove unnecessary JSX Text nodes
|
|
431
437
|
function filterChildren(children) {
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
438
|
+
const filtered = [];
|
|
439
|
+
let pendingCoverageIgnoreComments = [];
|
|
440
|
+
|
|
441
|
+
for (const path of children) {
|
|
442
|
+
const child = path.node;
|
|
443
|
+
if (t__namespace.isJSXExpressionContainer(child) && t__namespace.isJSXEmptyExpression(child.expression)) {
|
|
444
|
+
pendingCoverageIgnoreComments.push(
|
|
445
|
+
...(child.expression.innerComments?.filter(
|
|
446
|
+
(comment) =>
|
|
447
|
+
comment.type === "CommentBlock" && /^\s*(istanbul|c8)\s+ignore\b/.test(comment.value)
|
|
448
|
+
) ?? [])
|
|
449
|
+
);
|
|
450
|
+
continue;
|
|
451
|
+
}
|
|
452
|
+
if (t__namespace.isJSXText(child) && /^[\r\n]\s*$/.test(child.extra?.raw ?? "")) {
|
|
453
|
+
continue;
|
|
454
|
+
}
|
|
455
|
+
if (pendingCoverageIgnoreComments.length) {
|
|
456
|
+
path.setData(coverageIgnoreCommentData, pendingCoverageIgnoreComments);
|
|
457
|
+
pendingCoverageIgnoreComments = [];
|
|
458
|
+
}
|
|
459
|
+
filtered.push(path);
|
|
460
|
+
}
|
|
461
|
+
return filtered;
|
|
437
462
|
}
|
|
438
463
|
|
|
439
464
|
function checkLength(children) {
|
|
@@ -461,6 +486,12 @@ function trimWhitespace(text) {
|
|
|
461
486
|
return text.replace(/\s+/g, " ");
|
|
462
487
|
}
|
|
463
488
|
|
|
489
|
+
// Only `on` + an uppercase letter is an event handler. Lowercase `on*`
|
|
490
|
+
// names (`onclick`) are plain attributes.
|
|
491
|
+
function isEventName(name) {
|
|
492
|
+
return /^on[A-Z]/.test(name);
|
|
493
|
+
}
|
|
494
|
+
|
|
464
495
|
function toEventName(name) {
|
|
465
496
|
return name.slice(2).toLowerCase();
|
|
466
497
|
}
|
|
@@ -2783,7 +2814,7 @@ results)
|
|
|
2783
2814
|
}
|
|
2784
2815
|
} else if (key === "children") {
|
|
2785
2816
|
children = value;
|
|
2786
|
-
} else if (key
|
|
2817
|
+
} else if (isEventName(key)) {
|
|
2787
2818
|
const ev = toEventName(key);
|
|
2788
2819
|
if (
|
|
2789
2820
|
config.delegateEvents && (
|
|
@@ -4071,6 +4102,7 @@ function takeHoistedProps(program) {
|
|
|
4071
4102
|
|
|
4072
4103
|
|
|
4073
4104
|
|
|
4105
|
+
|
|
4074
4106
|
function keyName(node) {
|
|
4075
4107
|
if (node.computed) return undefined;
|
|
4076
4108
|
if (t__namespace.isIdentifier(node.key)) return node.key.name;
|
|
@@ -4089,6 +4121,20 @@ function insideRealFunction(path, stop) {
|
|
|
4089
4121
|
return false;
|
|
4090
4122
|
}
|
|
4091
4123
|
|
|
4124
|
+
/** Type positions are erased before the code runs: a name there (the `x` of
|
|
4125
|
+
* `typeof x`, or a type that shares a value's name) is not a value the
|
|
4126
|
+
* constructor can be handed. `TSType` covers `implements`/`extends` heritage. */
|
|
4127
|
+
function isTypePosition(path) {
|
|
4128
|
+
return (
|
|
4129
|
+
path.isTSType() ||
|
|
4130
|
+
path.isTSTypeAnnotation() ||
|
|
4131
|
+
path.isTSTypeParameterDeclaration() ||
|
|
4132
|
+
path.isTSTypeParameterInstantiation() ||
|
|
4133
|
+
path.isTSInterfaceDeclaration() ||
|
|
4134
|
+
path.isTSTypeAliasDeclaration());
|
|
4135
|
+
|
|
4136
|
+
}
|
|
4137
|
+
|
|
4092
4138
|
/** Is `binding` initialized by the time the site constructs its props? The
|
|
4093
4139
|
* getter read it lazily; the constructor reads it now. A parameter or a
|
|
4094
4140
|
* hoisted function always is. Otherwise the declaration must precede the
|
|
@@ -4174,6 +4220,10 @@ function hoistSite(p, state) {
|
|
|
4174
4220
|
if (q.node.meta.name === "new" && !insideRealFunction(q, m)) fallback = true;
|
|
4175
4221
|
return;
|
|
4176
4222
|
}
|
|
4223
|
+
if (isTypePosition(q)) {
|
|
4224
|
+
q.skip();
|
|
4225
|
+
return;
|
|
4226
|
+
}
|
|
4177
4227
|
if (!q.isIdentifier()) return;
|
|
4178
4228
|
const name = q.node.name;
|
|
4179
4229
|
let write = false;
|
|
@@ -4206,7 +4256,7 @@ function hoistSite(p, state) {
|
|
|
4206
4256
|
}
|
|
4207
4257
|
let capture = captures.get(binding);
|
|
4208
4258
|
if (!capture) {
|
|
4209
|
-
capture = { binding, name, index: captures.size };
|
|
4259
|
+
capture = { binding, name, reference: q.node, index: captures.size };
|
|
4210
4260
|
captures.set(binding, capture);
|
|
4211
4261
|
}
|
|
4212
4262
|
uses.add(binding);
|
|
@@ -4228,7 +4278,9 @@ function hoistSite(p, state) {
|
|
|
4228
4278
|
for (const capture of captures.values()) {
|
|
4229
4279
|
const param = scope.generateUidIdentifier("p");
|
|
4230
4280
|
params.push(param);
|
|
4231
|
-
|
|
4281
|
+
const argument = t__namespace.identifier(capture.name);
|
|
4282
|
+
argument.loc = capture.reference.loc;
|
|
4283
|
+
args.push(argument);
|
|
4232
4284
|
body.push(
|
|
4233
4285
|
t__namespace.expressionStatement(
|
|
4234
4286
|
t__namespace.assignmentExpression(
|
|
@@ -4910,13 +4962,11 @@ info)
|
|
|
4910
4962
|
return;
|
|
4911
4963
|
}
|
|
4912
4964
|
if (key.startsWith("prop:")) return;
|
|
4913
|
-
if (key
|
|
4965
|
+
if (isEventName(key)) {
|
|
4914
4966
|
// `onXxx` lowercases to the event name — the client runtime's own
|
|
4915
4967
|
// derivation (`onClick` -> `click`); the position is bound under it.
|
|
4916
|
-
if (info.serverComponents)
|
|
4917
|
-
|
|
4918
|
-
if (pos) claims.push([pos, value.expression]);
|
|
4919
|
-
}
|
|
4968
|
+
if (info.serverComponents)
|
|
4969
|
+
claims.push([toEventName(key), value.expression]);
|
|
4920
4970
|
return;
|
|
4921
4971
|
}
|
|
4922
4972
|
if (ChildProperties.has(key)) {
|
|
@@ -4950,15 +5000,11 @@ info)
|
|
|
4950
5000
|
checkMember: true,
|
|
4951
5001
|
checkTags: true
|
|
4952
5002
|
});
|
|
4953
|
-
// Server components (principles §9.2.3)
|
|
4954
|
-
//
|
|
4955
|
-
//
|
|
4956
|
-
//
|
|
4957
|
-
//
|
|
4958
|
-
// bound. Under the option the whole attribute is a runtime hole,
|
|
4959
|
-
// `ssrElementAttribute("class", x)`, whose helper emits the same
|
|
4960
|
-
// bytes for a plain value and the position marker for a stand-in.
|
|
4961
|
-
// Object literals stay objects (no inlining) for the same reason.
|
|
5003
|
+
// Server components (principles §9.2.3) need a whole-attribute
|
|
5004
|
+
// serializer that can emit a position marker for a binding-slot
|
|
5005
|
+
// value. The ordinary class/style serializers cannot bind slots.
|
|
5006
|
+
// Object literals stay objects (no inlining) so a slot used as a
|
|
5007
|
+
// property's value can also emit its marker.
|
|
4962
5008
|
if (info.serverComponents && (key === "class" || key === "style")) {
|
|
4963
5009
|
const attr = t.callExpression(registerImportMethod(path, "ssrElementAttribute"), [
|
|
4964
5010
|
t.stringLiteral(key),
|
|
@@ -4974,6 +5020,36 @@ info)
|
|
|
4974
5020
|
);
|
|
4975
5021
|
return;
|
|
4976
5022
|
}
|
|
5023
|
+
if ((key === "class" || key === "style") && !t.isObjectExpression(value.expression)) {
|
|
5024
|
+
// The whole attribute must be absent for nullish values. Keep the
|
|
5025
|
+
// ordinary serializers: ssrElementAttribute would also bind slots
|
|
5026
|
+
// without serverComponents. Capture the value once before testing it.
|
|
5027
|
+
const id = path.scope.generateUidIdentifier("v$");
|
|
5028
|
+
const attr = t.callExpression(
|
|
5029
|
+
t.arrowFunctionExpression(
|
|
5030
|
+
[id],
|
|
5031
|
+
t.conditionalExpression(
|
|
5032
|
+
t.binaryExpression("==", t.cloneNode(id), t.nullLiteral()),
|
|
5033
|
+
t.stringLiteral(""),
|
|
5034
|
+
t.callExpression(registerImportMethod(path, "ssrAttribute"), [
|
|
5035
|
+
t.stringLiteral(key),
|
|
5036
|
+
t.callExpression(
|
|
5037
|
+
registerImportMethod(path, key === "class" ? "ssrClassName" : "ssrStyle"),
|
|
5038
|
+
[t.cloneNode(id)]
|
|
5039
|
+
)]
|
|
5040
|
+
)
|
|
5041
|
+
)
|
|
5042
|
+
),
|
|
5043
|
+
[value.expression]
|
|
5044
|
+
);
|
|
5045
|
+
results.template.push("");
|
|
5046
|
+
results.templateValues.push(
|
|
5047
|
+
isDynamicValue ?
|
|
5048
|
+
hoistExpression(path, results, t.arrowFunctionExpression([], attr), { group: true }) :
|
|
5049
|
+
attr
|
|
5050
|
+
);
|
|
5051
|
+
return;
|
|
5052
|
+
}
|
|
4977
5053
|
let doEscape = true;
|
|
4978
5054
|
let isBoolean =
|
|
4979
5055
|
t.isBooleanLiteral(value) ||
|
|
@@ -4988,52 +5064,48 @@ info)
|
|
|
4988
5064
|
if (value.expression.properties.length === 0) {
|
|
4989
5065
|
return;
|
|
4990
5066
|
}
|
|
4991
|
-
const
|
|
4992
|
-
|
|
5067
|
+
const entries = value.expression.properties.filter(
|
|
5068
|
+
(p) => t.isObjectProperty(p)
|
|
5069
|
+
);
|
|
5070
|
+
// One entry is `ssrStyleProperty(name, value)`; several go to
|
|
5071
|
+
// `ssrStyleProperties`, which writes the `;` only between the
|
|
5072
|
+
// entries it writes, so a nullish one leaves no separator behind.
|
|
5073
|
+
const helper = registerImportMethod(
|
|
5074
|
+
path,
|
|
5075
|
+
entries.length === 1 ? "ssrStyleProperty" : "ssrStyleProperties"
|
|
5076
|
+
);
|
|
5077
|
+
const args = entries.flatMap((p) => {
|
|
5078
|
+
let name;
|
|
4993
5079
|
if (p.computed) {
|
|
4994
5080
|
// Computed keys are user-controlled at runtime; wrap with
|
|
4995
|
-
// `_$escape(..., true)` so
|
|
4996
|
-
// string concat
|
|
4997
|
-
|
|
4998
|
-
return t.callExpression(registerImportMethod(path, "ssrStyleProperty"), [
|
|
4999
|
-
t.binaryExpression(
|
|
5081
|
+
// `_$escape(..., true)` so the style helpers can stay pure
|
|
5082
|
+
// string concat (the literal-key path is already safe).
|
|
5083
|
+
name = t.binaryExpression(
|
|
5000
5084
|
"+",
|
|
5001
|
-
t.callExpression(escape, [
|
|
5085
|
+
t.callExpression(registerImportMethod(path, "escape"), [
|
|
5002
5086
|
p.key,
|
|
5003
5087
|
t.booleanLiteral(true)]
|
|
5004
5088
|
),
|
|
5005
5089
|
t.stringLiteral(":")
|
|
5006
|
-
)
|
|
5007
|
-
|
|
5008
|
-
|
|
5009
|
-
p.
|
|
5010
|
-
|
|
5011
|
-
|
|
5012
|
-
)]
|
|
5090
|
+
);
|
|
5091
|
+
} else {
|
|
5092
|
+
name = t.stringLiteral(
|
|
5093
|
+
(t.isIdentifier(p.key) ?
|
|
5094
|
+
p.key.name :
|
|
5095
|
+
p.key.value) + ":"
|
|
5013
5096
|
);
|
|
5014
5097
|
}
|
|
5015
|
-
return
|
|
5016
|
-
|
|
5017
|
-
(i ? ";" : "") + (
|
|
5018
|
-
t.isIdentifier(p.key) ?
|
|
5019
|
-
p.key.name :
|
|
5020
|
-
p.key.value) +
|
|
5021
|
-
":"
|
|
5022
|
-
),
|
|
5098
|
+
return [
|
|
5099
|
+
name,
|
|
5023
5100
|
escapeExpression(
|
|
5024
5101
|
path,
|
|
5025
5102
|
p.value,
|
|
5026
5103
|
true,
|
|
5027
5104
|
true
|
|
5028
|
-
)]
|
|
5029
|
-
);
|
|
5030
|
-
});
|
|
5105
|
+
)];
|
|
5031
5106
|
|
|
5032
|
-
|
|
5033
|
-
|
|
5034
|
-
res = t.binaryExpression("+", res, props[i]);
|
|
5035
|
-
}
|
|
5036
|
-
value.expression = res;
|
|
5107
|
+
});
|
|
5108
|
+
value.expression = t.callExpression(helper, args);
|
|
5037
5109
|
} else {
|
|
5038
5110
|
value.expression = t.callExpression(registerImportMethod(path, "ssrStyle"), [
|
|
5039
5111
|
value.expression]
|
|
@@ -5403,12 +5475,11 @@ path,
|
|
|
5403
5475
|
node.name.name;
|
|
5404
5476
|
|
|
5405
5477
|
if (hasChildren && key === "children") return;
|
|
5406
|
-
if (key === "ref" || key
|
|
5478
|
+
if (key === "ref" || isEventName(key)) {
|
|
5407
5479
|
if (serverComponents && t.isJSXExpressionContainer(value)) {
|
|
5408
5480
|
const expression = value.expression;
|
|
5409
|
-
const pos = key === "ref" ? "ref" : key
|
|
5481
|
+
const pos = key === "ref" ? "ref" : toEventName(key);
|
|
5410
5482
|
if (
|
|
5411
|
-
pos &&
|
|
5412
5483
|
!(
|
|
5413
5484
|
t.isJSXEmptyExpression(expression) ||
|
|
5414
5485
|
t.isStringLiteral(expression) ||
|
|
@@ -6044,14 +6115,14 @@ path)
|
|
|
6044
6115
|
t__namespace.isFunction(childResult[0]) ?
|
|
6045
6116
|
childResult[0].body :
|
|
6046
6117
|
childResult[0];
|
|
6047
|
-
|
|
6048
|
-
|
|
6049
|
-
|
|
6050
|
-
|
|
6051
|
-
|
|
6052
|
-
t__namespace.isExpression(body) ? t__namespace.blockStatement([t__namespace.returnStatement(body)]) : body
|
|
6053
|
-
)
|
|
6118
|
+
const getter = t__namespace.objectMethod(
|
|
6119
|
+
"get",
|
|
6120
|
+
t__namespace.identifier("children"),
|
|
6121
|
+
[],
|
|
6122
|
+
t__namespace.isExpression(body) ? t__namespace.blockStatement([t__namespace.returnStatement(body)]) : body
|
|
6054
6123
|
);
|
|
6124
|
+
if (childResult[2]?.length) getter.leadingComments = childResult[2];
|
|
6125
|
+
runningObject.push(getter);
|
|
6055
6126
|
} else runningObject.push(t__namespace.objectProperty(t__namespace.identifier("children"), childResult[0]));
|
|
6056
6127
|
}
|
|
6057
6128
|
if (runningObject.length || !props.length) props.push(propsLiteral(runningObject));
|
|
@@ -6112,9 +6183,11 @@ config)
|
|
|
6112
6183
|
if (!filteredChildren.length) return;
|
|
6113
6184
|
let dynamic = false;
|
|
6114
6185
|
let pathNodes = [];
|
|
6186
|
+
let coverageIgnoreComments;
|
|
6115
6187
|
|
|
6116
6188
|
let transformedChildren = filteredChildren.reduce(
|
|
6117
6189
|
(memo, path) => {
|
|
6190
|
+
coverageIgnoreComments ||= getCoverageIgnoreComments(path);
|
|
6118
6191
|
if (t__namespace.isJSXText(path.node)) {
|
|
6119
6192
|
const v = htmlEntities.decode(trimWhitespace(path.node.extra?.raw ?? ""));
|
|
6120
6193
|
if (v.length) {
|
|
@@ -6171,7 +6244,7 @@ config)
|
|
|
6171
6244
|
transformedChildren = t__namespace.arrowFunctionExpression([], t__namespace.arrayExpression(transformedChildren));
|
|
6172
6245
|
dynamic = true;
|
|
6173
6246
|
}
|
|
6174
|
-
return [transformedChildren, dynamic];
|
|
6247
|
+
return [transformedChildren, dynamic, coverageIgnoreComments];
|
|
6175
6248
|
}
|
|
6176
6249
|
|
|
6177
6250
|
function transformFragmentChildren(
|