@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.cjs.js
CHANGED
|
@@ -75,7 +75,10 @@ const DASHED_TAGNAME_ELEMENT_SET = new Set([
|
|
|
75
75
|
'missing-glyph',
|
|
76
76
|
]);
|
|
77
77
|
// Subset of LWC template directives that can safely be statically optimized
|
|
78
|
-
const STATIC_SAFE_DIRECTIVES = new Set([
|
|
78
|
+
const STATIC_SAFE_DIRECTIVES = new Set([
|
|
79
|
+
'Ref',
|
|
80
|
+
'Key',
|
|
81
|
+
]);
|
|
79
82
|
|
|
80
83
|
/*
|
|
81
84
|
* Copyright (c) 2024, Salesforce, Inc.
|
|
@@ -87,7 +90,7 @@ function toPropertyName(attr) {
|
|
|
87
90
|
let prop = '';
|
|
88
91
|
let shouldUpperCaseNext = false;
|
|
89
92
|
for (let i = 0; i < attr.length; i++) {
|
|
90
|
-
const char =
|
|
93
|
+
const char = shared.StringCharAt.call(attr, i);
|
|
91
94
|
if (char === '-') {
|
|
92
95
|
shouldUpperCaseNext = true;
|
|
93
96
|
}
|
|
@@ -9317,6 +9320,9 @@ function isProperty(node) {
|
|
|
9317
9320
|
function isScopedSlotFragment(node) {
|
|
9318
9321
|
return node.type === 'ScopedSlotFragment';
|
|
9319
9322
|
}
|
|
9323
|
+
function isAttribute$1(node) {
|
|
9324
|
+
return node.type === 'Attribute';
|
|
9325
|
+
}
|
|
9320
9326
|
|
|
9321
9327
|
exports.LWCDirectiveDomMode = void 0;
|
|
9322
9328
|
(function (LWCDirectiveDomMode) {
|
|
@@ -12602,6 +12608,18 @@ function parseClassNames(classNames) {
|
|
|
12602
12608
|
.map((className) => className.trim())
|
|
12603
12609
|
.filter((className) => className.length);
|
|
12604
12610
|
}
|
|
12611
|
+
|
|
12612
|
+
/*
|
|
12613
|
+
* Copyright (c) 2024, salesforce.com, inc.
|
|
12614
|
+
* All rights reserved.
|
|
12615
|
+
* SPDX-License-Identifier: MIT
|
|
12616
|
+
* For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT
|
|
12617
|
+
*/
|
|
12618
|
+
// This set keeps track of static safe elements that have dynamic text in their direct children.
|
|
12619
|
+
const STATIC_ELEMENT_WITH_DYNAMIC_TEXT_SET = new WeakSet();
|
|
12620
|
+
// This map keeps track of static safe elements to their transformed children.
|
|
12621
|
+
// The children are transformed so that contiguous text nodes are consolidated into arrays.
|
|
12622
|
+
const STATIC_ELEMENT_TO_DYNAMIC_TEXT_CHILDREN_CACHE = new WeakMap();
|
|
12605
12623
|
function isStaticNode(node, apiVersion) {
|
|
12606
12624
|
let result = true;
|
|
12607
12625
|
const { name: nodeName, namespace = '', attributes, directives, properties } = node;
|
|
@@ -12614,8 +12632,9 @@ function isStaticNode(node, apiVersion) {
|
|
|
12614
12632
|
// it is an element
|
|
12615
12633
|
result &&= isElement(node);
|
|
12616
12634
|
// all attrs are static-safe
|
|
12635
|
+
// the criteria to determine safety can be found in computeAttrValue
|
|
12617
12636
|
result &&= attributes.every(({ name, value }) => {
|
|
12618
|
-
|
|
12637
|
+
const isStaticSafeLiteral = isLiteral(value) &&
|
|
12619
12638
|
name !== 'slot' &&
|
|
12620
12639
|
// check for ScopedId
|
|
12621
12640
|
name !== 'id' &&
|
|
@@ -12625,7 +12644,13 @@ function isStaticNode(node, apiVersion) {
|
|
|
12625
12644
|
!isSvgUseHref(nodeName, name, namespace) &&
|
|
12626
12645
|
// Check for ScopedFragId
|
|
12627
12646
|
!(isAllowedFragOnlyUrlsXHTML(nodeName, name, namespace) &&
|
|
12628
|
-
isFragmentOnlyUrl(value.value))
|
|
12647
|
+
isFragmentOnlyUrl(value.value));
|
|
12648
|
+
const isStaticSafeExpression = isExpression$1(value) &&
|
|
12649
|
+
name !== 'slot' &&
|
|
12650
|
+
// TODO [#3624]: Revisit whether svgs can be included in static content optimization
|
|
12651
|
+
// svg href needs sanitization.
|
|
12652
|
+
!isSvgUseHref(nodeName, name, namespace);
|
|
12653
|
+
return isStaticSafeLiteral || isStaticSafeExpression;
|
|
12629
12654
|
});
|
|
12630
12655
|
// all directives are static-safe
|
|
12631
12656
|
result &&= !directives.some((directive) => !STATIC_SAFE_DIRECTIVES.has(directive.name));
|
|
@@ -12634,30 +12659,40 @@ function isStaticNode(node, apiVersion) {
|
|
|
12634
12659
|
return result;
|
|
12635
12660
|
}
|
|
12636
12661
|
function collectStaticNodes(node, staticNodes, state) {
|
|
12637
|
-
let
|
|
12638
|
-
let
|
|
12662
|
+
let childrenAreStaticSafe = true;
|
|
12663
|
+
let nodeIsStaticSafe;
|
|
12639
12664
|
if (isText(node)) {
|
|
12640
|
-
|
|
12665
|
+
nodeIsStaticSafe = true;
|
|
12641
12666
|
}
|
|
12642
12667
|
else if (isComment(node)) {
|
|
12643
|
-
|
|
12668
|
+
nodeIsStaticSafe = true;
|
|
12644
12669
|
}
|
|
12645
12670
|
else {
|
|
12671
|
+
let hasDynamicText = false;
|
|
12646
12672
|
// it is ElseBlock | ForBlock | If | BaseElement
|
|
12647
12673
|
node.children.forEach((childNode) => {
|
|
12648
12674
|
collectStaticNodes(childNode, staticNodes, state);
|
|
12649
|
-
|
|
12675
|
+
childrenAreStaticSafe &&= staticNodes.has(childNode);
|
|
12676
|
+
// Collect nodes that have dynamic text ahead of time.
|
|
12677
|
+
// We only need to know if the direct child has dynamic text.
|
|
12678
|
+
hasDynamicText ||= isText(childNode) && !isStringLiteral(childNode.value);
|
|
12650
12679
|
});
|
|
12651
12680
|
// for IfBlock and ElseifBlock, traverse down the else branch
|
|
12652
12681
|
if (isConditionalParentBlock(node) && node.else) {
|
|
12653
12682
|
collectStaticNodes(node.else, staticNodes, state);
|
|
12654
12683
|
}
|
|
12655
|
-
|
|
12684
|
+
nodeIsStaticSafe =
|
|
12656
12685
|
isBaseElement(node) &&
|
|
12657
12686
|
!isCustomRendererHookRequired(node, state) &&
|
|
12658
12687
|
isStaticNode(node, state.config.apiVersion);
|
|
12688
|
+
if (nodeIsStaticSafe && hasDynamicText) {
|
|
12689
|
+
// Track when the static element contains dynamic text.
|
|
12690
|
+
// This will alter the way the children need to be traversed to apply static parts.
|
|
12691
|
+
// See transformStaticChildren below.
|
|
12692
|
+
STATIC_ELEMENT_WITH_DYNAMIC_TEXT_SET.add(node);
|
|
12693
|
+
}
|
|
12659
12694
|
}
|
|
12660
|
-
if (
|
|
12695
|
+
if (nodeIsStaticSafe && childrenAreStaticSafe) {
|
|
12661
12696
|
staticNodes.add(node);
|
|
12662
12697
|
}
|
|
12663
12698
|
}
|
|
@@ -12668,6 +12703,62 @@ function getStaticNodes(root, state) {
|
|
|
12668
12703
|
});
|
|
12669
12704
|
return staticNodes;
|
|
12670
12705
|
}
|
|
12706
|
+
// The purpose of this function is to concatenate the contiguous text nodes into a single array
|
|
12707
|
+
// to simplify the traversing logic when generating static parts and serializing the element.
|
|
12708
|
+
function transformStaticChildren(elm) {
|
|
12709
|
+
const children = elm.children;
|
|
12710
|
+
if (!children.length || !STATIC_ELEMENT_WITH_DYNAMIC_TEXT_SET.has(elm)) {
|
|
12711
|
+
// The element either has no children or its children does not contain dynamic text.
|
|
12712
|
+
return children;
|
|
12713
|
+
}
|
|
12714
|
+
if (STATIC_ELEMENT_TO_DYNAMIC_TEXT_CHILDREN_CACHE.has(elm)) {
|
|
12715
|
+
return STATIC_ELEMENT_TO_DYNAMIC_TEXT_CHILDREN_CACHE.get(elm);
|
|
12716
|
+
}
|
|
12717
|
+
const result = [];
|
|
12718
|
+
const len = children.length;
|
|
12719
|
+
let current;
|
|
12720
|
+
let next;
|
|
12721
|
+
let contiguousTextNodes = null;
|
|
12722
|
+
for (let i = 0; i < len; i++) {
|
|
12723
|
+
current = children[i];
|
|
12724
|
+
if (!isText(current)) {
|
|
12725
|
+
contiguousTextNodes = null;
|
|
12726
|
+
result.push(current);
|
|
12727
|
+
}
|
|
12728
|
+
else {
|
|
12729
|
+
if (!shared.isNull(contiguousTextNodes)) {
|
|
12730
|
+
// Already in a contiguous text node chain
|
|
12731
|
+
// All contiguous nodes represent an expression in the source, it's guaranteed by the parser.
|
|
12732
|
+
contiguousTextNodes.push(current);
|
|
12733
|
+
}
|
|
12734
|
+
else {
|
|
12735
|
+
next = children[i + 1];
|
|
12736
|
+
if (isExpression$1(current) || (!shared.isUndefined(next) && isText(next))) {
|
|
12737
|
+
// Text nodes can appear as follows:
|
|
12738
|
+
// 1. A single text literal node.
|
|
12739
|
+
// 2. A single text expression node.
|
|
12740
|
+
// 3. Contiguous series of text nodes (literal/expression mixed) with at least 1 expression.
|
|
12741
|
+
// When there is an expression in the source, the text nodes are split into contiguous text nodes.
|
|
12742
|
+
// When there is no expression in the source, the text will appear as a single text literal.
|
|
12743
|
+
// We normalize all of the contiguous text nodes or single text expression to an array.
|
|
12744
|
+
// Single text literal nodes (no expression or are not part of a contiguous set of text nodes) remain text nodes
|
|
12745
|
+
// and will not be consolidated to an array.
|
|
12746
|
+
// This is to normalize the traversal behavior when creating static parts and when serializing
|
|
12747
|
+
// the elements.
|
|
12748
|
+
contiguousTextNodes = [current];
|
|
12749
|
+
}
|
|
12750
|
+
// When contiguousTextNodes is null it is a single string literal.
|
|
12751
|
+
result.push(contiguousTextNodes ?? current);
|
|
12752
|
+
}
|
|
12753
|
+
}
|
|
12754
|
+
}
|
|
12755
|
+
STATIC_ELEMENT_TO_DYNAMIC_TEXT_CHILDREN_CACHE.set(elm, result);
|
|
12756
|
+
return result;
|
|
12757
|
+
}
|
|
12758
|
+
// Dynamic text is consolidated from individual text arrays into a single Text[].
|
|
12759
|
+
// Static text = a single text literal node (not in an array).
|
|
12760
|
+
// Dynamic text = At least 1 text expression node + 0 or more text literal nodes (always in an array).
|
|
12761
|
+
const isDynamicText = (nodes) => shared.isArray(nodes) && shared.ArrayEvery.call(nodes, isText);
|
|
12671
12762
|
|
|
12672
12763
|
/*
|
|
12673
12764
|
* Copyright (c) 2018, salesforce.com, inc.
|
|
@@ -12694,7 +12785,7 @@ const rawContentElements = new Set([
|
|
|
12694
12785
|
function templateStringEscape(str) {
|
|
12695
12786
|
return str.replace(/\\/g, '\\\\').replace(/`/g, '\\`').replace(/\$\{/g, '\\${');
|
|
12696
12787
|
}
|
|
12697
|
-
function serializeAttrs(element) {
|
|
12788
|
+
function serializeAttrs(element, codeGen) {
|
|
12698
12789
|
/**
|
|
12699
12790
|
* 0: styleToken in existing class attr
|
|
12700
12791
|
* 1: styleToken for added class attr
|
|
@@ -12702,24 +12793,36 @@ function serializeAttrs(element) {
|
|
|
12702
12793
|
*/
|
|
12703
12794
|
const attrs = [];
|
|
12704
12795
|
let hasClassAttr = false;
|
|
12705
|
-
const collector = ({ name, value }) => {
|
|
12796
|
+
const collector = ({ name, value, hasExpression, }) => {
|
|
12706
12797
|
let v = typeof value === 'string' ? templateStringEscape(value) : value;
|
|
12707
12798
|
if (name === 'class') {
|
|
12708
12799
|
hasClassAttr = true;
|
|
12709
|
-
|
|
12800
|
+
// ${0} maps to class token that will be appended to the string.
|
|
12801
|
+
// See buildParseFragmentFn for details.
|
|
12802
|
+
// The token is only needed when the class attribute is static.
|
|
12803
|
+
// The token will be injected at runtime for expressions in parseFragmentFn.
|
|
12804
|
+
if (!hasExpression) {
|
|
12805
|
+
v += '${0}';
|
|
12806
|
+
}
|
|
12710
12807
|
}
|
|
12711
12808
|
if (typeof v === 'string') {
|
|
12712
|
-
|
|
12809
|
+
// Inject a placeholder where the staticPartId will go when an expression occurs.
|
|
12810
|
+
// This is only needed for SSR to inject the expression value during serialization.
|
|
12811
|
+
attrs.push(hasExpression ? `\${"${v}"}` : ` ${name}="${shared.htmlEscape(v, true)}"`);
|
|
12713
12812
|
}
|
|
12714
12813
|
else {
|
|
12715
|
-
attrs.push(name);
|
|
12814
|
+
attrs.push(` ${name}`);
|
|
12716
12815
|
}
|
|
12717
12816
|
};
|
|
12718
12817
|
element.attributes
|
|
12719
12818
|
.map((attr) => {
|
|
12819
|
+
const hasExpression = isExpression$1(attr.value);
|
|
12720
12820
|
return {
|
|
12821
|
+
hasExpression,
|
|
12721
12822
|
name: attr.name,
|
|
12722
|
-
value:
|
|
12823
|
+
value: hasExpression
|
|
12824
|
+
? codeGen.getStaticExpressionToken(attr)
|
|
12825
|
+
: attr.value.value,
|
|
12723
12826
|
};
|
|
12724
12827
|
})
|
|
12725
12828
|
.forEach(collector);
|
|
@@ -12735,31 +12838,42 @@ function serializeAttrs(element) {
|
|
|
12735
12838
|
};
|
|
12736
12839
|
})
|
|
12737
12840
|
.forEach(collector);
|
|
12738
|
-
|
|
12841
|
+
// ${2} maps to style token attribute
|
|
12842
|
+
// ${3} maps to class attribute token + style token attribute
|
|
12843
|
+
// See buildParseFragmentFn for details.
|
|
12844
|
+
return attrs.join('') + (hasClassAttr ? '${2}' : '${3}');
|
|
12739
12845
|
}
|
|
12740
|
-
function serializeChildren(
|
|
12846
|
+
function serializeChildren(node, parentTagName, codeGen) {
|
|
12741
12847
|
let html = '';
|
|
12742
|
-
|
|
12848
|
+
for (const child of transformStaticChildren(node)) {
|
|
12743
12849
|
/* istanbul ignore else */
|
|
12744
|
-
if (
|
|
12745
|
-
html +=
|
|
12850
|
+
if (isDynamicText(child)) {
|
|
12851
|
+
html += serializeDynamicTextNode(child, codeGen);
|
|
12746
12852
|
}
|
|
12747
12853
|
else if (isText(child)) {
|
|
12748
|
-
html +=
|
|
12854
|
+
html += serializeStaticTextNode(child, rawContentElements.has(parentTagName.toUpperCase()));
|
|
12855
|
+
}
|
|
12856
|
+
else if (isElement(child)) {
|
|
12857
|
+
html += serializeStaticElement(child, codeGen);
|
|
12749
12858
|
}
|
|
12750
12859
|
else if (isComment(child)) {
|
|
12751
|
-
html += serializeCommentNode(child, preserveComments);
|
|
12860
|
+
html += serializeCommentNode(child, codeGen.preserveComments);
|
|
12752
12861
|
}
|
|
12753
12862
|
else {
|
|
12754
12863
|
throw new TypeError('Unknown node found while serializing static content. Allowed nodes types are: Element, Text and Comment.');
|
|
12755
12864
|
}
|
|
12756
|
-
}
|
|
12865
|
+
}
|
|
12757
12866
|
return html;
|
|
12758
12867
|
}
|
|
12759
12868
|
function serializeCommentNode(comment, preserveComment) {
|
|
12760
12869
|
return preserveComment ? `<!--${shared.htmlEscape(templateStringEscape(comment.value))}-->` : '';
|
|
12761
12870
|
}
|
|
12762
|
-
function
|
|
12871
|
+
function serializeDynamicTextNode(textNodes, codeGen) {
|
|
12872
|
+
// The first text node is they key for contiguous text nodes and single expressions.
|
|
12873
|
+
// This is guaranteed to have a value by the isDynamicText check.
|
|
12874
|
+
return `\${"${codeGen.getStaticExpressionToken(textNodes[0])}"}`;
|
|
12875
|
+
}
|
|
12876
|
+
function serializeStaticTextNode(text, useRawContent) {
|
|
12763
12877
|
let content;
|
|
12764
12878
|
if (useRawContent) {
|
|
12765
12879
|
content = text.raw;
|
|
@@ -12767,19 +12881,20 @@ function serializeTextNode(text, useRawContent) {
|
|
|
12767
12881
|
else {
|
|
12768
12882
|
content = shared.htmlEscape(text.value.value);
|
|
12769
12883
|
}
|
|
12770
|
-
|
|
12884
|
+
content = templateStringEscape(content);
|
|
12885
|
+
return content;
|
|
12771
12886
|
}
|
|
12772
|
-
function serializeStaticElement(element,
|
|
12887
|
+
function serializeStaticElement(element, codeGen) {
|
|
12773
12888
|
const { name: tagName, namespace } = element;
|
|
12774
12889
|
const isForeignElement = namespace !== shared.HTML_NAMESPACE;
|
|
12775
12890
|
const hasChildren = element.children.length > 0;
|
|
12776
|
-
let html = `<${tagName}${serializeAttrs(element)}`;
|
|
12891
|
+
let html = `<${tagName}${serializeAttrs(element, codeGen)}`;
|
|
12777
12892
|
if (isForeignElement && !hasChildren) {
|
|
12778
12893
|
html += '/>';
|
|
12779
12894
|
return html;
|
|
12780
12895
|
}
|
|
12781
12896
|
html += '>';
|
|
12782
|
-
html += serializeChildren(element
|
|
12897
|
+
html += serializeChildren(element, tagName, codeGen);
|
|
12783
12898
|
if (!shared.isVoidElement(tagName, namespace) || hasChildren) {
|
|
12784
12899
|
html += `</${tagName}>`;
|
|
12785
12900
|
}
|
|
@@ -12931,6 +13046,42 @@ function collectParamsFromMemberExpression(_node, _vars) {
|
|
|
12931
13046
|
// the AST, we'll validate anyway.
|
|
12932
13047
|
errors.invariant(false, errors.ParserDiagnostics.INVALID_EXPR_ARROW_FN_PARAM, ['member expressions']);
|
|
12933
13048
|
}
|
|
13049
|
+
function bindAttributeExpression(attr, element, codeGen, addLegacySanitizationHook) {
|
|
13050
|
+
const { name: elmName, namespace = '' } = element;
|
|
13051
|
+
const { value: attrValue } = attr;
|
|
13052
|
+
// Evaluate properties based on their attribute name
|
|
13053
|
+
const attrName = isProperty(attr) ? attr.attributeName : attr.name;
|
|
13054
|
+
const isUsedAsAttribute = isAttribute(element, attrName);
|
|
13055
|
+
const expression = codeGen.bindExpression(attrValue);
|
|
13056
|
+
// TODO [#2012]: Normalize global boolean attrs values passed to custom elements as props
|
|
13057
|
+
if (isUsedAsAttribute && shared.isBooleanAttribute(attrName, elmName)) {
|
|
13058
|
+
// We need to do some manipulation to allow the diffing algorithm add/remove the attribute
|
|
13059
|
+
// without handling special cases at runtime.
|
|
13060
|
+
return codeGen.genBooleanAttributeExpr(expression);
|
|
13061
|
+
}
|
|
13062
|
+
if (attrName === 'tabindex') {
|
|
13063
|
+
return codeGen.genTabIndex([expression]);
|
|
13064
|
+
}
|
|
13065
|
+
if (attrName === 'id' || isIdReferencingAttribute(attrName)) {
|
|
13066
|
+
return codeGen.genScopedId(expression);
|
|
13067
|
+
}
|
|
13068
|
+
if (codeGen.scopeFragmentId && isAllowedFragOnlyUrlsXHTML(elmName, attrName, namespace)) {
|
|
13069
|
+
return codeGen.genScopedFragId(expression);
|
|
13070
|
+
}
|
|
13071
|
+
if (isSvgUseHref(elmName, attrName, namespace)) {
|
|
13072
|
+
if (addLegacySanitizationHook) {
|
|
13073
|
+
codeGen.usedLwcApis.add('sanitizeAttribute');
|
|
13074
|
+
return callExpression(identifier('sanitizeAttribute'), [
|
|
13075
|
+
literal$1(elmName),
|
|
13076
|
+
literal$1(namespace),
|
|
13077
|
+
literal$1(attrName),
|
|
13078
|
+
codeGen.genScopedFragId(expression),
|
|
13079
|
+
]);
|
|
13080
|
+
}
|
|
13081
|
+
return codeGen.genScopedFragId(expression);
|
|
13082
|
+
}
|
|
13083
|
+
return expression;
|
|
13084
|
+
}
|
|
12934
13085
|
|
|
12935
13086
|
/*
|
|
12936
13087
|
* Copyright (c) 2018, salesforce.com, inc.
|
|
@@ -12944,27 +13095,27 @@ const doStructuredClone = typeof structuredClone === 'function'
|
|
|
12944
13095
|
? structuredClone
|
|
12945
13096
|
: (obj) => JSON.parse(JSON.stringify(obj));
|
|
12946
13097
|
const RENDER_APIS = {
|
|
12947
|
-
|
|
12948
|
-
|
|
12949
|
-
element: { name: 'h', alias: 'api_element' },
|
|
12950
|
-
slot: { name: 's', alias: 'api_slot' },
|
|
13098
|
+
bind: { name: 'b', alias: 'api_bind' },
|
|
13099
|
+
comment: { name: 'co', alias: 'api_comment' },
|
|
12951
13100
|
customElement: { name: 'c', alias: 'api_custom_element' },
|
|
12952
|
-
dynamicCtor: { name: 'dc', alias: 'api_dynamic_component' },
|
|
12953
13101
|
// TODO [#3331]: remove usage of lwc:dynamic in 246
|
|
12954
13102
|
deprecatedDynamicCtor: { name: 'ddc', alias: 'api_deprecated_dynamic_component' },
|
|
12955
|
-
|
|
12956
|
-
text: { name: 't', alias: 'api_text' },
|
|
13103
|
+
dynamicCtor: { name: 'dc', alias: 'api_dynamic_component' },
|
|
12957
13104
|
dynamicText: { name: 'd', alias: 'api_dynamic_text' },
|
|
13105
|
+
element: { name: 'h', alias: 'api_element' },
|
|
13106
|
+
flatten: { name: 'f', alias: 'api_flatten' },
|
|
13107
|
+
fragment: { name: 'fr', alias: 'api_fragment' },
|
|
13108
|
+
iterator: { name: 'i', alias: 'api_iterator' },
|
|
12958
13109
|
key: { name: 'k', alias: 'api_key' },
|
|
12959
|
-
tabindex: { name: 'ti', alias: 'api_tab_index' },
|
|
12960
|
-
scopedId: { name: 'gid', alias: 'api_scoped_id' },
|
|
12961
|
-
scopedFragId: { name: 'fid', alias: 'api_scoped_frag_id' },
|
|
12962
|
-
comment: { name: 'co', alias: 'api_comment' },
|
|
12963
13110
|
sanitizeHtmlContent: { name: 'shc', alias: 'api_sanitize_html_content' },
|
|
12964
|
-
|
|
12965
|
-
|
|
13111
|
+
scopedFragId: { name: 'fid', alias: 'api_scoped_frag_id' },
|
|
13112
|
+
scopedId: { name: 'gid', alias: 'api_scoped_id' },
|
|
12966
13113
|
scopedSlotFactory: { name: 'ssf', alias: 'api_scoped_slot_factory' },
|
|
13114
|
+
slot: { name: 's', alias: 'api_slot' },
|
|
13115
|
+
staticFragment: { name: 'st', alias: 'api_static_fragment' },
|
|
12967
13116
|
staticPart: { name: 'sp', alias: 'api_static_part' },
|
|
13117
|
+
tabindex: { name: 'ti', alias: 'api_tab_index' },
|
|
13118
|
+
text: { name: 't', alias: 'api_text' },
|
|
12968
13119
|
};
|
|
12969
13120
|
class CodeGen {
|
|
12970
13121
|
constructor({ root, state, scopeFragmentId, }) {
|
|
@@ -12980,6 +13131,7 @@ class CodeGen {
|
|
|
12980
13131
|
this.slotNames = new Set();
|
|
12981
13132
|
this.memorizedIds = [];
|
|
12982
13133
|
this.referencedComponents = new Set();
|
|
13134
|
+
this.staticExpressionMap = new WeakMap();
|
|
12983
13135
|
this.root = root;
|
|
12984
13136
|
if (state.config.enableStaticContentOptimization) {
|
|
12985
13137
|
this.staticNodes = getStaticNodes(root, state);
|
|
@@ -13028,6 +13180,9 @@ class CodeGen {
|
|
|
13028
13180
|
return this._renderApiCall(RENDER_APIS.deprecatedDynamicCtor, args);
|
|
13029
13181
|
}
|
|
13030
13182
|
genText(value) {
|
|
13183
|
+
return this._renderApiCall(RENDER_APIS.text, [this.genConcatenatedText(value)]);
|
|
13184
|
+
}
|
|
13185
|
+
genConcatenatedText(value) {
|
|
13031
13186
|
const mappedValues = value.map((v) => {
|
|
13032
13187
|
return typeof v === 'string'
|
|
13033
13188
|
? literal$1(v)
|
|
@@ -13037,7 +13192,7 @@ class CodeGen {
|
|
|
13037
13192
|
for (let i = 1, n = mappedValues.length; i < n; i++) {
|
|
13038
13193
|
textConcatenation = binaryExpression('+', textConcatenation, mappedValues[i]);
|
|
13039
13194
|
}
|
|
13040
|
-
return
|
|
13195
|
+
return textConcatenation;
|
|
13041
13196
|
}
|
|
13042
13197
|
genComment(value) {
|
|
13043
13198
|
return this._renderApiCall(RENDER_APIS.comment, [literal$1(value)]);
|
|
@@ -13058,9 +13213,6 @@ class CodeGen {
|
|
|
13058
13213
|
genFlatten(children) {
|
|
13059
13214
|
return this._renderApiCall(RENDER_APIS.flatten, children);
|
|
13060
13215
|
}
|
|
13061
|
-
genKey(compilerKey, value) {
|
|
13062
|
-
return this._renderApiCall(RENDER_APIS.key, [compilerKey, value]);
|
|
13063
|
-
}
|
|
13064
13216
|
genScopedId(id) {
|
|
13065
13217
|
if (typeof id === 'string') {
|
|
13066
13218
|
return this._renderApiCall(RENDER_APIS.scopedId, [literal$1(id)]);
|
|
@@ -13121,6 +13273,28 @@ class CodeGen {
|
|
|
13121
13273
|
this.hasRefs = true;
|
|
13122
13274
|
return property$1(identifier('ref'), ref.value);
|
|
13123
13275
|
}
|
|
13276
|
+
genKeyExpression(ref, slotParentName) {
|
|
13277
|
+
if (ref) {
|
|
13278
|
+
// If element has user-supplied `key` or is in iterator, call `api.k`
|
|
13279
|
+
const forKeyExpression = this.bindExpression(ref.value);
|
|
13280
|
+
const key = this.generateKey();
|
|
13281
|
+
return this._renderApiCall(RENDER_APIS.key, [literal$1(key), forKeyExpression]);
|
|
13282
|
+
}
|
|
13283
|
+
else {
|
|
13284
|
+
// If standalone element with no user-defined key
|
|
13285
|
+
let key = this.generateKey();
|
|
13286
|
+
// Parent slot name could be the empty string
|
|
13287
|
+
if (slotParentName !== undefined) {
|
|
13288
|
+
// Prefixing the key is necessary to avoid conflicts with default content for the
|
|
13289
|
+
// slot which might have similar keys. Each vnode will always have a key that starts
|
|
13290
|
+
// with a numeric character from compiler. In this case, we add a unique notation
|
|
13291
|
+
// for slotted vnodes keys, e.g.: `@foo:1:1`. Note that this is *not* needed for
|
|
13292
|
+
// dynamic keys, since `api.k` already scopes based on the iteration.
|
|
13293
|
+
key = `@${slotParentName}:${key}`;
|
|
13294
|
+
}
|
|
13295
|
+
return literal$1(key);
|
|
13296
|
+
}
|
|
13297
|
+
}
|
|
13124
13298
|
/**
|
|
13125
13299
|
* This routine generates an expression that avoids
|
|
13126
13300
|
* computing the sanitized html of a raw html if it does not change
|
|
@@ -13230,10 +13404,9 @@ class CodeGen {
|
|
|
13230
13404
|
return expression;
|
|
13231
13405
|
}
|
|
13232
13406
|
genStaticElement(element, slotParentName) {
|
|
13233
|
-
const
|
|
13234
|
-
|
|
13235
|
-
|
|
13236
|
-
const html = serializeStaticElement(element, this.preserveComments);
|
|
13407
|
+
const staticParts = this.genStaticParts(element);
|
|
13408
|
+
// Generate static parts prior to serialization to inject the corresponding static part Id into the serialized output.
|
|
13409
|
+
const html = serializeStaticElement(element, this);
|
|
13237
13410
|
const parseMethod = element.name !== 'svg' && element.namespace === shared.SVG_NAMESPACE
|
|
13238
13411
|
? PARSE_SVG_FRAGMENT_METHOD_NAME
|
|
13239
13412
|
: PARSE_FRAGMENT_METHOD_NAME;
|
|
@@ -13254,9 +13427,14 @@ class CodeGen {
|
|
|
13254
13427
|
identifier: identifier$1,
|
|
13255
13428
|
expr,
|
|
13256
13429
|
});
|
|
13257
|
-
|
|
13430
|
+
// Keys are only supported at the top level of a static block, and are serialized directly in the args for
|
|
13431
|
+
// the `api_static_fragment` call. We don't need to support keys in static parts (i.e. children of
|
|
13432
|
+
// the top-level element), because the compiler ignores any keys that aren't direct children of a
|
|
13433
|
+
// for:each block (see error code 1149 - "KEY_SHOULD_BE_IN_ITERATION").
|
|
13434
|
+
const key = element.directives.find(isKeyDirective);
|
|
13435
|
+
const keyExpression = this.genKeyExpression(key, slotParentName);
|
|
13436
|
+
const args = [identifier$1, keyExpression];
|
|
13258
13437
|
// Only add the third argument (staticParts) if this element needs it
|
|
13259
|
-
const staticParts = this.genStaticParts(element);
|
|
13260
13438
|
if (staticParts) {
|
|
13261
13439
|
args.push(staticParts);
|
|
13262
13440
|
}
|
|
@@ -13264,52 +13442,106 @@ class CodeGen {
|
|
|
13264
13442
|
}
|
|
13265
13443
|
genStaticParts(element) {
|
|
13266
13444
|
const stack = [element];
|
|
13267
|
-
const
|
|
13445
|
+
const partIdsToArgs = new Map();
|
|
13268
13446
|
let partId = -1;
|
|
13269
|
-
const
|
|
13270
|
-
let
|
|
13271
|
-
if (!
|
|
13272
|
-
|
|
13273
|
-
|
|
13447
|
+
const getPartIdArgs = (partId) => {
|
|
13448
|
+
let args = partIdsToArgs.get(partId);
|
|
13449
|
+
if (!args) {
|
|
13450
|
+
args = { text: literal$1(null), databag: literal$1(null) };
|
|
13451
|
+
partIdsToArgs.set(partId, args);
|
|
13274
13452
|
}
|
|
13275
|
-
|
|
13453
|
+
return args;
|
|
13454
|
+
};
|
|
13455
|
+
const setPartIdText = (text) => {
|
|
13456
|
+
const args = getPartIdArgs(partId);
|
|
13457
|
+
args.text = text;
|
|
13458
|
+
};
|
|
13459
|
+
const setPartIdDatabag = (databag) => {
|
|
13460
|
+
const args = getPartIdArgs(partId);
|
|
13461
|
+
args.databag = objectExpression(databag);
|
|
13276
13462
|
};
|
|
13277
13463
|
// Depth-first traversal. We assign a partId to each element, which is an integer based on traversal order.
|
|
13278
13464
|
while (stack.length > 0) {
|
|
13279
|
-
const
|
|
13465
|
+
const current = stack.shift();
|
|
13280
13466
|
// Skip comment nodes in parts count, as they will be stripped in production, unless when `lwc:preserve-comments` is enabled
|
|
13281
|
-
if (!isComment(
|
|
13467
|
+
if (isDynamicText(current) || !isComment(current) || this.preserveComments) {
|
|
13282
13468
|
partId++;
|
|
13283
13469
|
}
|
|
13284
|
-
if (
|
|
13470
|
+
if (isDynamicText(current)) {
|
|
13471
|
+
const textNodes = current;
|
|
13472
|
+
const partToken = `${"t" /* STATIC_PART_TOKEN_ID.TEXT */}${partId}`;
|
|
13473
|
+
// Use the first text node as the key.
|
|
13474
|
+
// Dynamic text is guaranteed to have at least 1 text node in the array by transformStaticChildren.
|
|
13475
|
+
this.staticExpressionMap.set(textNodes[0], partToken);
|
|
13476
|
+
const concatenatedText = this.genConcatenatedText(textNodes.map(({ value }) => isStringLiteral(value) ? value.value : this.bindExpression(value)));
|
|
13477
|
+
setPartIdText(concatenatedText);
|
|
13478
|
+
}
|
|
13479
|
+
else if (isElement(current)) {
|
|
13480
|
+
const elm = current;
|
|
13481
|
+
const databag = [];
|
|
13285
13482
|
// has event listeners
|
|
13286
|
-
if (
|
|
13287
|
-
|
|
13483
|
+
if (elm.listeners.length) {
|
|
13484
|
+
databag.push(this.genEventListeners(elm.listeners));
|
|
13288
13485
|
}
|
|
13289
|
-
//
|
|
13290
|
-
|
|
13486
|
+
// See STATIC_SAFE_DIRECTIVES for what's allowed here.
|
|
13487
|
+
// Also note that we don't generate the 'key' here, because we only support it at the top level
|
|
13488
|
+
// directly passed into the `api_static_fragment` function, not as a part.
|
|
13489
|
+
for (const directive of elm.directives) {
|
|
13291
13490
|
if (directive.name === 'Ref') {
|
|
13292
|
-
|
|
13491
|
+
databag.push(this.genRef(directive));
|
|
13293
13492
|
}
|
|
13294
13493
|
}
|
|
13295
|
-
|
|
13494
|
+
const attributeExpressions = [];
|
|
13495
|
+
for (const attribute of elm.attributes) {
|
|
13496
|
+
const { name, value } = attribute;
|
|
13497
|
+
if (isExpression$1(value)) {
|
|
13498
|
+
let partToken = '';
|
|
13499
|
+
if (name === 'style') {
|
|
13500
|
+
partToken = `${"s" /* STATIC_PART_TOKEN_ID.STYLE */}${partId}`;
|
|
13501
|
+
databag.push(property$1(identifier('style'), this.bindExpression(value)));
|
|
13502
|
+
}
|
|
13503
|
+
else if (name === 'class') {
|
|
13504
|
+
partToken = `${"c" /* STATIC_PART_TOKEN_ID.CLASS */}${partId}`;
|
|
13505
|
+
databag.push(property$1(identifier('className'), this.bindExpression(value)));
|
|
13506
|
+
}
|
|
13507
|
+
else {
|
|
13508
|
+
partToken = `${"a" /* STATIC_PART_TOKEN_ID.ATTRIBUTE */}${partId}:${name}`;
|
|
13509
|
+
attributeExpressions.push(property$1(literal$1(name), bindAttributeExpression(attribute, elm, this, false)));
|
|
13510
|
+
}
|
|
13511
|
+
this.staticExpressionMap.set(attribute, partToken);
|
|
13512
|
+
}
|
|
13513
|
+
}
|
|
13514
|
+
if (attributeExpressions.length) {
|
|
13515
|
+
databag.push(property$1(identifier('attrs'), objectExpression(attributeExpressions)));
|
|
13516
|
+
}
|
|
13517
|
+
if (databag.length) {
|
|
13518
|
+
setPartIdDatabag(databag);
|
|
13519
|
+
}
|
|
13520
|
+
// For depth-first traversal, children must be prepended in order, so that they are processed before
|
|
13296
13521
|
// siblings. Note that this is consistent with the order used in the diffing algo as well as
|
|
13297
13522
|
// `traverseAndSetElements` in @lwc/engine-core.
|
|
13298
|
-
stack.unshift(...
|
|
13523
|
+
stack.unshift(...transformStaticChildren(elm));
|
|
13299
13524
|
}
|
|
13300
13525
|
}
|
|
13301
|
-
if (
|
|
13302
|
-
return undefined; // no
|
|
13526
|
+
if (partIdsToArgs.size === 0) {
|
|
13527
|
+
return undefined; // no parts needed
|
|
13303
13528
|
}
|
|
13304
|
-
return arrayExpression([...
|
|
13305
|
-
return this.genStaticPart(partId,
|
|
13529
|
+
return arrayExpression([...partIdsToArgs.entries()].map(([partId, { databag, text }]) => {
|
|
13530
|
+
return this.genStaticPart(partId, databag, text);
|
|
13306
13531
|
}));
|
|
13307
13532
|
}
|
|
13308
|
-
genStaticPart(partId,
|
|
13309
|
-
return this._renderApiCall(RENDER_APIS.staticPart, [
|
|
13310
|
-
|
|
13311
|
-
|
|
13312
|
-
|
|
13533
|
+
genStaticPart(partId, data, text) {
|
|
13534
|
+
return this._renderApiCall(RENDER_APIS.staticPart, [literal$1(partId), data, text]);
|
|
13535
|
+
}
|
|
13536
|
+
getStaticExpressionToken(node) {
|
|
13537
|
+
const token = this.staticExpressionMap.get(node);
|
|
13538
|
+
/* istanbul ignore if */
|
|
13539
|
+
if (shared.isUndefined(token)) {
|
|
13540
|
+
// It should not be possible to hit this code path
|
|
13541
|
+
const nodeName = isAttribute$1(node) ? node.name : 'text node';
|
|
13542
|
+
throw new Error(`Template compiler internal error, unable to map ${nodeName} to a static expression.`);
|
|
13543
|
+
}
|
|
13544
|
+
return token;
|
|
13313
13545
|
}
|
|
13314
13546
|
}
|
|
13315
13547
|
|
|
@@ -13500,13 +13732,15 @@ function format(templateFn, codeGen) {
|
|
|
13500
13732
|
function transform(codeGen) {
|
|
13501
13733
|
const instrumentation = codeGen.state.config.instrumentation;
|
|
13502
13734
|
function transformElement(element, slotParentName) {
|
|
13735
|
+
// TODO [#4077]: Move databag gathering to after static element check as it doesn't seem to be used by static
|
|
13736
|
+
// content optimization.
|
|
13503
13737
|
const databag = elementDataBag(element, slotParentName);
|
|
13504
|
-
let res;
|
|
13505
13738
|
if (codeGen.staticNodes.has(element) && isElement(element)) {
|
|
13506
13739
|
// do not process children of static nodes.
|
|
13507
13740
|
return codeGen.genStaticElement(element, slotParentName);
|
|
13508
13741
|
}
|
|
13509
13742
|
const children = transformChildren(element);
|
|
13743
|
+
let res;
|
|
13510
13744
|
const { name } = element;
|
|
13511
13745
|
// lwc:dynamic directive
|
|
13512
13746
|
const deprecatedDynamicDirective = element.directives.find(isDynamicDirective);
|
|
@@ -13746,36 +13980,7 @@ function transform(codeGen) {
|
|
|
13746
13980
|
const attrName = isProperty(attr) ? attr.attributeName : attr.name;
|
|
13747
13981
|
const isUsedAsAttribute = isAttribute(element, attrName);
|
|
13748
13982
|
if (isExpression$1(attrValue)) {
|
|
13749
|
-
|
|
13750
|
-
// TODO [#2012]: Normalize global boolean attrs values passed to custom elements as props
|
|
13751
|
-
if (isUsedAsAttribute && shared.isBooleanAttribute(attrName, elmName)) {
|
|
13752
|
-
// We need to do some manipulation to allow the diffing algorithm add/remove the attribute
|
|
13753
|
-
// without handling special cases at runtime.
|
|
13754
|
-
return codeGen.genBooleanAttributeExpr(expression);
|
|
13755
|
-
}
|
|
13756
|
-
if (attrName === 'tabindex') {
|
|
13757
|
-
return codeGen.genTabIndex([expression]);
|
|
13758
|
-
}
|
|
13759
|
-
if (attrName === 'id' || isIdReferencingAttribute(attrName)) {
|
|
13760
|
-
return codeGen.genScopedId(expression);
|
|
13761
|
-
}
|
|
13762
|
-
if (codeGen.scopeFragmentId &&
|
|
13763
|
-
isAllowedFragOnlyUrlsXHTML(elmName, attrName, namespace)) {
|
|
13764
|
-
return codeGen.genScopedFragId(expression);
|
|
13765
|
-
}
|
|
13766
|
-
if (isSvgUseHref(elmName, attrName, namespace)) {
|
|
13767
|
-
if (addLegacySanitizationHook) {
|
|
13768
|
-
codeGen.usedLwcApis.add('sanitizeAttribute');
|
|
13769
|
-
return callExpression(identifier('sanitizeAttribute'), [
|
|
13770
|
-
literal$1(elmName),
|
|
13771
|
-
literal$1(namespace),
|
|
13772
|
-
literal$1(attrName),
|
|
13773
|
-
codeGen.genScopedFragId(expression),
|
|
13774
|
-
]);
|
|
13775
|
-
}
|
|
13776
|
-
return codeGen.genScopedFragId(expression);
|
|
13777
|
-
}
|
|
13778
|
-
return expression;
|
|
13983
|
+
return bindAttributeExpression(attr, element, codeGen, addLegacySanitizationHook);
|
|
13779
13984
|
}
|
|
13780
13985
|
else if (isStringLiteral(attrValue)) {
|
|
13781
13986
|
if (attrName === 'id') {
|
|
@@ -13926,26 +14131,7 @@ function transform(codeGen) {
|
|
|
13926
14131
|
data.push(property$1(identifier('context'), contextObj));
|
|
13927
14132
|
}
|
|
13928
14133
|
// Key property on VNode
|
|
13929
|
-
|
|
13930
|
-
// If element has user-supplied `key` or is in iterator, call `api.k`
|
|
13931
|
-
const forKeyExpression = codeGen.bindExpression(forKey.value);
|
|
13932
|
-
const generatedKey = codeGen.genKey(literal$1(codeGen.generateKey()), forKeyExpression);
|
|
13933
|
-
data.push(property$1(identifier('key'), generatedKey));
|
|
13934
|
-
}
|
|
13935
|
-
else {
|
|
13936
|
-
// If standalone element with no user-defined key
|
|
13937
|
-
let key = codeGen.generateKey();
|
|
13938
|
-
// Parent slot name could be the empty string
|
|
13939
|
-
if (slotParentName !== undefined) {
|
|
13940
|
-
// Prefixing the key is necessary to avoid conflicts with default content for the
|
|
13941
|
-
// slot which might have similar keys. Each vnode will always have a key that starts
|
|
13942
|
-
// with a numeric character from compiler. In this case, we add a unique notation
|
|
13943
|
-
// for slotted vnodes keys, e.g.: `@foo:1:1`. Note that this is *not* needed for
|
|
13944
|
-
// dynamic keys, since `api.k` already scopes based on the iteration.
|
|
13945
|
-
key = `@${slotParentName}:${key}`;
|
|
13946
|
-
}
|
|
13947
|
-
data.push(property$1(identifier('key'), literal$1(key)));
|
|
13948
|
-
}
|
|
14134
|
+
data.push(property$1(identifier('key'), codeGen.genKeyExpression(forKey, slotParentName)));
|
|
13949
14135
|
// Event handler
|
|
13950
14136
|
if (listeners.length) {
|
|
13951
14137
|
data.push(codeGen.genEventListeners(listeners));
|
|
@@ -14059,5 +14245,5 @@ function compile(source, config) {
|
|
|
14059
14245
|
exports.compile = compile;
|
|
14060
14246
|
exports.default = compile;
|
|
14061
14247
|
exports.parse = parse;
|
|
14062
|
-
/** version: 6.
|
|
14248
|
+
/** version: 6.4.0 */
|
|
14063
14249
|
//# sourceMappingURL=index.cjs.js.map
|