babel-plugin-jsx-dom-expressions 0.40.7 → 0.40.11
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/README.md +22 -0
- package/index.js +47 -7
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -184,6 +184,28 @@ When `true` (the default), the template output drops the whitespace between a qu
|
|
|
184
184
|
|
|
185
185
|
Set this to `false` to emit a single space between attributes (`<svg width="12" height="13">`) so the generated HTML is strictly valid. This may also be desirable together with `omitQuotes: false` / `omitLastClosingTag: false` when targeting stricter parsers.
|
|
186
186
|
|
|
187
|
+
### omitServerOnlyTemplates
|
|
188
|
+
|
|
189
|
+
- Type: `boolean`
|
|
190
|
+
- Default: `true`
|
|
191
|
+
|
|
192
|
+
Only applies when `hydratable` is `true`. When `true` (the default), an element marked `$ServerOnly` has no template registered, so its markup is never shipped to the client and the generated code calls `getNextElement()` with no argument. That element can then only be obtained by hydrating against server-rendered DOM.
|
|
193
|
+
|
|
194
|
+
Because `getNextElement` falls back to `template()` whenever it is not hydrating, such an element throws `TypeError: template is not a function` if it is ever re-created on the client. Hot module replacement does exactly that: it disposes the old root and re-runs the component outside of hydration, so editing any file containing `$ServerOnly` breaks the page in development.
|
|
195
|
+
|
|
196
|
+
Set this to `false` to keep registering the template. The hydration path is unchanged, since `getNextElement` prefers the hydration registry and only calls `template()` when there is nothing to hydrate. The only cost is the template markup being present in the output, which makes this suitable for development builds:
|
|
197
|
+
|
|
198
|
+
```js
|
|
199
|
+
{
|
|
200
|
+
plugins: [
|
|
201
|
+
["jsx-dom-expressions", {
|
|
202
|
+
hydratable: true,
|
|
203
|
+
omitServerOnlyTemplates: process.env.NODE_ENV === "production"
|
|
204
|
+
}]
|
|
205
|
+
]
|
|
206
|
+
}
|
|
207
|
+
```
|
|
208
|
+
|
|
187
209
|
### requireImportSource
|
|
188
210
|
|
|
189
211
|
- Type: `string | false`
|
package/index.js
CHANGED
|
@@ -409,6 +409,25 @@ function isComponent(tagName) {
|
|
|
409
409
|
|
|
410
410
|
}
|
|
411
411
|
|
|
412
|
+
// <script> and <style> are only raw text in HTML. Inside SVG the parser decodes
|
|
413
|
+
// entities and treats `<` as markup, so their children still need escaping.
|
|
414
|
+
function isRawTextElement(path) {
|
|
415
|
+
const tagName = getTagName(path.node);
|
|
416
|
+
if (tagName !== "script" && tagName !== "style") return false;
|
|
417
|
+
let parent = path.parentPath,
|
|
418
|
+
rootTag;
|
|
419
|
+
while (t__namespace.isJSXElement(parent.node)) {
|
|
420
|
+
const parentTag = getTagName(parent.node);
|
|
421
|
+
if (isComponent(parentTag)) break;
|
|
422
|
+
if (parentTag === "foreignObject") return true;
|
|
423
|
+
if (parentTag === "svg") return false;
|
|
424
|
+
rootTag = parentTag;
|
|
425
|
+
parent = parent.parentPath;
|
|
426
|
+
}
|
|
427
|
+
// a top-level SVG element is parsed inside an <svg> wrapper
|
|
428
|
+
return !rootTag || !SVGElements.has(rootTag);
|
|
429
|
+
}
|
|
430
|
+
|
|
412
431
|
function hasStaticMarker(object, path) {
|
|
413
432
|
if (!object) return false;
|
|
414
433
|
if (
|
|
@@ -1856,7 +1875,7 @@ function transformAttributes$2(path, results) {
|
|
|
1856
1875
|
}
|
|
1857
1876
|
} else {
|
|
1858
1877
|
if (config.hydratable && key === "$ServerOnly") {
|
|
1859
|
-
results.skipTemplate = true;
|
|
1878
|
+
if (config.omitServerOnlyTemplates) results.skipTemplate = true;
|
|
1860
1879
|
return;
|
|
1861
1880
|
}
|
|
1862
1881
|
if (t__namespace.isJSXExpressionContainer(value)) value = value.expression;
|
|
@@ -1901,6 +1920,7 @@ function findLastElement(children, hydratable) {
|
|
|
1901
1920
|
function transformChildren$2(path, results, config) {
|
|
1902
1921
|
let tempPath = results.id && results.id.name,
|
|
1903
1922
|
tagName = getTagName(path.node),
|
|
1923
|
+
doNotEscape = isRawTextElement(path),
|
|
1904
1924
|
nextPlaceholder,
|
|
1905
1925
|
childPostExprs = [],
|
|
1906
1926
|
i = 0;
|
|
@@ -1913,6 +1933,7 @@ function transformChildren$2(path, results, config) {
|
|
|
1913
1933
|
|
|
1914
1934
|
}
|
|
1915
1935
|
const transformed = transformNode(child, {
|
|
1936
|
+
doNotEscape,
|
|
1916
1937
|
toBeClosed: results.toBeClosed,
|
|
1917
1938
|
lastElement: index === lastElement,
|
|
1918
1939
|
skipId: !results.id || !detectExpressions(filteredChildren, index, config)
|
|
@@ -2542,7 +2563,7 @@ function transformElement$2(path, info) {
|
|
|
2542
2563
|
|
|
2543
2564
|
const config = getConfig(path);
|
|
2544
2565
|
const tagName = getTagName(path.node);
|
|
2545
|
-
if (
|
|
2566
|
+
if (isRawTextElement(path)) path.doNotEscape = true;
|
|
2546
2567
|
|
|
2547
2568
|
// contains spread attributes
|
|
2548
2569
|
if (path.node.openingElement.attributes.some((a) => t__namespace.isJSXSpreadAttribute(a)))
|
|
@@ -2647,6 +2668,10 @@ function escapeExpression(path, expression, attr, escapeLiterals) {
|
|
|
2647
2668
|
} else expression.body = escapeExpression(path, expression.body, attr, escapeLiterals);
|
|
2648
2669
|
return expression;
|
|
2649
2670
|
} else if (t__namespace.isTemplateLiteral(expression)) {
|
|
2671
|
+
// Interpolations are escaped recursively. The static quasis are not —
|
|
2672
|
+
// `url("${x}")` would otherwise leave a raw `"` inside style="..." /
|
|
2673
|
+
// attr="...", which HTML parses as attribute injection.
|
|
2674
|
+
if (attr) escapeTemplateQuasis(expression, true);
|
|
2650
2675
|
expression.expressions = expression.expressions.map((e) =>
|
|
2651
2676
|
escapeExpression(path, e, attr, escapeLiterals));
|
|
2652
2677
|
|
|
@@ -2689,6 +2714,18 @@ function escapeExpression(path, expression, attr, escapeLiterals) {
|
|
|
2689
2714
|
|
|
2690
2715
|
}
|
|
2691
2716
|
|
|
2717
|
+
function escapeTemplateQuasis(expression, attr) {
|
|
2718
|
+
for (const quasi of expression.quasis) {
|
|
2719
|
+
const src = quasi.value.cooked != null ? quasi.value.cooked : quasi.value.raw;
|
|
2720
|
+
const escaped = escapeHTML(src, attr);
|
|
2721
|
+
if (typeof escaped !== "string" || escaped === src) continue;
|
|
2722
|
+
quasi.value.cooked = escaped;
|
|
2723
|
+
// Codegen prints `raw`. Re-escape template delimiters so `"` etc.
|
|
2724
|
+
// stay literal text.
|
|
2725
|
+
quasi.value.raw = escaped.replace(/\\/g, "\\\\").replace(/`/g, "\\`").replace(/\$\{/g, "\\${");
|
|
2726
|
+
}
|
|
2727
|
+
}
|
|
2728
|
+
|
|
2692
2729
|
function transformToObject(attrName, attributes, selectedAttributes) {
|
|
2693
2730
|
const properties = [];
|
|
2694
2731
|
const existingAttribute = attributes.find((a) => a.node.name.name === attrName);
|
|
@@ -2825,8 +2862,11 @@ function transformAttributes$1(path, results, info) {
|
|
|
2825
2862
|
let doEscape = true;
|
|
2826
2863
|
|
|
2827
2864
|
if (key.startsWith("attr:")) key = key.replace("attr:", "");
|
|
2828
|
-
|
|
2829
|
-
|
|
2865
|
+
// preservesPitch is a boolean property, but not an HTML boolean attribute.
|
|
2866
|
+
const isBooleanAttribute =
|
|
2867
|
+
key !== "preservesPitch" && BooleanAttributes.has(toAttribute(key, isSVG));
|
|
2868
|
+
if (isBooleanAttribute || key.startsWith("bool:")) {
|
|
2869
|
+
key = toAttribute(key.replace("bool:", ""), isSVG);
|
|
2830
2870
|
results.template.push("");
|
|
2831
2871
|
const fn = t__namespace.callExpression(registerImportMethod(attribute, "ssrAttribute"), [
|
|
2832
2872
|
t__namespace.stringLiteral(key),
|
|
@@ -3785,8 +3825,7 @@ function transformComponent(path) {
|
|
|
3785
3825
|
}))
|
|
3786
3826
|
{
|
|
3787
3827
|
if (
|
|
3788
|
-
config.wrapConditionals &&
|
|
3789
|
-
config.generate !== "ssr" && (
|
|
3828
|
+
config.wrapConditionals && (
|
|
3790
3829
|
t__namespace.isLogicalExpression(value.expression) ||
|
|
3791
3830
|
t__namespace.isConditionalExpression(value.expression)))
|
|
3792
3831
|
{
|
|
@@ -4053,7 +4092,7 @@ function transformNode(path, info = {}) {
|
|
|
4053
4092
|
info.doNotEscape ?
|
|
4054
4093
|
staticValue.toString() :
|
|
4055
4094
|
escapeHTML(staticValue.toString()) :
|
|
4056
|
-
trimWhitespace(node.extra.raw);
|
|
4095
|
+
trimWhitespace(info.doNotEscape ? node.value : node.extra.raw);
|
|
4057
4096
|
if (!text.length) return null;
|
|
4058
4097
|
const results = {
|
|
4059
4098
|
template: text,
|
|
@@ -4310,6 +4349,7 @@ var config = {
|
|
|
4310
4349
|
omitQuotes: true,
|
|
4311
4350
|
omitAttributeSpacing: true,
|
|
4312
4351
|
contextToCustomElements: false,
|
|
4352
|
+
omitServerOnlyTemplates: true,
|
|
4313
4353
|
staticMarker: "@once",
|
|
4314
4354
|
effectWrapper: "effect",
|
|
4315
4355
|
memoWrapper: "memo",
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "babel-plugin-jsx-dom-expressions",
|
|
3
3
|
"description": "A JSX to DOM plugin that wraps expressions for fine grained change detection",
|
|
4
|
-
"version": "0.40.
|
|
4
|
+
"version": "0.40.11",
|
|
5
5
|
"author": "Ryan Carniato",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"repository": {
|
|
@@ -33,5 +33,5 @@
|
|
|
33
33
|
"@rollup/plugin-babel": "6.0.3",
|
|
34
34
|
"@types/babel__core": "^7.20.5"
|
|
35
35
|
},
|
|
36
|
-
"gitHead": "
|
|
36
|
+
"gitHead": "3eba17b37754abaae2346c0237a4a65de802a316"
|
|
37
37
|
}
|