@lwc/template-compiler 8.5.0 → 8.6.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 +100 -18
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.js +100 -18
- 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;
|
|
@@ -13011,14 +13039,33 @@ function serializeAttrs(element, codeGen) {
|
|
|
13011
13039
|
*/
|
|
13012
13040
|
const attrs = [];
|
|
13013
13041
|
let hasClassAttr = false;
|
|
13014
|
-
const collector = ({ name, value, hasExpression, hasSvgUseHref, needsScoping, }) => {
|
|
13042
|
+
const collector = ({ name, value, isProp, hasExpression, hasSvgUseHref, needsScoping, }) => {
|
|
13015
13043
|
// Do not serialize boolean class/style attribute (consistent with non-static optimized)
|
|
13016
13044
|
if (typeof value === 'boolean' && (name === 'class' || name === 'style')) {
|
|
13017
13045
|
return;
|
|
13018
13046
|
}
|
|
13019
13047
|
// See W-16614169
|
|
13020
13048
|
const escapedAttributeName = templateStringEscape(name);
|
|
13021
|
-
|
|
13049
|
+
// `<input checked="...">` and `<input value="...">` have a peculiar attr/prop relationship, so the engine
|
|
13050
|
+
// has historically treated them as props rather than attributes:
|
|
13051
|
+
// https://github.com/salesforce/lwc/blob/b584d39/packages/%40lwc/template-compiler/src/parser/attribute.ts#L217-L221
|
|
13052
|
+
// For example, an element might be rendered as `<input type=checkbox>` but `input.checked` could
|
|
13053
|
+
// still return true. `value` behaves similarly. `value` and `checked` behave surprisingly
|
|
13054
|
+
// because the attributes actually represent the "default" value rather than the current one:
|
|
13055
|
+
// - https://jakearchibald.com/2024/attributes-vs-properties/#value-on-input-fields
|
|
13056
|
+
// - https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/checkbox#checked
|
|
13057
|
+
if (isProp) {
|
|
13058
|
+
// The below logic matches the behavior of non-static-optimized DOM nodes.
|
|
13059
|
+
// There is no functional difference between e.g. `checked="checked"` and `checked` but we
|
|
13060
|
+
// match byte-for-byte the non-static-optimized HTML that would be rendered.
|
|
13061
|
+
if (name === 'checked' || (name === 'value' && value === '')) {
|
|
13062
|
+
attrs.push(` ${escapedAttributeName}`);
|
|
13063
|
+
}
|
|
13064
|
+
else {
|
|
13065
|
+
attrs.push(` ${escapedAttributeName}="${shared.htmlEscape(String(value), true)}"`);
|
|
13066
|
+
}
|
|
13067
|
+
}
|
|
13068
|
+
else if (typeof value === 'string') {
|
|
13022
13069
|
let v = templateStringEscape(value);
|
|
13023
13070
|
if (name === 'class') {
|
|
13024
13071
|
// ${0} maps to class token that will be appended to the string.
|
|
@@ -13054,12 +13101,25 @@ function serializeAttrs(element, codeGen) {
|
|
|
13054
13101
|
// Skip serializing here and handle it as if it were a dynamic attribute instead.
|
|
13055
13102
|
// Note that, to maintain backwards compatibility with the non-static output, we treat the valueless
|
|
13056
13103
|
// "boolean" format (e.g. `<div id>`) as the empty string, which is semantically equivalent.
|
|
13104
|
+
// `isProp` corresponds to `value` or `checked` on an `<input>` which is treated as a prop at runtime.
|
|
13057
13105
|
const needsPlaceholder = hasExpression || hasSvgUseHref || needsScoping;
|
|
13058
|
-
|
|
13059
|
-
|
|
13060
|
-
|
|
13061
|
-
|
|
13062
|
-
|
|
13106
|
+
let nameAndValue;
|
|
13107
|
+
if (needsPlaceholder) {
|
|
13108
|
+
// Inject a placeholder where the staticPartId will go when an expression occurs.
|
|
13109
|
+
// This is only needed for SSR to inject the expression value during serialization.
|
|
13110
|
+
nameAndValue = `\${"${v}"}`;
|
|
13111
|
+
}
|
|
13112
|
+
else if (v === '') {
|
|
13113
|
+
// In HTML, there is no difference between the empty string value (`<div foo="">`) and "boolean true"
|
|
13114
|
+
// (`<div foo>`). They are both parsed identically, and the DOM treats them the same (`getAttribute`
|
|
13115
|
+
// returns the empty string). Here we prefer the shorter format.
|
|
13116
|
+
// https://html.spec.whatwg.org/multipage/introduction.html#a-quick-introduction-to-html:syntax-attributes
|
|
13117
|
+
nameAndValue = ` ${escapedAttributeName}`;
|
|
13118
|
+
}
|
|
13119
|
+
else {
|
|
13120
|
+
nameAndValue = ` ${escapedAttributeName}="${shared.htmlEscape(v, true)}"`;
|
|
13121
|
+
}
|
|
13122
|
+
attrs.push(nameAndValue);
|
|
13063
13123
|
}
|
|
13064
13124
|
else {
|
|
13065
13125
|
attrs.push(` ${escapedAttributeName}`);
|
|
@@ -13086,23 +13146,33 @@ function serializeAttrs(element, codeGen) {
|
|
|
13086
13146
|
return {
|
|
13087
13147
|
hasExpression,
|
|
13088
13148
|
hasSvgUseHref,
|
|
13149
|
+
isProp: false,
|
|
13089
13150
|
needsScoping,
|
|
13090
13151
|
name,
|
|
13091
13152
|
value: hasExpression || hasSvgUseHref || needsScoping
|
|
13092
13153
|
? codeGen.getStaticExpressionToken(attr)
|
|
13093
13154
|
: value.value,
|
|
13155
|
+
elementName: element.name,
|
|
13094
13156
|
};
|
|
13095
13157
|
})
|
|
13096
13158
|
.forEach(collector);
|
|
13097
|
-
//
|
|
13098
|
-
// a property: https://github.com/salesforce/lwc/blob/master/packages/%40lwc/template-compiler/src/parser/attribute.ts#L198-L218
|
|
13099
|
-
// Because a component can't be a static element, we only look in the property bag on value and checked attribute
|
|
13100
|
-
// from the input.
|
|
13159
|
+
// See note above about `<input value>`/`<input checked>`
|
|
13101
13160
|
element.properties
|
|
13102
13161
|
.map((prop) => {
|
|
13162
|
+
const { attributeName, value } = prop;
|
|
13163
|
+
// Sanity check to ensure that only `<input value>`/`<input checked>` are treated as props
|
|
13164
|
+
/* v8 ignore start */
|
|
13165
|
+
if (process.env.NODE_ENV === 'test') {
|
|
13166
|
+
if (element.name !== 'input' &&
|
|
13167
|
+
!(attributeName === 'checked' || attributeName === 'value')) {
|
|
13168
|
+
throw new Error('Expected to only see `<input value>`/`<input checked>` here; instead found `<${element.name} ${attributeName}>');
|
|
13169
|
+
}
|
|
13170
|
+
}
|
|
13171
|
+
/* v8 ignore stop */
|
|
13103
13172
|
return {
|
|
13104
|
-
name:
|
|
13105
|
-
value:
|
|
13173
|
+
name: attributeName,
|
|
13174
|
+
value: value.value,
|
|
13175
|
+
isProp: true,
|
|
13106
13176
|
};
|
|
13107
13177
|
})
|
|
13108
13178
|
.forEach(collector);
|
|
@@ -14155,17 +14225,29 @@ function transform(codeGen) {
|
|
|
14155
14225
|
const children = parent.children;
|
|
14156
14226
|
const childrenIterator = children[Symbol.iterator]();
|
|
14157
14227
|
let current;
|
|
14228
|
+
function isTextOrIgnoredComment(node) {
|
|
14229
|
+
return isText(node) || (isComment(node) && !codeGen.preserveComments);
|
|
14230
|
+
}
|
|
14158
14231
|
while ((current = childrenIterator.next()) && !current.done) {
|
|
14159
14232
|
let child = current.value;
|
|
14160
|
-
|
|
14233
|
+
// Concatenate contiguous text nodes together (while skipping ignored comments)
|
|
14234
|
+
// E.g. `<div>{foo}{bar}</div>` can be concatenated into a single text node expression,
|
|
14235
|
+
// and so can `<div>{foo}<!-- baz -->{bar}</div>` if comments are ignored.
|
|
14236
|
+
if (isTextOrIgnoredComment(child)) {
|
|
14161
14237
|
const continuousText = [];
|
|
14162
14238
|
// Consume all the contiguous text nodes.
|
|
14163
14239
|
do {
|
|
14164
|
-
|
|
14240
|
+
if (isText(child)) {
|
|
14241
|
+
continuousText.push(child);
|
|
14242
|
+
}
|
|
14165
14243
|
current = childrenIterator.next();
|
|
14166
14244
|
child = current.value;
|
|
14167
|
-
} while (!current.done &&
|
|
14168
|
-
|
|
14245
|
+
} while (!current.done && isTextOrIgnoredComment(child));
|
|
14246
|
+
// Only push an api_text call if we actually have text to render.
|
|
14247
|
+
// (We might just have iterated through a sequence of ignored comments.)
|
|
14248
|
+
if (continuousText.length) {
|
|
14249
|
+
res.push(transformText(continuousText));
|
|
14250
|
+
}
|
|
14169
14251
|
// Early exit if a text node is the last child node.
|
|
14170
14252
|
if (current.done) {
|
|
14171
14253
|
break;
|
|
@@ -14636,5 +14718,5 @@ exports.generateScopeTokens = generateScopeTokens;
|
|
|
14636
14718
|
exports.kebabcaseToCamelcase = kebabcaseToCamelcase;
|
|
14637
14719
|
exports.parse = parse;
|
|
14638
14720
|
exports.toPropertyName = toPropertyName;
|
|
14639
|
-
/** version: 8.
|
|
14721
|
+
/** version: 8.6.0 */
|
|
14640
14722
|
//# sourceMappingURL=index.cjs.js.map
|