@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.js
CHANGED
|
@@ -11366,6 +11366,8 @@ function isAttribute(element, attrName) {
|
|
|
11366
11366
|
}
|
|
11367
11367
|
// Handle input tag value="" and checked attributes that are only used for state initialization.
|
|
11368
11368
|
// Because .setAttribute() won't update the value, those attributes should be considered as props.
|
|
11369
|
+
// Note: this is tightly-coupled with static-element-serializer.ts which treats `<input checked="...">`
|
|
11370
|
+
// and `<input value="...">` as special because of the logic below.
|
|
11369
11371
|
if (element.name === 'input' && (attrName === 'value' || attrName === 'checked')) {
|
|
11370
11372
|
return false;
|
|
11371
11373
|
}
|
|
@@ -11539,6 +11541,7 @@ function parseElement(ctx, parse5Elm, parentNode, parse5ParentLocation) {
|
|
|
11539
11541
|
applyKey(ctx, parsedAttr, element);
|
|
11540
11542
|
applyLwcDirectives(ctx, parsedAttr, element);
|
|
11541
11543
|
applyAttributes(ctx, parsedAttr, element);
|
|
11544
|
+
validateSlotAttribute(ctx, parsedAttr, parentNode, element);
|
|
11542
11545
|
validateElement(ctx, element, parse5Elm);
|
|
11543
11546
|
validateAttributes(ctx, parsedAttr, element);
|
|
11544
11547
|
validateProperties(ctx, element);
|
|
@@ -12541,6 +12544,31 @@ function validateAttributes(ctx, parsedAttr, element) {
|
|
|
12541
12544
|
}
|
|
12542
12545
|
}
|
|
12543
12546
|
}
|
|
12547
|
+
function validateSlotAttribute(ctx, parsedAttr, parentNode, element) {
|
|
12548
|
+
const slotAttr = parsedAttr.get('slot');
|
|
12549
|
+
if (!slotAttr) {
|
|
12550
|
+
return;
|
|
12551
|
+
}
|
|
12552
|
+
function isElementOrSlot(node) {
|
|
12553
|
+
return isElement(node) || isSlot(node);
|
|
12554
|
+
}
|
|
12555
|
+
// Find the nearest ancestor that is an element or `<slot>`, and stop if we hit a component.
|
|
12556
|
+
// E.g. this should warn due to the `<div>`: `<x-foo><div><span slot=bar></span></div></x-foo>`
|
|
12557
|
+
// And this should _not_ warn: `<div><x-foo><span slot=bar></span></x-foo></div>`
|
|
12558
|
+
const elementOrSlotAncestor = ctx.findAncestor(isElementOrSlot, ({ current }) => current && !isComponent(current) && !isExternalComponent(current), parentNode);
|
|
12559
|
+
// Warn if a `slot` attribute is on an element that isn't an immediate child of a containing LWC component or
|
|
12560
|
+
// `lwc:external` component. This is a case that all three of native-shadow/synthetic-shadow/light DOM will
|
|
12561
|
+
// simply ignore, but it's good to warn, so that developers realize that they may be making a mistake.
|
|
12562
|
+
// Note that, for the purposes of being considered an "immediate child," virtual elements like `for:each` and
|
|
12563
|
+
// `lwc:if` don't count - only rendered elements (including `<slot>`s) count.
|
|
12564
|
+
// Example of invalid usage: `<x-foo><div><span slot=bar></span></div></x-foo>`
|
|
12565
|
+
if (elementOrSlotAncestor) {
|
|
12566
|
+
ctx.warnOnNode(ParserDiagnostics.IGNORED_SLOT_ATTRIBUTE_IN_CHILD, slotAttr, [
|
|
12567
|
+
`<${element.name}>`,
|
|
12568
|
+
`<${elementOrSlotAncestor.name}>`,
|
|
12569
|
+
]);
|
|
12570
|
+
}
|
|
12571
|
+
}
|
|
12544
12572
|
function validateProperties(ctx, element) {
|
|
12545
12573
|
for (const prop of element.properties) {
|
|
12546
12574
|
const { attributeName: attrName, value } = prop;
|
|
@@ -12987,14 +13015,33 @@ function serializeAttrs(element, codeGen) {
|
|
|
12987
13015
|
*/
|
|
12988
13016
|
const attrs = [];
|
|
12989
13017
|
let hasClassAttr = false;
|
|
12990
|
-
const collector = ({ name, value, hasExpression, hasSvgUseHref, needsScoping, }) => {
|
|
13018
|
+
const collector = ({ name, value, isProp, hasExpression, hasSvgUseHref, needsScoping, }) => {
|
|
12991
13019
|
// Do not serialize boolean class/style attribute (consistent with non-static optimized)
|
|
12992
13020
|
if (typeof value === 'boolean' && (name === 'class' || name === 'style')) {
|
|
12993
13021
|
return;
|
|
12994
13022
|
}
|
|
12995
13023
|
// See W-16614169
|
|
12996
13024
|
const escapedAttributeName = templateStringEscape(name);
|
|
12997
|
-
|
|
13025
|
+
// `<input checked="...">` and `<input value="...">` have a peculiar attr/prop relationship, so the engine
|
|
13026
|
+
// has historically treated them as props rather than attributes:
|
|
13027
|
+
// https://github.com/salesforce/lwc/blob/b584d39/packages/%40lwc/template-compiler/src/parser/attribute.ts#L217-L221
|
|
13028
|
+
// For example, an element might be rendered as `<input type=checkbox>` but `input.checked` could
|
|
13029
|
+
// still return true. `value` behaves similarly. `value` and `checked` behave surprisingly
|
|
13030
|
+
// because the attributes actually represent the "default" value rather than the current one:
|
|
13031
|
+
// - https://jakearchibald.com/2024/attributes-vs-properties/#value-on-input-fields
|
|
13032
|
+
// - https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/checkbox#checked
|
|
13033
|
+
if (isProp) {
|
|
13034
|
+
// The below logic matches the behavior of non-static-optimized DOM nodes.
|
|
13035
|
+
// There is no functional difference between e.g. `checked="checked"` and `checked` but we
|
|
13036
|
+
// match byte-for-byte the non-static-optimized HTML that would be rendered.
|
|
13037
|
+
if (name === 'checked' || (name === 'value' && value === '')) {
|
|
13038
|
+
attrs.push(` ${escapedAttributeName}`);
|
|
13039
|
+
}
|
|
13040
|
+
else {
|
|
13041
|
+
attrs.push(` ${escapedAttributeName}="${htmlEscape(String(value), true)}"`);
|
|
13042
|
+
}
|
|
13043
|
+
}
|
|
13044
|
+
else if (typeof value === 'string') {
|
|
12998
13045
|
let v = templateStringEscape(value);
|
|
12999
13046
|
if (name === 'class') {
|
|
13000
13047
|
// ${0} maps to class token that will be appended to the string.
|
|
@@ -13030,12 +13077,25 @@ function serializeAttrs(element, codeGen) {
|
|
|
13030
13077
|
// Skip serializing here and handle it as if it were a dynamic attribute instead.
|
|
13031
13078
|
// Note that, to maintain backwards compatibility with the non-static output, we treat the valueless
|
|
13032
13079
|
// "boolean" format (e.g. `<div id>`) as the empty string, which is semantically equivalent.
|
|
13080
|
+
// `isProp` corresponds to `value` or `checked` on an `<input>` which is treated as a prop at runtime.
|
|
13033
13081
|
const needsPlaceholder = hasExpression || hasSvgUseHref || needsScoping;
|
|
13034
|
-
|
|
13035
|
-
|
|
13036
|
-
|
|
13037
|
-
|
|
13038
|
-
|
|
13082
|
+
let nameAndValue;
|
|
13083
|
+
if (needsPlaceholder) {
|
|
13084
|
+
// Inject a placeholder where the staticPartId will go when an expression occurs.
|
|
13085
|
+
// This is only needed for SSR to inject the expression value during serialization.
|
|
13086
|
+
nameAndValue = `\${"${v}"}`;
|
|
13087
|
+
}
|
|
13088
|
+
else if (v === '') {
|
|
13089
|
+
// In HTML, there is no difference between the empty string value (`<div foo="">`) and "boolean true"
|
|
13090
|
+
// (`<div foo>`). They are both parsed identically, and the DOM treats them the same (`getAttribute`
|
|
13091
|
+
// returns the empty string). Here we prefer the shorter format.
|
|
13092
|
+
// https://html.spec.whatwg.org/multipage/introduction.html#a-quick-introduction-to-html:syntax-attributes
|
|
13093
|
+
nameAndValue = ` ${escapedAttributeName}`;
|
|
13094
|
+
}
|
|
13095
|
+
else {
|
|
13096
|
+
nameAndValue = ` ${escapedAttributeName}="${htmlEscape(v, true)}"`;
|
|
13097
|
+
}
|
|
13098
|
+
attrs.push(nameAndValue);
|
|
13039
13099
|
}
|
|
13040
13100
|
else {
|
|
13041
13101
|
attrs.push(` ${escapedAttributeName}`);
|
|
@@ -13062,23 +13122,33 @@ function serializeAttrs(element, codeGen) {
|
|
|
13062
13122
|
return {
|
|
13063
13123
|
hasExpression,
|
|
13064
13124
|
hasSvgUseHref,
|
|
13125
|
+
isProp: false,
|
|
13065
13126
|
needsScoping,
|
|
13066
13127
|
name,
|
|
13067
13128
|
value: hasExpression || hasSvgUseHref || needsScoping
|
|
13068
13129
|
? codeGen.getStaticExpressionToken(attr)
|
|
13069
13130
|
: value.value,
|
|
13131
|
+
elementName: element.name,
|
|
13070
13132
|
};
|
|
13071
13133
|
})
|
|
13072
13134
|
.forEach(collector);
|
|
13073
|
-
//
|
|
13074
|
-
// a property: https://github.com/salesforce/lwc/blob/master/packages/%40lwc/template-compiler/src/parser/attribute.ts#L198-L218
|
|
13075
|
-
// Because a component can't be a static element, we only look in the property bag on value and checked attribute
|
|
13076
|
-
// from the input.
|
|
13135
|
+
// See note above about `<input value>`/`<input checked>`
|
|
13077
13136
|
element.properties
|
|
13078
13137
|
.map((prop) => {
|
|
13138
|
+
const { attributeName, value } = prop;
|
|
13139
|
+
// Sanity check to ensure that only `<input value>`/`<input checked>` are treated as props
|
|
13140
|
+
/* v8 ignore start */
|
|
13141
|
+
if (process.env.NODE_ENV === 'test') {
|
|
13142
|
+
if (element.name !== 'input' &&
|
|
13143
|
+
!(attributeName === 'checked' || attributeName === 'value')) {
|
|
13144
|
+
throw new Error('Expected to only see `<input value>`/`<input checked>` here; instead found `<${element.name} ${attributeName}>');
|
|
13145
|
+
}
|
|
13146
|
+
}
|
|
13147
|
+
/* v8 ignore stop */
|
|
13079
13148
|
return {
|
|
13080
|
-
name:
|
|
13081
|
-
value:
|
|
13149
|
+
name: attributeName,
|
|
13150
|
+
value: value.value,
|
|
13151
|
+
isProp: true,
|
|
13082
13152
|
};
|
|
13083
13153
|
})
|
|
13084
13154
|
.forEach(collector);
|
|
@@ -14131,17 +14201,29 @@ function transform(codeGen) {
|
|
|
14131
14201
|
const children = parent.children;
|
|
14132
14202
|
const childrenIterator = children[Symbol.iterator]();
|
|
14133
14203
|
let current;
|
|
14204
|
+
function isTextOrIgnoredComment(node) {
|
|
14205
|
+
return isText(node) || (isComment(node) && !codeGen.preserveComments);
|
|
14206
|
+
}
|
|
14134
14207
|
while ((current = childrenIterator.next()) && !current.done) {
|
|
14135
14208
|
let child = current.value;
|
|
14136
|
-
|
|
14209
|
+
// Concatenate contiguous text nodes together (while skipping ignored comments)
|
|
14210
|
+
// E.g. `<div>{foo}{bar}</div>` can be concatenated into a single text node expression,
|
|
14211
|
+
// and so can `<div>{foo}<!-- baz -->{bar}</div>` if comments are ignored.
|
|
14212
|
+
if (isTextOrIgnoredComment(child)) {
|
|
14137
14213
|
const continuousText = [];
|
|
14138
14214
|
// Consume all the contiguous text nodes.
|
|
14139
14215
|
do {
|
|
14140
|
-
|
|
14216
|
+
if (isText(child)) {
|
|
14217
|
+
continuousText.push(child);
|
|
14218
|
+
}
|
|
14141
14219
|
current = childrenIterator.next();
|
|
14142
14220
|
child = current.value;
|
|
14143
|
-
} while (!current.done &&
|
|
14144
|
-
|
|
14221
|
+
} while (!current.done && isTextOrIgnoredComment(child));
|
|
14222
|
+
// Only push an api_text call if we actually have text to render.
|
|
14223
|
+
// (We might just have iterated through a sequence of ignored comments.)
|
|
14224
|
+
if (continuousText.length) {
|
|
14225
|
+
res.push(transformText(continuousText));
|
|
14226
|
+
}
|
|
14145
14227
|
// Early exit if a text node is the last child node.
|
|
14146
14228
|
if (current.done) {
|
|
14147
14229
|
break;
|
|
@@ -14607,5 +14689,5 @@ function compile(source, filename, config) {
|
|
|
14607
14689
|
}
|
|
14608
14690
|
|
|
14609
14691
|
export { ElementDirectiveName, LWCDirectiveDomMode, LWCDirectiveRenderMode, LwcTagName, RootDirectiveName, TemplateDirectiveName, compile, compile as default, generateScopeTokens, kebabcaseToCamelcase, parse, toPropertyName };
|
|
14610
|
-
/** version: 8.
|
|
14692
|
+
/** version: 8.6.0 */
|
|
14611
14693
|
//# sourceMappingURL=index.js.map
|