@lwc/template-compiler 8.5.0 → 8.7.0
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/dist/index.cjs.js +93 -24
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.js +93 -24
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/index.cjs.js
CHANGED
|
@@ -11390,6 +11390,8 @@ function isAttribute(element, attrName) {
|
|
|
11390
11390
|
}
|
|
11391
11391
|
// Handle input tag value="" and checked attributes that are only used for state initialization.
|
|
11392
11392
|
// Because .setAttribute() won't update the value, those attributes should be considered as props.
|
|
11393
|
+
// Note: this is tightly-coupled with static-element-serializer.ts which treats `<input checked="...">`
|
|
11394
|
+
// and `<input value="...">` as special because of the logic below.
|
|
11393
11395
|
if (element.name === 'input' && (attrName === 'value' || attrName === 'checked')) {
|
|
11394
11396
|
return false;
|
|
11395
11397
|
}
|
|
@@ -11563,6 +11565,7 @@ function parseElement(ctx, parse5Elm, parentNode, parse5ParentLocation) {
|
|
|
11563
11565
|
applyKey(ctx, parsedAttr, element);
|
|
11564
11566
|
applyLwcDirectives(ctx, parsedAttr, element);
|
|
11565
11567
|
applyAttributes(ctx, parsedAttr, element);
|
|
11568
|
+
validateSlotAttribute(ctx, parsedAttr, parentNode, element);
|
|
11566
11569
|
validateElement(ctx, element, parse5Elm);
|
|
11567
11570
|
validateAttributes(ctx, parsedAttr, element);
|
|
11568
11571
|
validateProperties(ctx, element);
|
|
@@ -12565,6 +12568,31 @@ function validateAttributes(ctx, parsedAttr, element) {
|
|
|
12565
12568
|
}
|
|
12566
12569
|
}
|
|
12567
12570
|
}
|
|
12571
|
+
function validateSlotAttribute(ctx, parsedAttr, parentNode, element) {
|
|
12572
|
+
const slotAttr = parsedAttr.get('slot');
|
|
12573
|
+
if (!slotAttr) {
|
|
12574
|
+
return;
|
|
12575
|
+
}
|
|
12576
|
+
function isElementOrSlot(node) {
|
|
12577
|
+
return isElement(node) || isSlot(node);
|
|
12578
|
+
}
|
|
12579
|
+
// Find the nearest ancestor that is an element or `<slot>`, and stop if we hit a component.
|
|
12580
|
+
// E.g. this should warn due to the `<div>`: `<x-foo><div><span slot=bar></span></div></x-foo>`
|
|
12581
|
+
// And this should _not_ warn: `<div><x-foo><span slot=bar></span></x-foo></div>`
|
|
12582
|
+
const elementOrSlotAncestor = ctx.findAncestor(isElementOrSlot, ({ current }) => current && !isComponent(current) && !isExternalComponent(current), parentNode);
|
|
12583
|
+
// Warn if a `slot` attribute is on an element that isn't an immediate child of a containing LWC component or
|
|
12584
|
+
// `lwc:external` component. This is a case that all three of native-shadow/synthetic-shadow/light DOM will
|
|
12585
|
+
// simply ignore, but it's good to warn, so that developers realize that they may be making a mistake.
|
|
12586
|
+
// Note that, for the purposes of being considered an "immediate child," virtual elements like `for:each` and
|
|
12587
|
+
// `lwc:if` don't count - only rendered elements (including `<slot>`s) count.
|
|
12588
|
+
// Example of invalid usage: `<x-foo><div><span slot=bar></span></div></x-foo>`
|
|
12589
|
+
if (elementOrSlotAncestor) {
|
|
12590
|
+
ctx.warnOnNode(errors.ParserDiagnostics.IGNORED_SLOT_ATTRIBUTE_IN_CHILD, slotAttr, [
|
|
12591
|
+
`<${element.name}>`,
|
|
12592
|
+
`<${elementOrSlotAncestor.name}>`,
|
|
12593
|
+
]);
|
|
12594
|
+
}
|
|
12595
|
+
}
|
|
12568
12596
|
function validateProperties(ctx, element) {
|
|
12569
12597
|
for (const prop of element.properties) {
|
|
12570
12598
|
const { attributeName: attrName, value } = prop;
|
|
@@ -12874,8 +12902,30 @@ function isStaticNode(node, apiVersion) {
|
|
|
12874
12902
|
});
|
|
12875
12903
|
// all directives are static-safe
|
|
12876
12904
|
result &&= !directives.some((directive) => !STATIC_SAFE_DIRECTIVES.has(directive.name));
|
|
12877
|
-
//
|
|
12878
|
-
|
|
12905
|
+
// Sanity check to ensure that only `<input value>`/`<input checked>` are treated as props for elements
|
|
12906
|
+
/* v8 ignore start */
|
|
12907
|
+
if (process.env.NODE_ENV === 'test' && isElement(node)) {
|
|
12908
|
+
for (const { attributeName } of properties) {
|
|
12909
|
+
if (node.name !== 'input' &&
|
|
12910
|
+
!(attributeName === 'checked' || attributeName === 'value')) {
|
|
12911
|
+
throw new Error(`Expected to only see <input value>/<input checked> treated as an element prop. ` +
|
|
12912
|
+
`Instead found <${node.name} ${attributeName}>`);
|
|
12913
|
+
}
|
|
12914
|
+
}
|
|
12915
|
+
}
|
|
12916
|
+
/* v8 ignore stop */
|
|
12917
|
+
// `<input checked="...">` and `<input value="...">` have a peculiar attr/prop relationship, so the engine
|
|
12918
|
+
// has historically treated them as props rather than attributes:
|
|
12919
|
+
// https://github.com/salesforce/lwc/blob/b584d39/packages/%40lwc/template-compiler/src/parser/attribute.ts#L217-L221
|
|
12920
|
+
// For example, an element might be rendered as `<input type=checkbox>` but `input.checked` could
|
|
12921
|
+
// still return true. `value` behaves similarly. `value` and `checked` behave surprisingly
|
|
12922
|
+
// because the attributes actually represent the "default" value rather than the current one:
|
|
12923
|
+
// - https://jakearchibald.com/2024/attributes-vs-properties/#value-on-input-fields
|
|
12924
|
+
// - https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/checkbox#checked
|
|
12925
|
+
// For this reason, we currently avoid the static content optimization, and treat `value`/`checked` only as
|
|
12926
|
+
// runtime props.
|
|
12927
|
+
// TODO [#4775]: allow static optimization for `<input value>`/`<input checked>`
|
|
12928
|
+
result &&= properties.length === 0;
|
|
12879
12929
|
return result;
|
|
12880
12930
|
}
|
|
12881
12931
|
function collectStaticNodes(node, staticNodes, state) {
|
|
@@ -13055,11 +13105,23 @@ function serializeAttrs(element, codeGen) {
|
|
|
13055
13105
|
// Note that, to maintain backwards compatibility with the non-static output, we treat the valueless
|
|
13056
13106
|
// "boolean" format (e.g. `<div id>`) as the empty string, which is semantically equivalent.
|
|
13057
13107
|
const needsPlaceholder = hasExpression || hasSvgUseHref || needsScoping;
|
|
13058
|
-
|
|
13059
|
-
|
|
13060
|
-
|
|
13061
|
-
|
|
13062
|
-
|
|
13108
|
+
let nameAndValue;
|
|
13109
|
+
if (needsPlaceholder) {
|
|
13110
|
+
// Inject a placeholder where the staticPartId will go when an expression occurs.
|
|
13111
|
+
// This is only needed for SSR to inject the expression value during serialization.
|
|
13112
|
+
nameAndValue = `\${"${v}"}`;
|
|
13113
|
+
}
|
|
13114
|
+
else if (v === '') {
|
|
13115
|
+
// In HTML, there is no difference between the empty string value (`<div foo="">`) and "boolean true"
|
|
13116
|
+
// (`<div foo>`). They are both parsed identically, and the DOM treats them the same (`getAttribute`
|
|
13117
|
+
// returns the empty string). Here we prefer the shorter format.
|
|
13118
|
+
// https://html.spec.whatwg.org/multipage/introduction.html#a-quick-introduction-to-html:syntax-attributes
|
|
13119
|
+
nameAndValue = ` ${escapedAttributeName}`;
|
|
13120
|
+
}
|
|
13121
|
+
else {
|
|
13122
|
+
nameAndValue = ` ${escapedAttributeName}="${shared.htmlEscape(v, true)}"`;
|
|
13123
|
+
}
|
|
13124
|
+
attrs.push(nameAndValue);
|
|
13063
13125
|
}
|
|
13064
13126
|
else {
|
|
13065
13127
|
attrs.push(` ${escapedAttributeName}`);
|
|
@@ -13091,21 +13153,16 @@ function serializeAttrs(element, codeGen) {
|
|
|
13091
13153
|
value: hasExpression || hasSvgUseHref || needsScoping
|
|
13092
13154
|
? codeGen.getStaticExpressionToken(attr)
|
|
13093
13155
|
: value.value,
|
|
13156
|
+
elementName: element.name,
|
|
13094
13157
|
};
|
|
13095
13158
|
})
|
|
13096
13159
|
.forEach(collector);
|
|
13097
|
-
|
|
13098
|
-
//
|
|
13099
|
-
|
|
13100
|
-
|
|
13101
|
-
|
|
13102
|
-
|
|
13103
|
-
return {
|
|
13104
|
-
name: prop.attributeName,
|
|
13105
|
-
value: prop.value.value,
|
|
13106
|
-
};
|
|
13107
|
-
})
|
|
13108
|
-
.forEach(collector);
|
|
13160
|
+
/* v8 ignore start */
|
|
13161
|
+
// TODO [#4775]: allow static optimization for `<input value>`/`<input checked>`
|
|
13162
|
+
if (process.env.NODE_ENV === 'test' && element.properties.length > 0) {
|
|
13163
|
+
throw new Error('Expected zero properties at this point, found ' + element.properties.length);
|
|
13164
|
+
}
|
|
13165
|
+
/* v8 ignore stop */
|
|
13109
13166
|
// ${2} maps to style token attribute
|
|
13110
13167
|
// ${3} maps to class attribute token + style token attribute
|
|
13111
13168
|
// See buildParseFragmentFn for details.
|
|
@@ -14155,17 +14212,29 @@ function transform(codeGen) {
|
|
|
14155
14212
|
const children = parent.children;
|
|
14156
14213
|
const childrenIterator = children[Symbol.iterator]();
|
|
14157
14214
|
let current;
|
|
14215
|
+
function isTextOrIgnoredComment(node) {
|
|
14216
|
+
return isText(node) || (isComment(node) && !codeGen.preserveComments);
|
|
14217
|
+
}
|
|
14158
14218
|
while ((current = childrenIterator.next()) && !current.done) {
|
|
14159
14219
|
let child = current.value;
|
|
14160
|
-
|
|
14220
|
+
// Concatenate contiguous text nodes together (while skipping ignored comments)
|
|
14221
|
+
// E.g. `<div>{foo}{bar}</div>` can be concatenated into a single text node expression,
|
|
14222
|
+
// and so can `<div>{foo}<!-- baz -->{bar}</div>` if comments are ignored.
|
|
14223
|
+
if (isTextOrIgnoredComment(child)) {
|
|
14161
14224
|
const continuousText = [];
|
|
14162
14225
|
// Consume all the contiguous text nodes.
|
|
14163
14226
|
do {
|
|
14164
|
-
|
|
14227
|
+
if (isText(child)) {
|
|
14228
|
+
continuousText.push(child);
|
|
14229
|
+
}
|
|
14165
14230
|
current = childrenIterator.next();
|
|
14166
14231
|
child = current.value;
|
|
14167
|
-
} while (!current.done &&
|
|
14168
|
-
|
|
14232
|
+
} while (!current.done && isTextOrIgnoredComment(child));
|
|
14233
|
+
// Only push an api_text call if we actually have text to render.
|
|
14234
|
+
// (We might just have iterated through a sequence of ignored comments.)
|
|
14235
|
+
if (continuousText.length) {
|
|
14236
|
+
res.push(transformText(continuousText));
|
|
14237
|
+
}
|
|
14169
14238
|
// Early exit if a text node is the last child node.
|
|
14170
14239
|
if (current.done) {
|
|
14171
14240
|
break;
|
|
@@ -14636,5 +14705,5 @@ exports.generateScopeTokens = generateScopeTokens;
|
|
|
14636
14705
|
exports.kebabcaseToCamelcase = kebabcaseToCamelcase;
|
|
14637
14706
|
exports.parse = parse;
|
|
14638
14707
|
exports.toPropertyName = toPropertyName;
|
|
14639
|
-
/** version: 8.
|
|
14708
|
+
/** version: 8.7.0 */
|
|
14640
14709
|
//# sourceMappingURL=index.cjs.js.map
|