@lwc/template-compiler 6.3.3 → 6.4.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/codegen/codegen.d.ts +6 -3
- package/dist/codegen/expression.d.ts +2 -1
- package/dist/codegen/helpers.d.ts +1 -3
- package/dist/codegen/static-element-serializer.d.ts +3 -2
- package/dist/codegen/static-element.d.ts +5 -0
- package/dist/index.cjs.js +317 -131
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.js +318 -132
- package/dist/index.js.map +1 -1
- package/dist/shared/ast.d.ts +1 -0
- package/dist/shared/types.d.ts +2 -1
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Copyright (c) 2024 Salesforce, Inc.
|
|
3
3
|
*/
|
|
4
4
|
import { invariant, TemplateErrors, generateCompilerError, CompilerError, normalizeToDiagnostic, generateCompilerDiagnostic, ParserDiagnostics, DiagnosticLevel, CompilerMetrics } from '@lwc/errors';
|
|
5
|
-
import { hasOwnProperty, getAPIVersionFromNumber, HTML_NAMESPACE, AriaAttrNameToPropNameMap, isAPIFeatureEnabled,
|
|
5
|
+
import { StringCharAt, hasOwnProperty, getAPIVersionFromNumber, HTML_NAMESPACE, AriaAttrNameToPropNameMap, isAPIFeatureEnabled, ID_REFERENCING_ATTRIBUTES_SET, SVG_NAMESPACE, isBooleanAttribute, isGlobalHtmlAttribute, isAriaAttribute, isUndefined, isVoidElement, MATHML_NAMESPACE, isNull, isArray, ArrayEvery, htmlEscape, LWC_VERSION_COMMENT } from '@lwc/shared';
|
|
6
6
|
import * as he from 'he';
|
|
7
7
|
import { walk } from 'estree-walker';
|
|
8
8
|
import { parseExpressionAt, isIdentifierStart, isIdentifierChar } from 'acorn';
|
|
@@ -51,7 +51,10 @@ const DASHED_TAGNAME_ELEMENT_SET = new Set([
|
|
|
51
51
|
'missing-glyph',
|
|
52
52
|
]);
|
|
53
53
|
// Subset of LWC template directives that can safely be statically optimized
|
|
54
|
-
const STATIC_SAFE_DIRECTIVES = new Set([
|
|
54
|
+
const STATIC_SAFE_DIRECTIVES = new Set([
|
|
55
|
+
'Ref',
|
|
56
|
+
'Key',
|
|
57
|
+
]);
|
|
55
58
|
|
|
56
59
|
/*
|
|
57
60
|
* Copyright (c) 2024, Salesforce, Inc.
|
|
@@ -63,7 +66,7 @@ function toPropertyName(attr) {
|
|
|
63
66
|
let prop = '';
|
|
64
67
|
let shouldUpperCaseNext = false;
|
|
65
68
|
for (let i = 0; i < attr.length; i++) {
|
|
66
|
-
const char =
|
|
69
|
+
const char = StringCharAt.call(attr, i);
|
|
67
70
|
if (char === '-') {
|
|
68
71
|
shouldUpperCaseNext = true;
|
|
69
72
|
}
|
|
@@ -9293,6 +9296,9 @@ function isProperty(node) {
|
|
|
9293
9296
|
function isScopedSlotFragment(node) {
|
|
9294
9297
|
return node.type === 'ScopedSlotFragment';
|
|
9295
9298
|
}
|
|
9299
|
+
function isAttribute$1(node) {
|
|
9300
|
+
return node.type === 'Attribute';
|
|
9301
|
+
}
|
|
9296
9302
|
|
|
9297
9303
|
var LWCDirectiveDomMode;
|
|
9298
9304
|
(function (LWCDirectiveDomMode) {
|
|
@@ -12578,6 +12584,18 @@ function parseClassNames(classNames) {
|
|
|
12578
12584
|
.map((className) => className.trim())
|
|
12579
12585
|
.filter((className) => className.length);
|
|
12580
12586
|
}
|
|
12587
|
+
|
|
12588
|
+
/*
|
|
12589
|
+
* Copyright (c) 2024, salesforce.com, inc.
|
|
12590
|
+
* All rights reserved.
|
|
12591
|
+
* SPDX-License-Identifier: MIT
|
|
12592
|
+
* For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT
|
|
12593
|
+
*/
|
|
12594
|
+
// This set keeps track of static safe elements that have dynamic text in their direct children.
|
|
12595
|
+
const STATIC_ELEMENT_WITH_DYNAMIC_TEXT_SET = new WeakSet();
|
|
12596
|
+
// This map keeps track of static safe elements to their transformed children.
|
|
12597
|
+
// The children are transformed so that contiguous text nodes are consolidated into arrays.
|
|
12598
|
+
const STATIC_ELEMENT_TO_DYNAMIC_TEXT_CHILDREN_CACHE = new WeakMap();
|
|
12581
12599
|
function isStaticNode(node, apiVersion) {
|
|
12582
12600
|
let result = true;
|
|
12583
12601
|
const { name: nodeName, namespace = '', attributes, directives, properties } = node;
|
|
@@ -12590,8 +12608,9 @@ function isStaticNode(node, apiVersion) {
|
|
|
12590
12608
|
// it is an element
|
|
12591
12609
|
result &&= isElement(node);
|
|
12592
12610
|
// all attrs are static-safe
|
|
12611
|
+
// the criteria to determine safety can be found in computeAttrValue
|
|
12593
12612
|
result &&= attributes.every(({ name, value }) => {
|
|
12594
|
-
|
|
12613
|
+
const isStaticSafeLiteral = isLiteral(value) &&
|
|
12595
12614
|
name !== 'slot' &&
|
|
12596
12615
|
// check for ScopedId
|
|
12597
12616
|
name !== 'id' &&
|
|
@@ -12601,7 +12620,13 @@ function isStaticNode(node, apiVersion) {
|
|
|
12601
12620
|
!isSvgUseHref(nodeName, name, namespace) &&
|
|
12602
12621
|
// Check for ScopedFragId
|
|
12603
12622
|
!(isAllowedFragOnlyUrlsXHTML(nodeName, name, namespace) &&
|
|
12604
|
-
isFragmentOnlyUrl(value.value))
|
|
12623
|
+
isFragmentOnlyUrl(value.value));
|
|
12624
|
+
const isStaticSafeExpression = isExpression$1(value) &&
|
|
12625
|
+
name !== 'slot' &&
|
|
12626
|
+
// TODO [#3624]: Revisit whether svgs can be included in static content optimization
|
|
12627
|
+
// svg href needs sanitization.
|
|
12628
|
+
!isSvgUseHref(nodeName, name, namespace);
|
|
12629
|
+
return isStaticSafeLiteral || isStaticSafeExpression;
|
|
12605
12630
|
});
|
|
12606
12631
|
// all directives are static-safe
|
|
12607
12632
|
result &&= !directives.some((directive) => !STATIC_SAFE_DIRECTIVES.has(directive.name));
|
|
@@ -12610,30 +12635,40 @@ function isStaticNode(node, apiVersion) {
|
|
|
12610
12635
|
return result;
|
|
12611
12636
|
}
|
|
12612
12637
|
function collectStaticNodes(node, staticNodes, state) {
|
|
12613
|
-
let
|
|
12614
|
-
let
|
|
12638
|
+
let childrenAreStaticSafe = true;
|
|
12639
|
+
let nodeIsStaticSafe;
|
|
12615
12640
|
if (isText(node)) {
|
|
12616
|
-
|
|
12641
|
+
nodeIsStaticSafe = true;
|
|
12617
12642
|
}
|
|
12618
12643
|
else if (isComment(node)) {
|
|
12619
|
-
|
|
12644
|
+
nodeIsStaticSafe = true;
|
|
12620
12645
|
}
|
|
12621
12646
|
else {
|
|
12647
|
+
let hasDynamicText = false;
|
|
12622
12648
|
// it is ElseBlock | ForBlock | If | BaseElement
|
|
12623
12649
|
node.children.forEach((childNode) => {
|
|
12624
12650
|
collectStaticNodes(childNode, staticNodes, state);
|
|
12625
|
-
|
|
12651
|
+
childrenAreStaticSafe &&= staticNodes.has(childNode);
|
|
12652
|
+
// Collect nodes that have dynamic text ahead of time.
|
|
12653
|
+
// We only need to know if the direct child has dynamic text.
|
|
12654
|
+
hasDynamicText ||= isText(childNode) && !isStringLiteral(childNode.value);
|
|
12626
12655
|
});
|
|
12627
12656
|
// for IfBlock and ElseifBlock, traverse down the else branch
|
|
12628
12657
|
if (isConditionalParentBlock(node) && node.else) {
|
|
12629
12658
|
collectStaticNodes(node.else, staticNodes, state);
|
|
12630
12659
|
}
|
|
12631
|
-
|
|
12660
|
+
nodeIsStaticSafe =
|
|
12632
12661
|
isBaseElement(node) &&
|
|
12633
12662
|
!isCustomRendererHookRequired(node, state) &&
|
|
12634
12663
|
isStaticNode(node, state.config.apiVersion);
|
|
12664
|
+
if (nodeIsStaticSafe && hasDynamicText) {
|
|
12665
|
+
// Track when the static element contains dynamic text.
|
|
12666
|
+
// This will alter the way the children need to be traversed to apply static parts.
|
|
12667
|
+
// See transformStaticChildren below.
|
|
12668
|
+
STATIC_ELEMENT_WITH_DYNAMIC_TEXT_SET.add(node);
|
|
12669
|
+
}
|
|
12635
12670
|
}
|
|
12636
|
-
if (
|
|
12671
|
+
if (nodeIsStaticSafe && childrenAreStaticSafe) {
|
|
12637
12672
|
staticNodes.add(node);
|
|
12638
12673
|
}
|
|
12639
12674
|
}
|
|
@@ -12644,6 +12679,62 @@ function getStaticNodes(root, state) {
|
|
|
12644
12679
|
});
|
|
12645
12680
|
return staticNodes;
|
|
12646
12681
|
}
|
|
12682
|
+
// The purpose of this function is to concatenate the contiguous text nodes into a single array
|
|
12683
|
+
// to simplify the traversing logic when generating static parts and serializing the element.
|
|
12684
|
+
function transformStaticChildren(elm) {
|
|
12685
|
+
const children = elm.children;
|
|
12686
|
+
if (!children.length || !STATIC_ELEMENT_WITH_DYNAMIC_TEXT_SET.has(elm)) {
|
|
12687
|
+
// The element either has no children or its children does not contain dynamic text.
|
|
12688
|
+
return children;
|
|
12689
|
+
}
|
|
12690
|
+
if (STATIC_ELEMENT_TO_DYNAMIC_TEXT_CHILDREN_CACHE.has(elm)) {
|
|
12691
|
+
return STATIC_ELEMENT_TO_DYNAMIC_TEXT_CHILDREN_CACHE.get(elm);
|
|
12692
|
+
}
|
|
12693
|
+
const result = [];
|
|
12694
|
+
const len = children.length;
|
|
12695
|
+
let current;
|
|
12696
|
+
let next;
|
|
12697
|
+
let contiguousTextNodes = null;
|
|
12698
|
+
for (let i = 0; i < len; i++) {
|
|
12699
|
+
current = children[i];
|
|
12700
|
+
if (!isText(current)) {
|
|
12701
|
+
contiguousTextNodes = null;
|
|
12702
|
+
result.push(current);
|
|
12703
|
+
}
|
|
12704
|
+
else {
|
|
12705
|
+
if (!isNull(contiguousTextNodes)) {
|
|
12706
|
+
// Already in a contiguous text node chain
|
|
12707
|
+
// All contiguous nodes represent an expression in the source, it's guaranteed by the parser.
|
|
12708
|
+
contiguousTextNodes.push(current);
|
|
12709
|
+
}
|
|
12710
|
+
else {
|
|
12711
|
+
next = children[i + 1];
|
|
12712
|
+
if (isExpression$1(current) || (!isUndefined(next) && isText(next))) {
|
|
12713
|
+
// Text nodes can appear as follows:
|
|
12714
|
+
// 1. A single text literal node.
|
|
12715
|
+
// 2. A single text expression node.
|
|
12716
|
+
// 3. Contiguous series of text nodes (literal/expression mixed) with at least 1 expression.
|
|
12717
|
+
// When there is an expression in the source, the text nodes are split into contiguous text nodes.
|
|
12718
|
+
// When there is no expression in the source, the text will appear as a single text literal.
|
|
12719
|
+
// We normalize all of the contiguous text nodes or single text expression to an array.
|
|
12720
|
+
// Single text literal nodes (no expression or are not part of a contiguous set of text nodes) remain text nodes
|
|
12721
|
+
// and will not be consolidated to an array.
|
|
12722
|
+
// This is to normalize the traversal behavior when creating static parts and when serializing
|
|
12723
|
+
// the elements.
|
|
12724
|
+
contiguousTextNodes = [current];
|
|
12725
|
+
}
|
|
12726
|
+
// When contiguousTextNodes is null it is a single string literal.
|
|
12727
|
+
result.push(contiguousTextNodes ?? current);
|
|
12728
|
+
}
|
|
12729
|
+
}
|
|
12730
|
+
}
|
|
12731
|
+
STATIC_ELEMENT_TO_DYNAMIC_TEXT_CHILDREN_CACHE.set(elm, result);
|
|
12732
|
+
return result;
|
|
12733
|
+
}
|
|
12734
|
+
// Dynamic text is consolidated from individual text arrays into a single Text[].
|
|
12735
|
+
// Static text = a single text literal node (not in an array).
|
|
12736
|
+
// Dynamic text = At least 1 text expression node + 0 or more text literal nodes (always in an array).
|
|
12737
|
+
const isDynamicText = (nodes) => isArray(nodes) && ArrayEvery.call(nodes, isText);
|
|
12647
12738
|
|
|
12648
12739
|
/*
|
|
12649
12740
|
* Copyright (c) 2018, salesforce.com, inc.
|
|
@@ -12670,7 +12761,7 @@ const rawContentElements = new Set([
|
|
|
12670
12761
|
function templateStringEscape(str) {
|
|
12671
12762
|
return str.replace(/\\/g, '\\\\').replace(/`/g, '\\`').replace(/\$\{/g, '\\${');
|
|
12672
12763
|
}
|
|
12673
|
-
function serializeAttrs(element) {
|
|
12764
|
+
function serializeAttrs(element, codeGen) {
|
|
12674
12765
|
/**
|
|
12675
12766
|
* 0: styleToken in existing class attr
|
|
12676
12767
|
* 1: styleToken for added class attr
|
|
@@ -12678,24 +12769,36 @@ function serializeAttrs(element) {
|
|
|
12678
12769
|
*/
|
|
12679
12770
|
const attrs = [];
|
|
12680
12771
|
let hasClassAttr = false;
|
|
12681
|
-
const collector = ({ name, value }) => {
|
|
12772
|
+
const collector = ({ name, value, hasExpression, }) => {
|
|
12682
12773
|
let v = typeof value === 'string' ? templateStringEscape(value) : value;
|
|
12683
12774
|
if (name === 'class') {
|
|
12684
12775
|
hasClassAttr = true;
|
|
12685
|
-
|
|
12776
|
+
// ${0} maps to class token that will be appended to the string.
|
|
12777
|
+
// See buildParseFragmentFn for details.
|
|
12778
|
+
// The token is only needed when the class attribute is static.
|
|
12779
|
+
// The token will be injected at runtime for expressions in parseFragmentFn.
|
|
12780
|
+
if (!hasExpression) {
|
|
12781
|
+
v += '${0}';
|
|
12782
|
+
}
|
|
12686
12783
|
}
|
|
12687
12784
|
if (typeof v === 'string') {
|
|
12688
|
-
|
|
12785
|
+
// Inject a placeholder where the staticPartId will go when an expression occurs.
|
|
12786
|
+
// This is only needed for SSR to inject the expression value during serialization.
|
|
12787
|
+
attrs.push(hasExpression ? `\${"${v}"}` : ` ${name}="${htmlEscape(v, true)}"`);
|
|
12689
12788
|
}
|
|
12690
12789
|
else {
|
|
12691
|
-
attrs.push(name);
|
|
12790
|
+
attrs.push(` ${name}`);
|
|
12692
12791
|
}
|
|
12693
12792
|
};
|
|
12694
12793
|
element.attributes
|
|
12695
12794
|
.map((attr) => {
|
|
12795
|
+
const hasExpression = isExpression$1(attr.value);
|
|
12696
12796
|
return {
|
|
12797
|
+
hasExpression,
|
|
12697
12798
|
name: attr.name,
|
|
12698
|
-
value:
|
|
12799
|
+
value: hasExpression
|
|
12800
|
+
? codeGen.getStaticExpressionToken(attr)
|
|
12801
|
+
: attr.value.value,
|
|
12699
12802
|
};
|
|
12700
12803
|
})
|
|
12701
12804
|
.forEach(collector);
|
|
@@ -12711,31 +12814,42 @@ function serializeAttrs(element) {
|
|
|
12711
12814
|
};
|
|
12712
12815
|
})
|
|
12713
12816
|
.forEach(collector);
|
|
12714
|
-
|
|
12817
|
+
// ${2} maps to style token attribute
|
|
12818
|
+
// ${3} maps to class attribute token + style token attribute
|
|
12819
|
+
// See buildParseFragmentFn for details.
|
|
12820
|
+
return attrs.join('') + (hasClassAttr ? '${2}' : '${3}');
|
|
12715
12821
|
}
|
|
12716
|
-
function serializeChildren(
|
|
12822
|
+
function serializeChildren(node, parentTagName, codeGen) {
|
|
12717
12823
|
let html = '';
|
|
12718
|
-
|
|
12824
|
+
for (const child of transformStaticChildren(node)) {
|
|
12719
12825
|
/* istanbul ignore else */
|
|
12720
|
-
if (
|
|
12721
|
-
html +=
|
|
12826
|
+
if (isDynamicText(child)) {
|
|
12827
|
+
html += serializeDynamicTextNode(child, codeGen);
|
|
12722
12828
|
}
|
|
12723
12829
|
else if (isText(child)) {
|
|
12724
|
-
html +=
|
|
12830
|
+
html += serializeStaticTextNode(child, rawContentElements.has(parentTagName.toUpperCase()));
|
|
12831
|
+
}
|
|
12832
|
+
else if (isElement(child)) {
|
|
12833
|
+
html += serializeStaticElement(child, codeGen);
|
|
12725
12834
|
}
|
|
12726
12835
|
else if (isComment(child)) {
|
|
12727
|
-
html += serializeCommentNode(child, preserveComments);
|
|
12836
|
+
html += serializeCommentNode(child, codeGen.preserveComments);
|
|
12728
12837
|
}
|
|
12729
12838
|
else {
|
|
12730
12839
|
throw new TypeError('Unknown node found while serializing static content. Allowed nodes types are: Element, Text and Comment.');
|
|
12731
12840
|
}
|
|
12732
|
-
}
|
|
12841
|
+
}
|
|
12733
12842
|
return html;
|
|
12734
12843
|
}
|
|
12735
12844
|
function serializeCommentNode(comment, preserveComment) {
|
|
12736
12845
|
return preserveComment ? `<!--${htmlEscape(templateStringEscape(comment.value))}-->` : '';
|
|
12737
12846
|
}
|
|
12738
|
-
function
|
|
12847
|
+
function serializeDynamicTextNode(textNodes, codeGen) {
|
|
12848
|
+
// The first text node is they key for contiguous text nodes and single expressions.
|
|
12849
|
+
// This is guaranteed to have a value by the isDynamicText check.
|
|
12850
|
+
return `\${"${codeGen.getStaticExpressionToken(textNodes[0])}"}`;
|
|
12851
|
+
}
|
|
12852
|
+
function serializeStaticTextNode(text, useRawContent) {
|
|
12739
12853
|
let content;
|
|
12740
12854
|
if (useRawContent) {
|
|
12741
12855
|
content = text.raw;
|
|
@@ -12743,19 +12857,20 @@ function serializeTextNode(text, useRawContent) {
|
|
|
12743
12857
|
else {
|
|
12744
12858
|
content = htmlEscape(text.value.value);
|
|
12745
12859
|
}
|
|
12746
|
-
|
|
12860
|
+
content = templateStringEscape(content);
|
|
12861
|
+
return content;
|
|
12747
12862
|
}
|
|
12748
|
-
function serializeStaticElement(element,
|
|
12863
|
+
function serializeStaticElement(element, codeGen) {
|
|
12749
12864
|
const { name: tagName, namespace } = element;
|
|
12750
12865
|
const isForeignElement = namespace !== HTML_NAMESPACE;
|
|
12751
12866
|
const hasChildren = element.children.length > 0;
|
|
12752
|
-
let html = `<${tagName}${serializeAttrs(element)}`;
|
|
12867
|
+
let html = `<${tagName}${serializeAttrs(element, codeGen)}`;
|
|
12753
12868
|
if (isForeignElement && !hasChildren) {
|
|
12754
12869
|
html += '/>';
|
|
12755
12870
|
return html;
|
|
12756
12871
|
}
|
|
12757
12872
|
html += '>';
|
|
12758
|
-
html += serializeChildren(element
|
|
12873
|
+
html += serializeChildren(element, tagName, codeGen);
|
|
12759
12874
|
if (!isVoidElement(tagName, namespace) || hasChildren) {
|
|
12760
12875
|
html += `</${tagName}>`;
|
|
12761
12876
|
}
|
|
@@ -12907,6 +13022,42 @@ function collectParamsFromMemberExpression(_node, _vars) {
|
|
|
12907
13022
|
// the AST, we'll validate anyway.
|
|
12908
13023
|
invariant(false, ParserDiagnostics.INVALID_EXPR_ARROW_FN_PARAM, ['member expressions']);
|
|
12909
13024
|
}
|
|
13025
|
+
function bindAttributeExpression(attr, element, codeGen, addLegacySanitizationHook) {
|
|
13026
|
+
const { name: elmName, namespace = '' } = element;
|
|
13027
|
+
const { value: attrValue } = attr;
|
|
13028
|
+
// Evaluate properties based on their attribute name
|
|
13029
|
+
const attrName = isProperty(attr) ? attr.attributeName : attr.name;
|
|
13030
|
+
const isUsedAsAttribute = isAttribute(element, attrName);
|
|
13031
|
+
const expression = codeGen.bindExpression(attrValue);
|
|
13032
|
+
// TODO [#2012]: Normalize global boolean attrs values passed to custom elements as props
|
|
13033
|
+
if (isUsedAsAttribute && isBooleanAttribute(attrName, elmName)) {
|
|
13034
|
+
// We need to do some manipulation to allow the diffing algorithm add/remove the attribute
|
|
13035
|
+
// without handling special cases at runtime.
|
|
13036
|
+
return codeGen.genBooleanAttributeExpr(expression);
|
|
13037
|
+
}
|
|
13038
|
+
if (attrName === 'tabindex') {
|
|
13039
|
+
return codeGen.genTabIndex([expression]);
|
|
13040
|
+
}
|
|
13041
|
+
if (attrName === 'id' || isIdReferencingAttribute(attrName)) {
|
|
13042
|
+
return codeGen.genScopedId(expression);
|
|
13043
|
+
}
|
|
13044
|
+
if (codeGen.scopeFragmentId && isAllowedFragOnlyUrlsXHTML(elmName, attrName, namespace)) {
|
|
13045
|
+
return codeGen.genScopedFragId(expression);
|
|
13046
|
+
}
|
|
13047
|
+
if (isSvgUseHref(elmName, attrName, namespace)) {
|
|
13048
|
+
if (addLegacySanitizationHook) {
|
|
13049
|
+
codeGen.usedLwcApis.add('sanitizeAttribute');
|
|
13050
|
+
return callExpression(identifier('sanitizeAttribute'), [
|
|
13051
|
+
literal$1(elmName),
|
|
13052
|
+
literal$1(namespace),
|
|
13053
|
+
literal$1(attrName),
|
|
13054
|
+
codeGen.genScopedFragId(expression),
|
|
13055
|
+
]);
|
|
13056
|
+
}
|
|
13057
|
+
return codeGen.genScopedFragId(expression);
|
|
13058
|
+
}
|
|
13059
|
+
return expression;
|
|
13060
|
+
}
|
|
12910
13061
|
|
|
12911
13062
|
/*
|
|
12912
13063
|
* Copyright (c) 2018, salesforce.com, inc.
|
|
@@ -12920,27 +13071,27 @@ const doStructuredClone = typeof structuredClone === 'function'
|
|
|
12920
13071
|
? structuredClone
|
|
12921
13072
|
: (obj) => JSON.parse(JSON.stringify(obj));
|
|
12922
13073
|
const RENDER_APIS = {
|
|
12923
|
-
|
|
12924
|
-
|
|
12925
|
-
element: { name: 'h', alias: 'api_element' },
|
|
12926
|
-
slot: { name: 's', alias: 'api_slot' },
|
|
13074
|
+
bind: { name: 'b', alias: 'api_bind' },
|
|
13075
|
+
comment: { name: 'co', alias: 'api_comment' },
|
|
12927
13076
|
customElement: { name: 'c', alias: 'api_custom_element' },
|
|
12928
|
-
dynamicCtor: { name: 'dc', alias: 'api_dynamic_component' },
|
|
12929
13077
|
// TODO [#3331]: remove usage of lwc:dynamic in 246
|
|
12930
13078
|
deprecatedDynamicCtor: { name: 'ddc', alias: 'api_deprecated_dynamic_component' },
|
|
12931
|
-
|
|
12932
|
-
text: { name: 't', alias: 'api_text' },
|
|
13079
|
+
dynamicCtor: { name: 'dc', alias: 'api_dynamic_component' },
|
|
12933
13080
|
dynamicText: { name: 'd', alias: 'api_dynamic_text' },
|
|
13081
|
+
element: { name: 'h', alias: 'api_element' },
|
|
13082
|
+
flatten: { name: 'f', alias: 'api_flatten' },
|
|
13083
|
+
fragment: { name: 'fr', alias: 'api_fragment' },
|
|
13084
|
+
iterator: { name: 'i', alias: 'api_iterator' },
|
|
12934
13085
|
key: { name: 'k', alias: 'api_key' },
|
|
12935
|
-
tabindex: { name: 'ti', alias: 'api_tab_index' },
|
|
12936
|
-
scopedId: { name: 'gid', alias: 'api_scoped_id' },
|
|
12937
|
-
scopedFragId: { name: 'fid', alias: 'api_scoped_frag_id' },
|
|
12938
|
-
comment: { name: 'co', alias: 'api_comment' },
|
|
12939
13086
|
sanitizeHtmlContent: { name: 'shc', alias: 'api_sanitize_html_content' },
|
|
12940
|
-
|
|
12941
|
-
|
|
13087
|
+
scopedFragId: { name: 'fid', alias: 'api_scoped_frag_id' },
|
|
13088
|
+
scopedId: { name: 'gid', alias: 'api_scoped_id' },
|
|
12942
13089
|
scopedSlotFactory: { name: 'ssf', alias: 'api_scoped_slot_factory' },
|
|
13090
|
+
slot: { name: 's', alias: 'api_slot' },
|
|
13091
|
+
staticFragment: { name: 'st', alias: 'api_static_fragment' },
|
|
12943
13092
|
staticPart: { name: 'sp', alias: 'api_static_part' },
|
|
13093
|
+
tabindex: { name: 'ti', alias: 'api_tab_index' },
|
|
13094
|
+
text: { name: 't', alias: 'api_text' },
|
|
12944
13095
|
};
|
|
12945
13096
|
class CodeGen {
|
|
12946
13097
|
constructor({ root, state, scopeFragmentId, }) {
|
|
@@ -12956,6 +13107,7 @@ class CodeGen {
|
|
|
12956
13107
|
this.slotNames = new Set();
|
|
12957
13108
|
this.memorizedIds = [];
|
|
12958
13109
|
this.referencedComponents = new Set();
|
|
13110
|
+
this.staticExpressionMap = new WeakMap();
|
|
12959
13111
|
this.root = root;
|
|
12960
13112
|
if (state.config.enableStaticContentOptimization) {
|
|
12961
13113
|
this.staticNodes = getStaticNodes(root, state);
|
|
@@ -13004,6 +13156,9 @@ class CodeGen {
|
|
|
13004
13156
|
return this._renderApiCall(RENDER_APIS.deprecatedDynamicCtor, args);
|
|
13005
13157
|
}
|
|
13006
13158
|
genText(value) {
|
|
13159
|
+
return this._renderApiCall(RENDER_APIS.text, [this.genConcatenatedText(value)]);
|
|
13160
|
+
}
|
|
13161
|
+
genConcatenatedText(value) {
|
|
13007
13162
|
const mappedValues = value.map((v) => {
|
|
13008
13163
|
return typeof v === 'string'
|
|
13009
13164
|
? literal$1(v)
|
|
@@ -13013,7 +13168,7 @@ class CodeGen {
|
|
|
13013
13168
|
for (let i = 1, n = mappedValues.length; i < n; i++) {
|
|
13014
13169
|
textConcatenation = binaryExpression('+', textConcatenation, mappedValues[i]);
|
|
13015
13170
|
}
|
|
13016
|
-
return
|
|
13171
|
+
return textConcatenation;
|
|
13017
13172
|
}
|
|
13018
13173
|
genComment(value) {
|
|
13019
13174
|
return this._renderApiCall(RENDER_APIS.comment, [literal$1(value)]);
|
|
@@ -13034,9 +13189,6 @@ class CodeGen {
|
|
|
13034
13189
|
genFlatten(children) {
|
|
13035
13190
|
return this._renderApiCall(RENDER_APIS.flatten, children);
|
|
13036
13191
|
}
|
|
13037
|
-
genKey(compilerKey, value) {
|
|
13038
|
-
return this._renderApiCall(RENDER_APIS.key, [compilerKey, value]);
|
|
13039
|
-
}
|
|
13040
13192
|
genScopedId(id) {
|
|
13041
13193
|
if (typeof id === 'string') {
|
|
13042
13194
|
return this._renderApiCall(RENDER_APIS.scopedId, [literal$1(id)]);
|
|
@@ -13097,6 +13249,28 @@ class CodeGen {
|
|
|
13097
13249
|
this.hasRefs = true;
|
|
13098
13250
|
return property$1(identifier('ref'), ref.value);
|
|
13099
13251
|
}
|
|
13252
|
+
genKeyExpression(ref, slotParentName) {
|
|
13253
|
+
if (ref) {
|
|
13254
|
+
// If element has user-supplied `key` or is in iterator, call `api.k`
|
|
13255
|
+
const forKeyExpression = this.bindExpression(ref.value);
|
|
13256
|
+
const key = this.generateKey();
|
|
13257
|
+
return this._renderApiCall(RENDER_APIS.key, [literal$1(key), forKeyExpression]);
|
|
13258
|
+
}
|
|
13259
|
+
else {
|
|
13260
|
+
// If standalone element with no user-defined key
|
|
13261
|
+
let key = this.generateKey();
|
|
13262
|
+
// Parent slot name could be the empty string
|
|
13263
|
+
if (slotParentName !== undefined) {
|
|
13264
|
+
// Prefixing the key is necessary to avoid conflicts with default content for the
|
|
13265
|
+
// slot which might have similar keys. Each vnode will always have a key that starts
|
|
13266
|
+
// with a numeric character from compiler. In this case, we add a unique notation
|
|
13267
|
+
// for slotted vnodes keys, e.g.: `@foo:1:1`. Note that this is *not* needed for
|
|
13268
|
+
// dynamic keys, since `api.k` already scopes based on the iteration.
|
|
13269
|
+
key = `@${slotParentName}:${key}`;
|
|
13270
|
+
}
|
|
13271
|
+
return literal$1(key);
|
|
13272
|
+
}
|
|
13273
|
+
}
|
|
13100
13274
|
/**
|
|
13101
13275
|
* This routine generates an expression that avoids
|
|
13102
13276
|
* computing the sanitized html of a raw html if it does not change
|
|
@@ -13206,10 +13380,9 @@ class CodeGen {
|
|
|
13206
13380
|
return expression;
|
|
13207
13381
|
}
|
|
13208
13382
|
genStaticElement(element, slotParentName) {
|
|
13209
|
-
const
|
|
13210
|
-
|
|
13211
|
-
|
|
13212
|
-
const html = serializeStaticElement(element, this.preserveComments);
|
|
13383
|
+
const staticParts = this.genStaticParts(element);
|
|
13384
|
+
// Generate static parts prior to serialization to inject the corresponding static part Id into the serialized output.
|
|
13385
|
+
const html = serializeStaticElement(element, this);
|
|
13213
13386
|
const parseMethod = element.name !== 'svg' && element.namespace === SVG_NAMESPACE
|
|
13214
13387
|
? PARSE_SVG_FRAGMENT_METHOD_NAME
|
|
13215
13388
|
: PARSE_FRAGMENT_METHOD_NAME;
|
|
@@ -13230,9 +13403,14 @@ class CodeGen {
|
|
|
13230
13403
|
identifier: identifier$1,
|
|
13231
13404
|
expr,
|
|
13232
13405
|
});
|
|
13233
|
-
|
|
13406
|
+
// Keys are only supported at the top level of a static block, and are serialized directly in the args for
|
|
13407
|
+
// the `api_static_fragment` call. We don't need to support keys in static parts (i.e. children of
|
|
13408
|
+
// the top-level element), because the compiler ignores any keys that aren't direct children of a
|
|
13409
|
+
// for:each block (see error code 1149 - "KEY_SHOULD_BE_IN_ITERATION").
|
|
13410
|
+
const key = element.directives.find(isKeyDirective);
|
|
13411
|
+
const keyExpression = this.genKeyExpression(key, slotParentName);
|
|
13412
|
+
const args = [identifier$1, keyExpression];
|
|
13234
13413
|
// Only add the third argument (staticParts) if this element needs it
|
|
13235
|
-
const staticParts = this.genStaticParts(element);
|
|
13236
13414
|
if (staticParts) {
|
|
13237
13415
|
args.push(staticParts);
|
|
13238
13416
|
}
|
|
@@ -13240,52 +13418,106 @@ class CodeGen {
|
|
|
13240
13418
|
}
|
|
13241
13419
|
genStaticParts(element) {
|
|
13242
13420
|
const stack = [element];
|
|
13243
|
-
const
|
|
13421
|
+
const partIdsToArgs = new Map();
|
|
13244
13422
|
let partId = -1;
|
|
13245
|
-
const
|
|
13246
|
-
let
|
|
13247
|
-
if (!
|
|
13248
|
-
|
|
13249
|
-
|
|
13423
|
+
const getPartIdArgs = (partId) => {
|
|
13424
|
+
let args = partIdsToArgs.get(partId);
|
|
13425
|
+
if (!args) {
|
|
13426
|
+
args = { text: literal$1(null), databag: literal$1(null) };
|
|
13427
|
+
partIdsToArgs.set(partId, args);
|
|
13250
13428
|
}
|
|
13251
|
-
|
|
13429
|
+
return args;
|
|
13430
|
+
};
|
|
13431
|
+
const setPartIdText = (text) => {
|
|
13432
|
+
const args = getPartIdArgs(partId);
|
|
13433
|
+
args.text = text;
|
|
13434
|
+
};
|
|
13435
|
+
const setPartIdDatabag = (databag) => {
|
|
13436
|
+
const args = getPartIdArgs(partId);
|
|
13437
|
+
args.databag = objectExpression(databag);
|
|
13252
13438
|
};
|
|
13253
13439
|
// Depth-first traversal. We assign a partId to each element, which is an integer based on traversal order.
|
|
13254
13440
|
while (stack.length > 0) {
|
|
13255
|
-
const
|
|
13441
|
+
const current = stack.shift();
|
|
13256
13442
|
// Skip comment nodes in parts count, as they will be stripped in production, unless when `lwc:preserve-comments` is enabled
|
|
13257
|
-
if (!isComment(
|
|
13443
|
+
if (isDynamicText(current) || !isComment(current) || this.preserveComments) {
|
|
13258
13444
|
partId++;
|
|
13259
13445
|
}
|
|
13260
|
-
if (
|
|
13446
|
+
if (isDynamicText(current)) {
|
|
13447
|
+
const textNodes = current;
|
|
13448
|
+
const partToken = `${"t" /* STATIC_PART_TOKEN_ID.TEXT */}${partId}`;
|
|
13449
|
+
// Use the first text node as the key.
|
|
13450
|
+
// Dynamic text is guaranteed to have at least 1 text node in the array by transformStaticChildren.
|
|
13451
|
+
this.staticExpressionMap.set(textNodes[0], partToken);
|
|
13452
|
+
const concatenatedText = this.genConcatenatedText(textNodes.map(({ value }) => isStringLiteral(value) ? value.value : this.bindExpression(value)));
|
|
13453
|
+
setPartIdText(concatenatedText);
|
|
13454
|
+
}
|
|
13455
|
+
else if (isElement(current)) {
|
|
13456
|
+
const elm = current;
|
|
13457
|
+
const databag = [];
|
|
13261
13458
|
// has event listeners
|
|
13262
|
-
if (
|
|
13263
|
-
|
|
13459
|
+
if (elm.listeners.length) {
|
|
13460
|
+
databag.push(this.genEventListeners(elm.listeners));
|
|
13264
13461
|
}
|
|
13265
|
-
//
|
|
13266
|
-
|
|
13462
|
+
// See STATIC_SAFE_DIRECTIVES for what's allowed here.
|
|
13463
|
+
// Also note that we don't generate the 'key' here, because we only support it at the top level
|
|
13464
|
+
// directly passed into the `api_static_fragment` function, not as a part.
|
|
13465
|
+
for (const directive of elm.directives) {
|
|
13267
13466
|
if (directive.name === 'Ref') {
|
|
13268
|
-
|
|
13467
|
+
databag.push(this.genRef(directive));
|
|
13269
13468
|
}
|
|
13270
13469
|
}
|
|
13271
|
-
|
|
13470
|
+
const attributeExpressions = [];
|
|
13471
|
+
for (const attribute of elm.attributes) {
|
|
13472
|
+
const { name, value } = attribute;
|
|
13473
|
+
if (isExpression$1(value)) {
|
|
13474
|
+
let partToken = '';
|
|
13475
|
+
if (name === 'style') {
|
|
13476
|
+
partToken = `${"s" /* STATIC_PART_TOKEN_ID.STYLE */}${partId}`;
|
|
13477
|
+
databag.push(property$1(identifier('style'), this.bindExpression(value)));
|
|
13478
|
+
}
|
|
13479
|
+
else if (name === 'class') {
|
|
13480
|
+
partToken = `${"c" /* STATIC_PART_TOKEN_ID.CLASS */}${partId}`;
|
|
13481
|
+
databag.push(property$1(identifier('className'), this.bindExpression(value)));
|
|
13482
|
+
}
|
|
13483
|
+
else {
|
|
13484
|
+
partToken = `${"a" /* STATIC_PART_TOKEN_ID.ATTRIBUTE */}${partId}:${name}`;
|
|
13485
|
+
attributeExpressions.push(property$1(literal$1(name), bindAttributeExpression(attribute, elm, this, false)));
|
|
13486
|
+
}
|
|
13487
|
+
this.staticExpressionMap.set(attribute, partToken);
|
|
13488
|
+
}
|
|
13489
|
+
}
|
|
13490
|
+
if (attributeExpressions.length) {
|
|
13491
|
+
databag.push(property$1(identifier('attrs'), objectExpression(attributeExpressions)));
|
|
13492
|
+
}
|
|
13493
|
+
if (databag.length) {
|
|
13494
|
+
setPartIdDatabag(databag);
|
|
13495
|
+
}
|
|
13496
|
+
// For depth-first traversal, children must be prepended in order, so that they are processed before
|
|
13272
13497
|
// siblings. Note that this is consistent with the order used in the diffing algo as well as
|
|
13273
13498
|
// `traverseAndSetElements` in @lwc/engine-core.
|
|
13274
|
-
stack.unshift(...
|
|
13499
|
+
stack.unshift(...transformStaticChildren(elm));
|
|
13275
13500
|
}
|
|
13276
13501
|
}
|
|
13277
|
-
if (
|
|
13278
|
-
return undefined; // no
|
|
13502
|
+
if (partIdsToArgs.size === 0) {
|
|
13503
|
+
return undefined; // no parts needed
|
|
13279
13504
|
}
|
|
13280
|
-
return arrayExpression([...
|
|
13281
|
-
return this.genStaticPart(partId,
|
|
13505
|
+
return arrayExpression([...partIdsToArgs.entries()].map(([partId, { databag, text }]) => {
|
|
13506
|
+
return this.genStaticPart(partId, databag, text);
|
|
13282
13507
|
}));
|
|
13283
13508
|
}
|
|
13284
|
-
genStaticPart(partId,
|
|
13285
|
-
return this._renderApiCall(RENDER_APIS.staticPart, [
|
|
13286
|
-
|
|
13287
|
-
|
|
13288
|
-
|
|
13509
|
+
genStaticPart(partId, data, text) {
|
|
13510
|
+
return this._renderApiCall(RENDER_APIS.staticPart, [literal$1(partId), data, text]);
|
|
13511
|
+
}
|
|
13512
|
+
getStaticExpressionToken(node) {
|
|
13513
|
+
const token = this.staticExpressionMap.get(node);
|
|
13514
|
+
/* istanbul ignore if */
|
|
13515
|
+
if (isUndefined(token)) {
|
|
13516
|
+
// It should not be possible to hit this code path
|
|
13517
|
+
const nodeName = isAttribute$1(node) ? node.name : 'text node';
|
|
13518
|
+
throw new Error(`Template compiler internal error, unable to map ${nodeName} to a static expression.`);
|
|
13519
|
+
}
|
|
13520
|
+
return token;
|
|
13289
13521
|
}
|
|
13290
13522
|
}
|
|
13291
13523
|
|
|
@@ -13476,13 +13708,15 @@ function format(templateFn, codeGen) {
|
|
|
13476
13708
|
function transform(codeGen) {
|
|
13477
13709
|
const instrumentation = codeGen.state.config.instrumentation;
|
|
13478
13710
|
function transformElement(element, slotParentName) {
|
|
13711
|
+
// TODO [#4077]: Move databag gathering to after static element check as it doesn't seem to be used by static
|
|
13712
|
+
// content optimization.
|
|
13479
13713
|
const databag = elementDataBag(element, slotParentName);
|
|
13480
|
-
let res;
|
|
13481
13714
|
if (codeGen.staticNodes.has(element) && isElement(element)) {
|
|
13482
13715
|
// do not process children of static nodes.
|
|
13483
13716
|
return codeGen.genStaticElement(element, slotParentName);
|
|
13484
13717
|
}
|
|
13485
13718
|
const children = transformChildren(element);
|
|
13719
|
+
let res;
|
|
13486
13720
|
const { name } = element;
|
|
13487
13721
|
// lwc:dynamic directive
|
|
13488
13722
|
const deprecatedDynamicDirective = element.directives.find(isDynamicDirective);
|
|
@@ -13722,36 +13956,7 @@ function transform(codeGen) {
|
|
|
13722
13956
|
const attrName = isProperty(attr) ? attr.attributeName : attr.name;
|
|
13723
13957
|
const isUsedAsAttribute = isAttribute(element, attrName);
|
|
13724
13958
|
if (isExpression$1(attrValue)) {
|
|
13725
|
-
|
|
13726
|
-
// TODO [#2012]: Normalize global boolean attrs values passed to custom elements as props
|
|
13727
|
-
if (isUsedAsAttribute && isBooleanAttribute(attrName, elmName)) {
|
|
13728
|
-
// We need to do some manipulation to allow the diffing algorithm add/remove the attribute
|
|
13729
|
-
// without handling special cases at runtime.
|
|
13730
|
-
return codeGen.genBooleanAttributeExpr(expression);
|
|
13731
|
-
}
|
|
13732
|
-
if (attrName === 'tabindex') {
|
|
13733
|
-
return codeGen.genTabIndex([expression]);
|
|
13734
|
-
}
|
|
13735
|
-
if (attrName === 'id' || isIdReferencingAttribute(attrName)) {
|
|
13736
|
-
return codeGen.genScopedId(expression);
|
|
13737
|
-
}
|
|
13738
|
-
if (codeGen.scopeFragmentId &&
|
|
13739
|
-
isAllowedFragOnlyUrlsXHTML(elmName, attrName, namespace)) {
|
|
13740
|
-
return codeGen.genScopedFragId(expression);
|
|
13741
|
-
}
|
|
13742
|
-
if (isSvgUseHref(elmName, attrName, namespace)) {
|
|
13743
|
-
if (addLegacySanitizationHook) {
|
|
13744
|
-
codeGen.usedLwcApis.add('sanitizeAttribute');
|
|
13745
|
-
return callExpression(identifier('sanitizeAttribute'), [
|
|
13746
|
-
literal$1(elmName),
|
|
13747
|
-
literal$1(namespace),
|
|
13748
|
-
literal$1(attrName),
|
|
13749
|
-
codeGen.genScopedFragId(expression),
|
|
13750
|
-
]);
|
|
13751
|
-
}
|
|
13752
|
-
return codeGen.genScopedFragId(expression);
|
|
13753
|
-
}
|
|
13754
|
-
return expression;
|
|
13959
|
+
return bindAttributeExpression(attr, element, codeGen, addLegacySanitizationHook);
|
|
13755
13960
|
}
|
|
13756
13961
|
else if (isStringLiteral(attrValue)) {
|
|
13757
13962
|
if (attrName === 'id') {
|
|
@@ -13902,26 +14107,7 @@ function transform(codeGen) {
|
|
|
13902
14107
|
data.push(property$1(identifier('context'), contextObj));
|
|
13903
14108
|
}
|
|
13904
14109
|
// Key property on VNode
|
|
13905
|
-
|
|
13906
|
-
// If element has user-supplied `key` or is in iterator, call `api.k`
|
|
13907
|
-
const forKeyExpression = codeGen.bindExpression(forKey.value);
|
|
13908
|
-
const generatedKey = codeGen.genKey(literal$1(codeGen.generateKey()), forKeyExpression);
|
|
13909
|
-
data.push(property$1(identifier('key'), generatedKey));
|
|
13910
|
-
}
|
|
13911
|
-
else {
|
|
13912
|
-
// If standalone element with no user-defined key
|
|
13913
|
-
let key = codeGen.generateKey();
|
|
13914
|
-
// Parent slot name could be the empty string
|
|
13915
|
-
if (slotParentName !== undefined) {
|
|
13916
|
-
// Prefixing the key is necessary to avoid conflicts with default content for the
|
|
13917
|
-
// slot which might have similar keys. Each vnode will always have a key that starts
|
|
13918
|
-
// with a numeric character from compiler. In this case, we add a unique notation
|
|
13919
|
-
// for slotted vnodes keys, e.g.: `@foo:1:1`. Note that this is *not* needed for
|
|
13920
|
-
// dynamic keys, since `api.k` already scopes based on the iteration.
|
|
13921
|
-
key = `@${slotParentName}:${key}`;
|
|
13922
|
-
}
|
|
13923
|
-
data.push(property$1(identifier('key'), literal$1(key)));
|
|
13924
|
-
}
|
|
14110
|
+
data.push(property$1(identifier('key'), codeGen.genKeyExpression(forKey, slotParentName)));
|
|
13925
14111
|
// Event handler
|
|
13926
14112
|
if (listeners.length) {
|
|
13927
14113
|
data.push(codeGen.genEventListeners(listeners));
|
|
@@ -14033,5 +14219,5 @@ function compile(source, config) {
|
|
|
14033
14219
|
}
|
|
14034
14220
|
|
|
14035
14221
|
export { ElementDirectiveName, LWCDirectiveDomMode, LWCDirectiveRenderMode, LwcTagName, RootDirectiveName, TemplateDirectiveName, compile, compile as default, parse };
|
|
14036
|
-
/** version: 6.
|
|
14222
|
+
/** version: 6.4.0 */
|
|
14037
14223
|
//# sourceMappingURL=index.js.map
|