@lwc/style-compiler 8.28.2 → 9.0.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/README.md +1 -1
- package/dist/index.js +16 -14
- package/dist/transform.d.ts +1 -1
- package/package.json +7 -3
- package/dist/index.cjs.js +0 -895
package/README.md
CHANGED
package/dist/index.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import postcss, { CssSyntaxError } from 'postcss';
|
|
5
5
|
import { LWC_VERSION_COMMENT, KEY__SCOPED_CSS, KEY__NATIVE_ONLY_CSS, getAPIVersionFromNumber } from '@lwc/shared';
|
|
6
|
-
import
|
|
6
|
+
import postCssSelectorParser from 'postcss-selector-parser';
|
|
7
7
|
import valueParser from 'postcss-value-parser';
|
|
8
8
|
|
|
9
9
|
const PLUGIN_NAME = '@lwc/style-compiler';
|
|
@@ -400,7 +400,7 @@ function process$1(root, result, isScoped, ctx) {
|
|
|
400
400
|
* For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT
|
|
401
401
|
*/
|
|
402
402
|
function isDirPseudoClass(node) {
|
|
403
|
-
return isPseudoClass(node) && node.value === ':dir';
|
|
403
|
+
return postCssSelectorParser.isPseudoClass(node) && node.value === ':dir';
|
|
404
404
|
}
|
|
405
405
|
|
|
406
406
|
function findNode(container, predicate) {
|
|
@@ -483,7 +483,7 @@ function validate(root, native, ctx) {
|
|
|
483
483
|
* For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT
|
|
484
484
|
*/
|
|
485
485
|
function isHostPseudoClass(node) {
|
|
486
|
-
return isPseudoClass(node) && node.value === ':host';
|
|
486
|
+
return postCssSelectorParser.isPseudoClass(node) && node.value === ':host';
|
|
487
487
|
}
|
|
488
488
|
/**
|
|
489
489
|
* Add scoping attributes to all the matching selectors:
|
|
@@ -496,7 +496,7 @@ function scopeSelector(selector) {
|
|
|
496
496
|
// Split the selector per compound selector. Compound selectors are interleaved with combinator nodes.
|
|
497
497
|
// https://drafts.csswg.org/selectors-4/#typedef-complex-selector
|
|
498
498
|
selector.each((node) => {
|
|
499
|
-
if (isCombinator(node)) {
|
|
499
|
+
if (postCssSelectorParser.isCombinator(node)) {
|
|
500
500
|
compoundSelectors.push([]);
|
|
501
501
|
}
|
|
502
502
|
else {
|
|
@@ -515,11 +515,11 @@ function scopeSelector(selector) {
|
|
|
515
515
|
let nodeToScope;
|
|
516
516
|
// In each compound selector we need to locate the last selector to scope.
|
|
517
517
|
for (const node of compoundSelector) {
|
|
518
|
-
if (!isPseudoElement(node)) {
|
|
518
|
+
if (!postCssSelectorParser.isPseudoElement(node)) {
|
|
519
519
|
nodeToScope = node;
|
|
520
520
|
}
|
|
521
521
|
}
|
|
522
|
-
const shadowAttribute = attribute({
|
|
522
|
+
const shadowAttribute = postCssSelectorParser.attribute({
|
|
523
523
|
attribute: SHADOW_ATTRIBUTE,
|
|
524
524
|
value: undefined,
|
|
525
525
|
raws: {},
|
|
@@ -559,7 +559,7 @@ function transformHost(selector) {
|
|
|
559
559
|
// Store the original location of the :host in the selector
|
|
560
560
|
const hostIndex = selector.index(hostNode);
|
|
561
561
|
// Swap the :host pseudo-class with the host scoping token
|
|
562
|
-
const hostAttribute = attribute({
|
|
562
|
+
const hostAttribute = postCssSelectorParser.attribute({
|
|
563
563
|
attribute: HOST_ATTRIBUTE,
|
|
564
564
|
value: undefined,
|
|
565
565
|
raws: {},
|
|
@@ -629,12 +629,12 @@ function transformDirPseudoClass (root, ctx) {
|
|
|
629
629
|
// I.e. we need to use a descendant selector (' ' combinator) relying on a `dir`
|
|
630
630
|
// attribute added to the host element. So we need two placeholders:
|
|
631
631
|
// `<synthetic_placeholder> .foo<native_placeholder>:not(.bar)`
|
|
632
|
-
const nativeAttribute = attribute({
|
|
632
|
+
const nativeAttribute = postCssSelectorParser.attribute({
|
|
633
633
|
attribute: value === 'ltr' ? DIR_ATTRIBUTE_NATIVE_LTR : DIR_ATTRIBUTE_NATIVE_RTL,
|
|
634
634
|
value: undefined,
|
|
635
635
|
raws: {},
|
|
636
636
|
});
|
|
637
|
-
const syntheticAttribute = attribute({
|
|
637
|
+
const syntheticAttribute = postCssSelectorParser.attribute({
|
|
638
638
|
attribute: value === 'ltr' ? DIR_ATTRIBUTE_SYNTHETIC_LTR : DIR_ATTRIBUTE_SYNTHETIC_RTL,
|
|
639
639
|
value: undefined,
|
|
640
640
|
raws: {},
|
|
@@ -642,9 +642,11 @@ function transformDirPseudoClass (root, ctx) {
|
|
|
642
642
|
node.replaceWith(nativeAttribute);
|
|
643
643
|
// If the selector is not empty and if the first node in the selector is not already a
|
|
644
644
|
// " " combinator, we need to use the descendant selector format
|
|
645
|
-
const shouldAddDescendantCombinator = selector.first &&
|
|
645
|
+
const shouldAddDescendantCombinator = selector.first &&
|
|
646
|
+
!postCssSelectorParser.isCombinator(selector.first) &&
|
|
647
|
+
selector.first.value !== ' ';
|
|
646
648
|
if (shouldAddDescendantCombinator) {
|
|
647
|
-
selector.insertBefore(selector.first, combinator({
|
|
649
|
+
selector.insertBefore(selector.first, postCssSelectorParser.combinator({
|
|
648
650
|
value: ' ',
|
|
649
651
|
}));
|
|
650
652
|
// Add the [dir] attribute in front of the " " combinator, i.e. as an ancestor
|
|
@@ -731,7 +733,7 @@ function shouldTransformSelector(rule) {
|
|
|
731
733
|
return rule.parent?.type !== 'atrule' || rule.parent.name !== 'keyframes';
|
|
732
734
|
}
|
|
733
735
|
function selectorProcessorFactory(transformConfig, ctx) {
|
|
734
|
-
return
|
|
736
|
+
return postCssSelectorParser((root) => {
|
|
735
737
|
validateIdSelectors(root, ctx);
|
|
736
738
|
transformSelector(root, transformConfig, ctx);
|
|
737
739
|
transformDirPseudoClass(root, ctx);
|
|
@@ -839,7 +841,7 @@ class StyleCompilerCtx {
|
|
|
839
841
|
* @param config Transformation options
|
|
840
842
|
* @returns Transformed CSS
|
|
841
843
|
* @example
|
|
842
|
-
*
|
|
844
|
+
* import { transform } from '@lwc/style-compiler';
|
|
843
845
|
* const source = `
|
|
844
846
|
* :host {
|
|
845
847
|
* opacity: 0.4;
|
|
@@ -887,5 +889,5 @@ function transform(src, id, config = {}) {
|
|
|
887
889
|
}
|
|
888
890
|
|
|
889
891
|
export { transform };
|
|
890
|
-
/** version:
|
|
892
|
+
/** version: 9.0.0 */
|
|
891
893
|
//# sourceMappingURL=index.js.map
|
package/dist/transform.d.ts
CHANGED
|
@@ -24,7 +24,7 @@ export interface Config {
|
|
|
24
24
|
* @param config Transformation options
|
|
25
25
|
* @returns Transformed CSS
|
|
26
26
|
* @example
|
|
27
|
-
*
|
|
27
|
+
* import { transform } from '@lwc/style-compiler';
|
|
28
28
|
* const source = `
|
|
29
29
|
* :host {
|
|
30
30
|
* opacity: 0.4;
|
package/package.json
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"You can safely modify dependencies, devDependencies, keywords, etc., but other props will be overwritten."
|
|
5
5
|
],
|
|
6
6
|
"name": "@lwc/style-compiler",
|
|
7
|
-
"version": "
|
|
7
|
+
"version": "9.0.0",
|
|
8
8
|
"description": "Transform style sheet to be consumed by the LWC engine",
|
|
9
9
|
"keywords": [
|
|
10
10
|
"lwc"
|
|
@@ -19,13 +19,17 @@
|
|
|
19
19
|
"url": "https://github.com/salesforce/lwc/issues"
|
|
20
20
|
},
|
|
21
21
|
"license": "MIT",
|
|
22
|
+
"type": "module",
|
|
22
23
|
"publishConfig": {
|
|
23
24
|
"access": "public"
|
|
24
25
|
},
|
|
26
|
+
"engines": {
|
|
27
|
+
"node": ">=16.6.0"
|
|
28
|
+
},
|
|
25
29
|
"volta": {
|
|
26
30
|
"extends": "../../../package.json"
|
|
27
31
|
},
|
|
28
|
-
"main": "dist/index.
|
|
32
|
+
"main": "dist/index.js",
|
|
29
33
|
"module": "dist/index.js",
|
|
30
34
|
"types": "dist/index.d.ts",
|
|
31
35
|
"files": [
|
|
@@ -46,7 +50,7 @@
|
|
|
46
50
|
}
|
|
47
51
|
},
|
|
48
52
|
"dependencies": {
|
|
49
|
-
"@lwc/shared": "
|
|
53
|
+
"@lwc/shared": "9.0.0",
|
|
50
54
|
"postcss": "~8.5.6",
|
|
51
55
|
"postcss-selector-parser": "~7.1.1",
|
|
52
56
|
"postcss-value-parser": "~4.2.0"
|
package/dist/index.cjs.js
DELETED
|
@@ -1,895 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Copyright (c) 2026 Salesforce, Inc.
|
|
3
|
-
*/
|
|
4
|
-
'use strict';
|
|
5
|
-
|
|
6
|
-
Object.defineProperty(exports, '__esModule', { value: true });
|
|
7
|
-
|
|
8
|
-
var postcss = require('postcss');
|
|
9
|
-
var shared = require('@lwc/shared');
|
|
10
|
-
var postCssSelector = require('postcss-selector-parser');
|
|
11
|
-
var valueParser = require('postcss-value-parser');
|
|
12
|
-
|
|
13
|
-
const PLUGIN_NAME = '@lwc/style-compiler';
|
|
14
|
-
const IMPORT_TYPE = 'import';
|
|
15
|
-
function importMessage(id) {
|
|
16
|
-
return {
|
|
17
|
-
plugin: PLUGIN_NAME,
|
|
18
|
-
type: IMPORT_TYPE,
|
|
19
|
-
id,
|
|
20
|
-
};
|
|
21
|
-
}
|
|
22
|
-
function isImportMessage(message) {
|
|
23
|
-
return message.type === IMPORT_TYPE && message.id;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
/*
|
|
27
|
-
* Copyright (c) 2018, salesforce.com, inc.
|
|
28
|
-
* All rights reserved.
|
|
29
|
-
* SPDX-License-Identifier: MIT
|
|
30
|
-
* For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT
|
|
31
|
-
*/
|
|
32
|
-
const HOST_ATTRIBUTE = '__hostAttribute__';
|
|
33
|
-
const SHADOW_ATTRIBUTE = '__shadowAttribute__';
|
|
34
|
-
|
|
35
|
-
/*
|
|
36
|
-
* Copyright (c) 2018, salesforce.com, inc.
|
|
37
|
-
* All rights reserved.
|
|
38
|
-
* SPDX-License-Identifier: MIT
|
|
39
|
-
* For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT
|
|
40
|
-
*/
|
|
41
|
-
const DIR_ATTRIBUTE_NATIVE_LTR = `__dirAttributeNativeLtr__`;
|
|
42
|
-
const DIR_ATTRIBUTE_NATIVE_RTL = `__dirAttributeNativeRtl__`;
|
|
43
|
-
const DIR_ATTRIBUTE_SYNTHETIC_LTR = `__dirAttributeSyntheticLtr__`;
|
|
44
|
-
const DIR_ATTRIBUTE_SYNTHETIC_RTL = `__dirAttributeSyntheticRtl__`;
|
|
45
|
-
|
|
46
|
-
/*
|
|
47
|
-
* Copyright (c) 2018, salesforce.com, inc.
|
|
48
|
-
* All rights reserved.
|
|
49
|
-
* SPDX-License-Identifier: MIT
|
|
50
|
-
* For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT
|
|
51
|
-
*/
|
|
52
|
-
var TokenType;
|
|
53
|
-
(function (TokenType) {
|
|
54
|
-
TokenType["text"] = "text";
|
|
55
|
-
TokenType["expression"] = "expression";
|
|
56
|
-
TokenType["identifier"] = "identifier";
|
|
57
|
-
TokenType["divider"] = "divider";
|
|
58
|
-
})(TokenType || (TokenType = {}));
|
|
59
|
-
// "1400 binary expressions are enough to reach Node.js maximum call stack size"
|
|
60
|
-
// https://github.com/salesforce/lwc/issues/1726
|
|
61
|
-
// The vast majority of stylesheet functions are much less than this, so we can set the limit lower
|
|
62
|
-
// to play it safe.
|
|
63
|
-
const BINARY_EXPRESSION_LIMIT = 100;
|
|
64
|
-
// Javascript identifiers used for the generation of the style module
|
|
65
|
-
const HOST_SELECTOR_IDENTIFIER = 'hostSelector';
|
|
66
|
-
const SHADOW_SELECTOR_IDENTIFIER = 'shadowSelector';
|
|
67
|
-
const SUFFIX_TOKEN_IDENTIFIER = 'suffixToken';
|
|
68
|
-
const USE_ACTUAL_HOST_SELECTOR = 'useActualHostSelector';
|
|
69
|
-
const USE_NATIVE_DIR_PSEUDOCLASS = 'useNativeDirPseudoclass';
|
|
70
|
-
const TOKEN = 'token';
|
|
71
|
-
const STYLESHEET_IDENTIFIER = 'stylesheet';
|
|
72
|
-
function serialize(result, config) {
|
|
73
|
-
const { messages } = result;
|
|
74
|
-
const importedStylesheets = messages.filter(isImportMessage).map((message) => message.id);
|
|
75
|
-
const disableSyntheticShadow = Boolean(config.disableSyntheticShadowSupport);
|
|
76
|
-
const scoped = Boolean(config.scoped);
|
|
77
|
-
let buffer = '';
|
|
78
|
-
for (let i = 0; i < importedStylesheets.length; i++) {
|
|
79
|
-
buffer += `import ${STYLESHEET_IDENTIFIER + i} from "${importedStylesheets[i]}";\n`;
|
|
80
|
-
}
|
|
81
|
-
if (importedStylesheets.length) {
|
|
82
|
-
buffer += '\n';
|
|
83
|
-
}
|
|
84
|
-
const stylesheetList = importedStylesheets.map((_str, i) => `${STYLESHEET_IDENTIFIER + i}`);
|
|
85
|
-
const serializedStyle = serializeCss(result).trim();
|
|
86
|
-
if (serializedStyle) {
|
|
87
|
-
// inline function
|
|
88
|
-
if (disableSyntheticShadow && !scoped) {
|
|
89
|
-
// If synthetic shadow DOM support is disabled and this is not a scoped stylesheet, then the
|
|
90
|
-
// function signature will always be:
|
|
91
|
-
// stylesheet(token = undefined, useActualHostSelector = true, useNativeDirPseudoclass = true)
|
|
92
|
-
// This means that we can just have a function that takes no arguments and returns a string,
|
|
93
|
-
// reducing the bundle size when minified.
|
|
94
|
-
buffer += `function ${STYLESHEET_IDENTIFIER}() {\n`;
|
|
95
|
-
buffer += ` var ${TOKEN};\n`; // undefined
|
|
96
|
-
buffer += ` var ${USE_ACTUAL_HOST_SELECTOR} = true;\n`;
|
|
97
|
-
buffer += ` var ${USE_NATIVE_DIR_PSEUDOCLASS} = true;\n`;
|
|
98
|
-
}
|
|
99
|
-
else {
|
|
100
|
-
buffer += `function ${STYLESHEET_IDENTIFIER}(${TOKEN}, ${USE_ACTUAL_HOST_SELECTOR}, ${USE_NATIVE_DIR_PSEUDOCLASS}) {\n`;
|
|
101
|
-
}
|
|
102
|
-
// For scoped stylesheets, we use classes, but for synthetic shadow DOM, we use attributes
|
|
103
|
-
if (scoped) {
|
|
104
|
-
buffer += ` var ${SHADOW_SELECTOR_IDENTIFIER} = ${TOKEN} ? ("." + ${TOKEN}) : "";\n`;
|
|
105
|
-
buffer += ` var ${HOST_SELECTOR_IDENTIFIER} = ${TOKEN} ? ("." + ${TOKEN} + "-host") : "";\n`;
|
|
106
|
-
}
|
|
107
|
-
else {
|
|
108
|
-
buffer += ` var ${SHADOW_SELECTOR_IDENTIFIER} = ${TOKEN} ? ("[" + ${TOKEN} + "]") : "";\n`;
|
|
109
|
-
buffer += ` var ${HOST_SELECTOR_IDENTIFIER} = ${TOKEN} ? ("[" + ${TOKEN} + "-host]") : "";\n`;
|
|
110
|
-
}
|
|
111
|
-
// Used for keyframes
|
|
112
|
-
buffer += ` var ${SUFFIX_TOKEN_IDENTIFIER} = ${TOKEN} ? ("-" + ${TOKEN}) : "";\n`;
|
|
113
|
-
buffer += ` return ${serializedStyle};\n`;
|
|
114
|
-
buffer += ` /*${shared.LWC_VERSION_COMMENT}*/\n`;
|
|
115
|
-
buffer += `}\n`;
|
|
116
|
-
if (scoped) {
|
|
117
|
-
// Mark the stylesheet as scoped so that we can distinguish it later at runtime
|
|
118
|
-
buffer += `${STYLESHEET_IDENTIFIER}.${shared.KEY__SCOPED_CSS} = true;\n`;
|
|
119
|
-
}
|
|
120
|
-
if (disableSyntheticShadow) {
|
|
121
|
-
// Mark the stylesheet as $nativeOnly$ so it can be ignored in synthetic shadow mode
|
|
122
|
-
buffer += `${STYLESHEET_IDENTIFIER}.${shared.KEY__NATIVE_ONLY_CSS} = true;\n`;
|
|
123
|
-
}
|
|
124
|
-
// add import at the end
|
|
125
|
-
stylesheetList.push(STYLESHEET_IDENTIFIER);
|
|
126
|
-
}
|
|
127
|
-
// exports
|
|
128
|
-
if (stylesheetList.length) {
|
|
129
|
-
buffer += `export default [${stylesheetList.join(', ')}];`;
|
|
130
|
-
}
|
|
131
|
-
else {
|
|
132
|
-
buffer += `export default undefined;`;
|
|
133
|
-
}
|
|
134
|
-
return buffer;
|
|
135
|
-
}
|
|
136
|
-
function reduceTokens(tokens) {
|
|
137
|
-
return [{ type: TokenType.text, value: '' }, ...tokens, { type: TokenType.text, value: '' }]
|
|
138
|
-
.reduce((acc, token) => {
|
|
139
|
-
const prev = acc[acc.length - 1];
|
|
140
|
-
if (token.type === TokenType.text && prev && prev.type === TokenType.text) {
|
|
141
|
-
// clone the previous token to avoid mutating it in-place
|
|
142
|
-
acc[acc.length - 1] = {
|
|
143
|
-
type: prev.type,
|
|
144
|
-
value: prev.value + token.value,
|
|
145
|
-
};
|
|
146
|
-
return acc;
|
|
147
|
-
}
|
|
148
|
-
else {
|
|
149
|
-
return [...acc, token];
|
|
150
|
-
}
|
|
151
|
-
}, [])
|
|
152
|
-
.filter((t) => t.value !== '');
|
|
153
|
-
}
|
|
154
|
-
function normalizeString(str) {
|
|
155
|
-
return str.replace(/(\r\n\t|\n|\r\t)/gm, '').trim();
|
|
156
|
-
}
|
|
157
|
-
function generateExpressionFromTokens(tokens) {
|
|
158
|
-
const serializedTokens = reduceTokens(tokens).map(({ type, value }) => {
|
|
159
|
-
switch (type) {
|
|
160
|
-
// Note that we don't expect to get a TokenType.divider here. It should be converted into an
|
|
161
|
-
// expression elsewhere.
|
|
162
|
-
case TokenType.text:
|
|
163
|
-
return JSON.stringify(value);
|
|
164
|
-
// Expressions may be concatenated with " + ", in which case we must remove ambiguity
|
|
165
|
-
case TokenType.expression:
|
|
166
|
-
return `(${value})`;
|
|
167
|
-
default:
|
|
168
|
-
return value;
|
|
169
|
-
}
|
|
170
|
-
});
|
|
171
|
-
if (serializedTokens.length === 0) {
|
|
172
|
-
return '';
|
|
173
|
-
}
|
|
174
|
-
else if (serializedTokens.length === 1) {
|
|
175
|
-
return serializedTokens[0];
|
|
176
|
-
}
|
|
177
|
-
else if (serializedTokens.length < BINARY_EXPRESSION_LIMIT) {
|
|
178
|
-
return serializedTokens.join(' + ');
|
|
179
|
-
}
|
|
180
|
-
else {
|
|
181
|
-
// #1726 Using Array.prototype.join() instead of a standard "+" operator to concatenate the
|
|
182
|
-
// string to avoid running into a maximum call stack error when the stylesheet is parsed
|
|
183
|
-
// again by the bundler.
|
|
184
|
-
return `[${serializedTokens.join(', ')}].join('')`;
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
function areTokensEqual(left, right) {
|
|
188
|
-
return left.type === right.type && left.value === right.value;
|
|
189
|
-
}
|
|
190
|
-
function calculateNumDuplicatedTokens(left, right) {
|
|
191
|
-
// Walk backwards until we find a token that is different between left and right
|
|
192
|
-
let i = 0;
|
|
193
|
-
for (; i < left.length && i < right.length; i++) {
|
|
194
|
-
const currentLeft = left[left.length - 1 - i];
|
|
195
|
-
const currentRight = right[right.length - 1 - i];
|
|
196
|
-
if (!areTokensEqual(currentLeft, currentRight)) {
|
|
197
|
-
break;
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
return i;
|
|
201
|
-
}
|
|
202
|
-
// For `:host` selectors, the token lists for native vs synthetic will be identical at the end of
|
|
203
|
-
// each list. So as an optimization, we can de-dup these tokens.
|
|
204
|
-
// See: https://github.com/salesforce/lwc/issues/3224#issuecomment-1353520052
|
|
205
|
-
function deduplicateHostTokens(nativeHostTokens, syntheticHostTokens) {
|
|
206
|
-
const numDuplicatedTokens = calculateNumDuplicatedTokens(nativeHostTokens, syntheticHostTokens);
|
|
207
|
-
const numUniqueNativeTokens = nativeHostTokens.length - numDuplicatedTokens;
|
|
208
|
-
const numUniqueSyntheticTokens = syntheticHostTokens.length - numDuplicatedTokens;
|
|
209
|
-
const uniqueNativeTokens = nativeHostTokens.slice(0, numUniqueNativeTokens);
|
|
210
|
-
const uniqueSyntheticTokens = syntheticHostTokens.slice(0, numUniqueSyntheticTokens);
|
|
211
|
-
const nativeExpression = generateExpressionFromTokens(uniqueNativeTokens);
|
|
212
|
-
const syntheticExpression = generateExpressionFromTokens(uniqueSyntheticTokens);
|
|
213
|
-
// Generate a conditional ternary to switch between native vs synthetic for the unique tokens
|
|
214
|
-
const conditionalToken = {
|
|
215
|
-
type: TokenType.expression,
|
|
216
|
-
value: `(${USE_ACTUAL_HOST_SELECTOR} ? ${nativeExpression} : ${syntheticExpression})`,
|
|
217
|
-
};
|
|
218
|
-
return [
|
|
219
|
-
conditionalToken,
|
|
220
|
-
// The remaining tokens are the same between native and synthetic
|
|
221
|
-
...syntheticHostTokens.slice(numUniqueSyntheticTokens),
|
|
222
|
-
];
|
|
223
|
-
}
|
|
224
|
-
function serializeCss(result) {
|
|
225
|
-
const tokens = [];
|
|
226
|
-
let currentRuleTokens = [];
|
|
227
|
-
let nativeHostTokens;
|
|
228
|
-
// Walk though all nodes in the CSS...
|
|
229
|
-
postcss.stringify(result.root, (part, node, nodePosition) => {
|
|
230
|
-
// When consuming the beginning of a rule, first we tokenize the selector
|
|
231
|
-
if (node && node.type === 'rule' && nodePosition === 'start') {
|
|
232
|
-
currentRuleTokens.push(...tokenizeCss(normalizeString(part)));
|
|
233
|
-
// When consuming the end of a rule we normalize it and produce a new one
|
|
234
|
-
}
|
|
235
|
-
else if (node && node.type === 'rule' && nodePosition === 'end') {
|
|
236
|
-
currentRuleTokens.push({ type: TokenType.text, value: part });
|
|
237
|
-
// If we are in synthetic shadow or scoped light DOM, we don't want to have native :host selectors
|
|
238
|
-
// Note that postcss-lwc-plugin should ensure that _isNativeHost appears before _isSyntheticHost
|
|
239
|
-
if (node._isNativeHost) {
|
|
240
|
-
// Save native tokens so in the next rule we can apply a conditional ternary
|
|
241
|
-
nativeHostTokens = [...currentRuleTokens];
|
|
242
|
-
}
|
|
243
|
-
else if (node._isSyntheticHost) {
|
|
244
|
-
/* istanbul ignore if */
|
|
245
|
-
if (!nativeHostTokens) {
|
|
246
|
-
throw new Error('Unexpected host rules ordering');
|
|
247
|
-
}
|
|
248
|
-
const hostTokens = deduplicateHostTokens(nativeHostTokens, currentRuleTokens);
|
|
249
|
-
tokens.push(...hostTokens);
|
|
250
|
-
nativeHostTokens = undefined;
|
|
251
|
-
}
|
|
252
|
-
else {
|
|
253
|
-
/* istanbul ignore if */
|
|
254
|
-
if (nativeHostTokens) {
|
|
255
|
-
throw new Error('Unexpected host rules ordering');
|
|
256
|
-
}
|
|
257
|
-
tokens.push(...currentRuleTokens);
|
|
258
|
-
}
|
|
259
|
-
// Reset rule
|
|
260
|
-
currentRuleTokens = [];
|
|
261
|
-
// When inside a declaration, tokenize it and push it to the current token list
|
|
262
|
-
}
|
|
263
|
-
else if (node && node.type === 'decl') {
|
|
264
|
-
currentRuleTokens.push(...tokenizeCss(part));
|
|
265
|
-
}
|
|
266
|
-
else if (node && node.type === 'atrule') {
|
|
267
|
-
// Certain atrules have declaration associated with for example @font-face. We need to add the rules tokens
|
|
268
|
-
// when it's the case.
|
|
269
|
-
if (currentRuleTokens.length) {
|
|
270
|
-
tokens.push(...currentRuleTokens);
|
|
271
|
-
currentRuleTokens = [];
|
|
272
|
-
}
|
|
273
|
-
tokens.push(...tokenizeCss(normalizeString(part)));
|
|
274
|
-
}
|
|
275
|
-
else {
|
|
276
|
-
// When inside anything else but a comment just push it
|
|
277
|
-
if (!node || node.type !== 'comment') {
|
|
278
|
-
currentRuleTokens.push({ type: TokenType.text, value: normalizeString(part) });
|
|
279
|
-
}
|
|
280
|
-
}
|
|
281
|
-
});
|
|
282
|
-
return generateExpressionFromTokens(tokens);
|
|
283
|
-
}
|
|
284
|
-
// Given any CSS string, replace the scope tokens from the CSS with code to properly
|
|
285
|
-
// replace it in the stylesheet function.
|
|
286
|
-
function tokenizeCss(data) {
|
|
287
|
-
data = data.replace(/( {2,})/gm, ' '); // remove when there are more than two spaces
|
|
288
|
-
const tokens = [];
|
|
289
|
-
const attributes = [
|
|
290
|
-
SHADOW_ATTRIBUTE,
|
|
291
|
-
HOST_ATTRIBUTE,
|
|
292
|
-
DIR_ATTRIBUTE_NATIVE_LTR,
|
|
293
|
-
DIR_ATTRIBUTE_NATIVE_RTL,
|
|
294
|
-
DIR_ATTRIBUTE_SYNTHETIC_LTR,
|
|
295
|
-
DIR_ATTRIBUTE_SYNTHETIC_RTL,
|
|
296
|
-
];
|
|
297
|
-
const regex = new RegExp(`[[-](${attributes.join('|')})]?`, 'g');
|
|
298
|
-
let lastIndex = 0;
|
|
299
|
-
for (const match of data.matchAll(regex)) {
|
|
300
|
-
const index = match.index;
|
|
301
|
-
const [matchString, substring] = match;
|
|
302
|
-
if (index > lastIndex) {
|
|
303
|
-
tokens.push({ type: TokenType.text, value: data.substring(lastIndex, index) });
|
|
304
|
-
}
|
|
305
|
-
const identifier = substring === SHADOW_ATTRIBUTE ? SHADOW_SELECTOR_IDENTIFIER : HOST_SELECTOR_IDENTIFIER;
|
|
306
|
-
if (matchString.startsWith('[')) {
|
|
307
|
-
if (substring === SHADOW_ATTRIBUTE || substring === HOST_ATTRIBUTE) {
|
|
308
|
-
// attribute in a selector, e.g. `[__shadowAttribute__]` or `[__hostAttribute__]`
|
|
309
|
-
tokens.push({
|
|
310
|
-
type: TokenType.identifier,
|
|
311
|
-
value: identifier,
|
|
312
|
-
});
|
|
313
|
-
}
|
|
314
|
-
else {
|
|
315
|
-
// :dir pseudoclass placeholder, e.g. `[__dirAttributeNativeLtr__]` or `[__dirAttributeSyntheticRtl__]`
|
|
316
|
-
const native = substring === DIR_ATTRIBUTE_NATIVE_LTR ||
|
|
317
|
-
substring === DIR_ATTRIBUTE_NATIVE_RTL;
|
|
318
|
-
const dirValue = substring === DIR_ATTRIBUTE_NATIVE_LTR ||
|
|
319
|
-
substring === DIR_ATTRIBUTE_SYNTHETIC_LTR
|
|
320
|
-
? 'ltr'
|
|
321
|
-
: 'rtl';
|
|
322
|
-
tokens.push({
|
|
323
|
-
type: TokenType.expression,
|
|
324
|
-
// use the native :dir() pseudoclass for native shadow, the [dir] attribute otherwise
|
|
325
|
-
value: native
|
|
326
|
-
? `${USE_NATIVE_DIR_PSEUDOCLASS} ? ':dir(${dirValue})' : ''`
|
|
327
|
-
: `${USE_NATIVE_DIR_PSEUDOCLASS} ? '' : '[dir="${dirValue}"]'`,
|
|
328
|
-
});
|
|
329
|
-
}
|
|
330
|
-
}
|
|
331
|
-
else {
|
|
332
|
-
// suffix for an at-rule, e.g. `@keyframes spin-__shadowAttribute__`
|
|
333
|
-
tokens.push({
|
|
334
|
-
type: TokenType.identifier,
|
|
335
|
-
// Suffix the keyframe (i.e. "-" plus the token)
|
|
336
|
-
value: SUFFIX_TOKEN_IDENTIFIER,
|
|
337
|
-
});
|
|
338
|
-
}
|
|
339
|
-
lastIndex = index + matchString.length;
|
|
340
|
-
}
|
|
341
|
-
if (lastIndex < data.length) {
|
|
342
|
-
tokens.push({ type: TokenType.text, value: data.substring(lastIndex, data.length) });
|
|
343
|
-
}
|
|
344
|
-
return tokens;
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
function validateIdSelectors (root, ctx) {
|
|
348
|
-
root.walkIds((node) => {
|
|
349
|
-
ctx.withErrorRecovery(() => {
|
|
350
|
-
const message = `Invalid usage of id selector '#${node.value}'. Try using a class selector or some other selector.`;
|
|
351
|
-
throw root.error(message, {
|
|
352
|
-
index: node.sourceIndex,
|
|
353
|
-
word: node.value,
|
|
354
|
-
});
|
|
355
|
-
});
|
|
356
|
-
});
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
/*
|
|
360
|
-
* Copyright (c) 2018, salesforce.com, inc.
|
|
361
|
-
* All rights reserved.
|
|
362
|
-
* SPDX-License-Identifier: MIT
|
|
363
|
-
* For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT
|
|
364
|
-
*/
|
|
365
|
-
function process$1(root, result, isScoped, ctx) {
|
|
366
|
-
root.walkAtRules('import', (node) => {
|
|
367
|
-
ctx.withErrorRecovery(() => {
|
|
368
|
-
if (isScoped) {
|
|
369
|
-
throw node.error(`Invalid import statement, imports are not allowed in *.scoped.css files.`);
|
|
370
|
-
}
|
|
371
|
-
// Ensure @import are at the top of the file
|
|
372
|
-
let prev = node.prev();
|
|
373
|
-
while (prev) {
|
|
374
|
-
if (prev.type === 'comment' || (prev.type === 'atrule' && prev.name === 'import')) {
|
|
375
|
-
prev = prev.prev();
|
|
376
|
-
}
|
|
377
|
-
else {
|
|
378
|
-
throw prev.error('@import must precede all other statements');
|
|
379
|
-
}
|
|
380
|
-
}
|
|
381
|
-
const { nodes: params } = valueParser(node.params);
|
|
382
|
-
// Ensure import match the following syntax:
|
|
383
|
-
// @import "foo";
|
|
384
|
-
// @import "./foo.css";
|
|
385
|
-
if (!params.length || params[0].type !== 'string' || !params[0].value) {
|
|
386
|
-
throw node.error(`Invalid import statement, unable to find imported module.`);
|
|
387
|
-
}
|
|
388
|
-
if (params.length > 1) {
|
|
389
|
-
throw node.error(`Invalid import statement, import statement only support a single parameter.`);
|
|
390
|
-
}
|
|
391
|
-
// Add the imported to results messages
|
|
392
|
-
const message = importMessage(params[0].value);
|
|
393
|
-
result.messages.push(message);
|
|
394
|
-
// Remove the import from the generated css
|
|
395
|
-
node.remove();
|
|
396
|
-
});
|
|
397
|
-
});
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
/*
|
|
401
|
-
* Copyright (c) 2018, salesforce.com, inc.
|
|
402
|
-
* All rights reserved.
|
|
403
|
-
* SPDX-License-Identifier: MIT
|
|
404
|
-
* For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT
|
|
405
|
-
*/
|
|
406
|
-
function isDirPseudoClass(node) {
|
|
407
|
-
return postCssSelector.isPseudoClass(node) && node.value === ':dir';
|
|
408
|
-
}
|
|
409
|
-
|
|
410
|
-
function findNode(container, predicate) {
|
|
411
|
-
return container && container.nodes && container.nodes.find(predicate);
|
|
412
|
-
}
|
|
413
|
-
function replaceNodeWith(oldNode, ...newNodes) {
|
|
414
|
-
if (newNodes.length) {
|
|
415
|
-
const { parent } = oldNode;
|
|
416
|
-
if (!parent) {
|
|
417
|
-
throw new Error(`Impossible to replace root node.`);
|
|
418
|
-
}
|
|
419
|
-
newNodes.forEach((node) => {
|
|
420
|
-
parent.insertBefore(oldNode, node);
|
|
421
|
-
});
|
|
422
|
-
oldNode.remove();
|
|
423
|
-
}
|
|
424
|
-
}
|
|
425
|
-
function trimNodeWhitespaces(node) {
|
|
426
|
-
if (node && node.spaces) {
|
|
427
|
-
node.spaces.before = '';
|
|
428
|
-
node.spaces.after = '';
|
|
429
|
-
}
|
|
430
|
-
}
|
|
431
|
-
|
|
432
|
-
const DEPRECATED_SELECTORS = new Set(['/deep/', '::shadow', '>>>']);
|
|
433
|
-
const UNSUPPORTED_SELECTORS = new Set([':root', ':host-context']);
|
|
434
|
-
const TEMPLATE_DIRECTIVES = [/^key$/, /^lwc:*/, /^if:*/, /^for:*/, /^iterator:*/];
|
|
435
|
-
function validateSelectors(root, native, ctx) {
|
|
436
|
-
root.walk((node) => {
|
|
437
|
-
ctx.withErrorRecovery(() => {
|
|
438
|
-
const { value, sourceIndex } = node;
|
|
439
|
-
if (value) {
|
|
440
|
-
// Ensure the selector doesn't use a deprecated CSS selector.
|
|
441
|
-
if (DEPRECATED_SELECTORS.has(value)) {
|
|
442
|
-
throw root.error(`Invalid usage of deprecated selector "${value}".`, {
|
|
443
|
-
index: sourceIndex,
|
|
444
|
-
word: value,
|
|
445
|
-
});
|
|
446
|
-
}
|
|
447
|
-
// Ensure the selector doesn't use an unsupported selector.
|
|
448
|
-
if (!native && UNSUPPORTED_SELECTORS.has(value)) {
|
|
449
|
-
throw root.error(`Invalid usage of unsupported selector "${value}". This selector is only supported in non-scoped CSS where the \`disableSyntheticShadowSupport\` flag is set to true.`, {
|
|
450
|
-
index: sourceIndex,
|
|
451
|
-
word: value,
|
|
452
|
-
});
|
|
453
|
-
}
|
|
454
|
-
}
|
|
455
|
-
});
|
|
456
|
-
});
|
|
457
|
-
}
|
|
458
|
-
function validateAttribute(root, ctx) {
|
|
459
|
-
root.walkAttributes((node) => {
|
|
460
|
-
ctx.withErrorRecovery(() => {
|
|
461
|
-
const { attribute: attributeName, sourceIndex } = node;
|
|
462
|
-
const isTemplateDirective = TEMPLATE_DIRECTIVES.some((directive) => {
|
|
463
|
-
return directive.test(attributeName);
|
|
464
|
-
});
|
|
465
|
-
if (isTemplateDirective) {
|
|
466
|
-
const message = [
|
|
467
|
-
`Invalid usage of attribute selector "${attributeName}". `,
|
|
468
|
-
`"${attributeName}" is a template directive and therefore not supported in css rules.`,
|
|
469
|
-
];
|
|
470
|
-
throw root.error(message.join(''), {
|
|
471
|
-
index: sourceIndex,
|
|
472
|
-
word: attributeName,
|
|
473
|
-
});
|
|
474
|
-
}
|
|
475
|
-
});
|
|
476
|
-
});
|
|
477
|
-
}
|
|
478
|
-
function validate(root, native, ctx) {
|
|
479
|
-
validateSelectors(root, native, ctx);
|
|
480
|
-
validateAttribute(root, ctx);
|
|
481
|
-
}
|
|
482
|
-
|
|
483
|
-
/*
|
|
484
|
-
* Copyright (c) 2018, salesforce.com, inc.
|
|
485
|
-
* All rights reserved.
|
|
486
|
-
* SPDX-License-Identifier: MIT
|
|
487
|
-
* For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT
|
|
488
|
-
*/
|
|
489
|
-
function isHostPseudoClass(node) {
|
|
490
|
-
return postCssSelector.isPseudoClass(node) && node.value === ':host';
|
|
491
|
-
}
|
|
492
|
-
/**
|
|
493
|
-
* Add scoping attributes to all the matching selectors:
|
|
494
|
-
* - h1 -> h1[x-foo_tmpl]
|
|
495
|
-
* - p a -> p[x-foo_tmpl] a[x-foo_tmpl]
|
|
496
|
-
* @param selector
|
|
497
|
-
*/
|
|
498
|
-
function scopeSelector(selector) {
|
|
499
|
-
const compoundSelectors = [[]];
|
|
500
|
-
// Split the selector per compound selector. Compound selectors are interleaved with combinator nodes.
|
|
501
|
-
// https://drafts.csswg.org/selectors-4/#typedef-complex-selector
|
|
502
|
-
selector.each((node) => {
|
|
503
|
-
if (postCssSelector.isCombinator(node)) {
|
|
504
|
-
compoundSelectors.push([]);
|
|
505
|
-
}
|
|
506
|
-
else {
|
|
507
|
-
const current = compoundSelectors[compoundSelectors.length - 1];
|
|
508
|
-
current.push(node);
|
|
509
|
-
}
|
|
510
|
-
});
|
|
511
|
-
for (const compoundSelector of compoundSelectors) {
|
|
512
|
-
// Compound selectors with only a single :dir pseudo class should be scoped, the dir pseudo
|
|
513
|
-
// class transform will take care of transforming it properly.
|
|
514
|
-
const containsSingleDirSelector = compoundSelector.length === 1 && isDirPseudoClass(compoundSelector[0]);
|
|
515
|
-
// Compound selectors containing :host have a special treatment and should not be scoped
|
|
516
|
-
// like the rest of the complex selectors.
|
|
517
|
-
const containsHost = compoundSelector.some(isHostPseudoClass);
|
|
518
|
-
if (!containsSingleDirSelector && !containsHost) {
|
|
519
|
-
let nodeToScope;
|
|
520
|
-
// In each compound selector we need to locate the last selector to scope.
|
|
521
|
-
for (const node of compoundSelector) {
|
|
522
|
-
if (!postCssSelector.isPseudoElement(node)) {
|
|
523
|
-
nodeToScope = node;
|
|
524
|
-
}
|
|
525
|
-
}
|
|
526
|
-
const shadowAttribute = postCssSelector.attribute({
|
|
527
|
-
attribute: SHADOW_ATTRIBUTE,
|
|
528
|
-
value: undefined,
|
|
529
|
-
raws: {},
|
|
530
|
-
});
|
|
531
|
-
if (nodeToScope) {
|
|
532
|
-
// Add the scoping attribute right after the node scope
|
|
533
|
-
selector.insertAfter(nodeToScope, shadowAttribute);
|
|
534
|
-
}
|
|
535
|
-
else {
|
|
536
|
-
// Add the scoping token in the first position of the compound selector as a fallback
|
|
537
|
-
// when there is no node to scope. For example: ::after {}
|
|
538
|
-
const [firstSelector] = compoundSelector;
|
|
539
|
-
selector.insertBefore(firstSelector, shadowAttribute);
|
|
540
|
-
// Move any whitespace before the selector (e.g. " ::after") to before the shadow attribute,
|
|
541
|
-
// so that the resulting selector is correct (e.g. " [attr]::after", not "[attr] ::after")
|
|
542
|
-
if (firstSelector && firstSelector.spaces.before) {
|
|
543
|
-
shadowAttribute.spaces.before = firstSelector.spaces.before;
|
|
544
|
-
const clonedFirstSelector = firstSelector.clone({});
|
|
545
|
-
clonedFirstSelector.spaces.before = '';
|
|
546
|
-
firstSelector.replaceWith(clonedFirstSelector);
|
|
547
|
-
}
|
|
548
|
-
}
|
|
549
|
-
}
|
|
550
|
-
}
|
|
551
|
-
}
|
|
552
|
-
/**
|
|
553
|
-
* Mark the :host selector with a placeholder. If the selector has a list of
|
|
554
|
-
* contextual selector it will generate a rule for each of them.
|
|
555
|
-
* - `:host -> [x-foo_tmpl-host]`
|
|
556
|
-
* - `:host(.foo, .bar) -> [x-foo_tmpl-host].foo, [x-foo_tmpl-host].bar`
|
|
557
|
-
* @param selector
|
|
558
|
-
*/
|
|
559
|
-
function transformHost(selector) {
|
|
560
|
-
// Locate the first :host pseudo-class
|
|
561
|
-
const hostNode = findNode(selector, isHostPseudoClass);
|
|
562
|
-
if (hostNode) {
|
|
563
|
-
// Store the original location of the :host in the selector
|
|
564
|
-
const hostIndex = selector.index(hostNode);
|
|
565
|
-
// Swap the :host pseudo-class with the host scoping token
|
|
566
|
-
const hostAttribute = postCssSelector.attribute({
|
|
567
|
-
attribute: HOST_ATTRIBUTE,
|
|
568
|
-
value: undefined,
|
|
569
|
-
raws: {},
|
|
570
|
-
});
|
|
571
|
-
hostNode.replaceWith(hostAttribute);
|
|
572
|
-
// Generate a unique contextualized version of the selector for each selector pass as argument
|
|
573
|
-
// to the :host
|
|
574
|
-
const contextualSelectors = hostNode.nodes.map((contextSelectors) => {
|
|
575
|
-
const clonedSelector = selector.clone({});
|
|
576
|
-
const clonedHostNode = clonedSelector.at(hostIndex);
|
|
577
|
-
// Add to the compound selector previously containing the :host pseudo class
|
|
578
|
-
// the contextual selectors.
|
|
579
|
-
contextSelectors.each((node) => {
|
|
580
|
-
trimNodeWhitespaces(node);
|
|
581
|
-
clonedSelector.insertAfter(clonedHostNode, node);
|
|
582
|
-
});
|
|
583
|
-
return clonedSelector;
|
|
584
|
-
});
|
|
585
|
-
// Replace the current selector with the different variants
|
|
586
|
-
replaceNodeWith(selector, ...contextualSelectors);
|
|
587
|
-
}
|
|
588
|
-
}
|
|
589
|
-
function transformSelector(root, transformConfig, ctx) {
|
|
590
|
-
validate(root, transformConfig.disableSyntheticShadowSupport && !transformConfig.scoped, ctx);
|
|
591
|
-
root.each(scopeSelector);
|
|
592
|
-
if (transformConfig.transformHost) {
|
|
593
|
-
root.each(transformHost);
|
|
594
|
-
}
|
|
595
|
-
}
|
|
596
|
-
|
|
597
|
-
/*
|
|
598
|
-
* Copyright (c) 2018, salesforce.com, inc.
|
|
599
|
-
* All rights reserved.
|
|
600
|
-
* SPDX-License-Identifier: MIT
|
|
601
|
-
* For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT
|
|
602
|
-
*/
|
|
603
|
-
function isValidDirValue(value) {
|
|
604
|
-
return value === 'ltr' || value === 'rtl';
|
|
605
|
-
}
|
|
606
|
-
function transformDirPseudoClass (root, ctx) {
|
|
607
|
-
root.nodes.forEach((selector) => {
|
|
608
|
-
selector.nodes.forEach((node) => {
|
|
609
|
-
ctx.withErrorRecovery(() => {
|
|
610
|
-
if (!isDirPseudoClass(node)) {
|
|
611
|
-
return;
|
|
612
|
-
}
|
|
613
|
-
const value = node.nodes.toString().trim();
|
|
614
|
-
if (!isValidDirValue(value)) {
|
|
615
|
-
throw root.error(`:dir() pseudo class expects "ltr" or "rtl" for value, but received "${value}".`, {
|
|
616
|
-
index: node.sourceIndex,
|
|
617
|
-
word: node.value,
|
|
618
|
-
});
|
|
619
|
-
}
|
|
620
|
-
// Set placeholders for `:dir()` so we can keep it for native shadow and
|
|
621
|
-
// replace it with a polyfill for synthetic shadow.
|
|
622
|
-
//
|
|
623
|
-
// Native: `:dir(ltr)`
|
|
624
|
-
// Synthetic: `[dir="ltr"]`
|
|
625
|
-
//
|
|
626
|
-
// The placeholders look like this: `[__dirAttributeNativeLtr__]`
|
|
627
|
-
// The attribute has no value because it's simpler during serialization, and there
|
|
628
|
-
// are only two valid values: "ltr" and "rtl".
|
|
629
|
-
//
|
|
630
|
-
// Now consider a more complex selector: `.foo:dir(ltr):not(.bar)`.
|
|
631
|
-
// For native shadow, we need to leave it as-is. Whereas for synthetic shadow, we need
|
|
632
|
-
// to convert it to: `[dir="ltr"] .foo:not(.bar)`.
|
|
633
|
-
// I.e. we need to use a descendant selector (' ' combinator) relying on a `dir`
|
|
634
|
-
// attribute added to the host element. So we need two placeholders:
|
|
635
|
-
// `<synthetic_placeholder> .foo<native_placeholder>:not(.bar)`
|
|
636
|
-
const nativeAttribute = postCssSelector.attribute({
|
|
637
|
-
attribute: value === 'ltr' ? DIR_ATTRIBUTE_NATIVE_LTR : DIR_ATTRIBUTE_NATIVE_RTL,
|
|
638
|
-
value: undefined,
|
|
639
|
-
raws: {},
|
|
640
|
-
});
|
|
641
|
-
const syntheticAttribute = postCssSelector.attribute({
|
|
642
|
-
attribute: value === 'ltr' ? DIR_ATTRIBUTE_SYNTHETIC_LTR : DIR_ATTRIBUTE_SYNTHETIC_RTL,
|
|
643
|
-
value: undefined,
|
|
644
|
-
raws: {},
|
|
645
|
-
});
|
|
646
|
-
node.replaceWith(nativeAttribute);
|
|
647
|
-
// If the selector is not empty and if the first node in the selector is not already a
|
|
648
|
-
// " " combinator, we need to use the descendant selector format
|
|
649
|
-
const shouldAddDescendantCombinator = selector.first && !postCssSelector.isCombinator(selector.first) && selector.first.value !== ' ';
|
|
650
|
-
if (shouldAddDescendantCombinator) {
|
|
651
|
-
selector.insertBefore(selector.first, postCssSelector.combinator({
|
|
652
|
-
value: ' ',
|
|
653
|
-
}));
|
|
654
|
-
// Add the [dir] attribute in front of the " " combinator, i.e. as an ancestor
|
|
655
|
-
selector.insertBefore(selector.first, syntheticAttribute);
|
|
656
|
-
}
|
|
657
|
-
else {
|
|
658
|
-
// Otherwise there's no need for the descendant selector, so we can skip adding the
|
|
659
|
-
// space combinator and just put the synthetic placeholder next to the native one
|
|
660
|
-
selector.insertBefore(nativeAttribute, syntheticAttribute);
|
|
661
|
-
}
|
|
662
|
-
});
|
|
663
|
-
});
|
|
664
|
-
});
|
|
665
|
-
}
|
|
666
|
-
|
|
667
|
-
/*
|
|
668
|
-
* Copyright (c) 2018, salesforce.com, inc.
|
|
669
|
-
* All rights reserved.
|
|
670
|
-
* SPDX-License-Identifier: MIT
|
|
671
|
-
* For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT
|
|
672
|
-
*/
|
|
673
|
-
// Subset of prefixes for animation-related names that we expect people might be using.
|
|
674
|
-
// The most important is -webkit, which is actually part of the spec now. All -webkit prefixes
|
|
675
|
-
// are listed here: https://developer.mozilla.org/en-US/docs/Web/CSS/Webkit_Extensions
|
|
676
|
-
// -moz is also still supported as of Firefox 95.
|
|
677
|
-
// We could probably get away with just doing -webkit and -moz (since -ms never seems
|
|
678
|
-
// to have existed for keyframes/animations, and Opera has used Blink since 2013), but
|
|
679
|
-
// covering all the popular ones will at least make the compiled code more consistent
|
|
680
|
-
// for developers who are using all the variants.
|
|
681
|
-
// List based on a subset from https://github.com/wooorm/vendors/blob/2f489ad/index.js
|
|
682
|
-
const VENDOR_PREFIXES = ['moz', 'ms', 'o', 'webkit'];
|
|
683
|
-
// create a list like ['animation', '-webkit-animation', ...]
|
|
684
|
-
function getAllNames(name) {
|
|
685
|
-
return new Set([name, ...VENDOR_PREFIXES.map((prefix) => `-${prefix}-${name}`)]);
|
|
686
|
-
}
|
|
687
|
-
const ANIMATION = getAllNames('animation');
|
|
688
|
-
const ANIMATION_NAME = getAllNames('animation-name');
|
|
689
|
-
function process(root, ctx) {
|
|
690
|
-
const knownNames = new Set();
|
|
691
|
-
root.walkAtRules((atRule) => {
|
|
692
|
-
ctx.withErrorRecovery(() => {
|
|
693
|
-
// Note that @-webkit-keyframes, @-moz-keyframes, etc. are not actually a thing supported
|
|
694
|
-
// in any browser, even though you'll see it on some StackOverflow answers.
|
|
695
|
-
if (atRule.name === 'keyframes') {
|
|
696
|
-
const { params } = atRule;
|
|
697
|
-
knownNames.add(params);
|
|
698
|
-
atRule.params = `${params}-${SHADOW_ATTRIBUTE}`;
|
|
699
|
-
}
|
|
700
|
-
});
|
|
701
|
-
});
|
|
702
|
-
root.walkRules((rule) => {
|
|
703
|
-
rule.walkDecls((decl) => {
|
|
704
|
-
ctx.withErrorRecovery(() => {
|
|
705
|
-
if (ANIMATION.has(decl.prop)) {
|
|
706
|
-
// Use a simple heuristic of breaking up the tokens by whitespace. We could use
|
|
707
|
-
// a dedicated animation prop parser (e.g.
|
|
708
|
-
// https://github.com/hookhookun/parse-animation-shorthand) but it's
|
|
709
|
-
// probably overkill.
|
|
710
|
-
const tokens = decl.value
|
|
711
|
-
.trim()
|
|
712
|
-
.split(/\s+/g)
|
|
713
|
-
.map((token) => knownNames.has(token) ? `${token}-${SHADOW_ATTRIBUTE}` : token);
|
|
714
|
-
decl.value = tokens.join(' ');
|
|
715
|
-
}
|
|
716
|
-
else if (ANIMATION_NAME.has(decl.prop)) {
|
|
717
|
-
if (knownNames.has(decl.value)) {
|
|
718
|
-
decl.value = `${decl.value}-${SHADOW_ATTRIBUTE}`;
|
|
719
|
-
}
|
|
720
|
-
}
|
|
721
|
-
});
|
|
722
|
-
});
|
|
723
|
-
});
|
|
724
|
-
}
|
|
725
|
-
|
|
726
|
-
/*
|
|
727
|
-
* Copyright (c) 2018, salesforce.com, inc.
|
|
728
|
-
* All rights reserved.
|
|
729
|
-
* SPDX-License-Identifier: MIT
|
|
730
|
-
* For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT
|
|
731
|
-
*/
|
|
732
|
-
function shouldTransformSelector(rule) {
|
|
733
|
-
// @keyframe at-rules are special, rules inside are not standard selectors and should not be
|
|
734
|
-
// scoped like any other rules.
|
|
735
|
-
return rule.parent?.type !== 'atrule' || rule.parent.name !== 'keyframes';
|
|
736
|
-
}
|
|
737
|
-
function selectorProcessorFactory(transformConfig, ctx) {
|
|
738
|
-
return postCssSelector((root) => {
|
|
739
|
-
validateIdSelectors(root, ctx);
|
|
740
|
-
transformSelector(root, transformConfig, ctx);
|
|
741
|
-
transformDirPseudoClass(root, ctx);
|
|
742
|
-
});
|
|
743
|
-
}
|
|
744
|
-
function postCssLwcPlugin(options) {
|
|
745
|
-
const { ctx } = options;
|
|
746
|
-
// We need 2 types of selectors processors, since transforming the :host selector make the selector
|
|
747
|
-
// unusable when used in the context of the native shadow and vice-versa.
|
|
748
|
-
// This distinction also applies to light DOM in scoped (synthetic-like) vs unscoped (native-like) mode.
|
|
749
|
-
const nativeShadowSelectorProcessor = selectorProcessorFactory({
|
|
750
|
-
transformHost: false,
|
|
751
|
-
disableSyntheticShadowSupport: options.disableSyntheticShadowSupport,
|
|
752
|
-
scoped: options.scoped,
|
|
753
|
-
}, ctx);
|
|
754
|
-
const syntheticShadowSelectorProcessor = selectorProcessorFactory({
|
|
755
|
-
transformHost: true,
|
|
756
|
-
disableSyntheticShadowSupport: options.disableSyntheticShadowSupport,
|
|
757
|
-
scoped: options.scoped,
|
|
758
|
-
}, ctx);
|
|
759
|
-
return (root, result) => {
|
|
760
|
-
process$1(root, result, options.scoped, ctx);
|
|
761
|
-
process(root, ctx);
|
|
762
|
-
// Wrap rule processing with error recovery
|
|
763
|
-
root.walkRules((rule) => {
|
|
764
|
-
ctx.withErrorRecovery(() => {
|
|
765
|
-
if (!shouldTransformSelector(rule)) {
|
|
766
|
-
return;
|
|
767
|
-
}
|
|
768
|
-
// Let transform the selector with the 2 processors.
|
|
769
|
-
const syntheticSelector = syntheticShadowSelectorProcessor.processSync(rule);
|
|
770
|
-
const nativeSelector = nativeShadowSelectorProcessor.processSync(rule);
|
|
771
|
-
rule.selector = syntheticSelector;
|
|
772
|
-
// If the resulting selector are different it means that the selector use the :host selector. In
|
|
773
|
-
// this case we need to duplicate the CSS rule and assign the other selector.
|
|
774
|
-
if (syntheticSelector !== nativeSelector) {
|
|
775
|
-
// The cloned selector is inserted before the currently processed selector to avoid processing
|
|
776
|
-
// again the cloned selector.
|
|
777
|
-
const currentRule = rule;
|
|
778
|
-
const clonedRule = rule.cloneBefore();
|
|
779
|
-
clonedRule.selector = nativeSelector;
|
|
780
|
-
// Safe a reference to each other
|
|
781
|
-
clonedRule._isNativeHost = true;
|
|
782
|
-
currentRule._isSyntheticHost = true;
|
|
783
|
-
}
|
|
784
|
-
});
|
|
785
|
-
});
|
|
786
|
-
};
|
|
787
|
-
}
|
|
788
|
-
|
|
789
|
-
/*
|
|
790
|
-
* Copyright (c) 2025, Salesforce, Inc.
|
|
791
|
-
* All rights reserved.
|
|
792
|
-
* SPDX-License-Identifier: MIT
|
|
793
|
-
* For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT
|
|
794
|
-
*/
|
|
795
|
-
class StyleCompilerCtx {
|
|
796
|
-
constructor(errorRecoveryMode, filename) {
|
|
797
|
-
this.errors = [];
|
|
798
|
-
this.seenErrorKeys = new Set();
|
|
799
|
-
this.errorRecoveryMode = errorRecoveryMode;
|
|
800
|
-
this.filename = filename;
|
|
801
|
-
}
|
|
802
|
-
/**
|
|
803
|
-
* This method recovers from CSS syntax errors that are encountered when fn is invoked.
|
|
804
|
-
* All other errors are considered compiler errors and can not be recovered from.
|
|
805
|
-
* @param fn method to be invoked.
|
|
806
|
-
*/
|
|
807
|
-
withErrorRecovery(fn) {
|
|
808
|
-
if (!this.errorRecoveryMode) {
|
|
809
|
-
return fn();
|
|
810
|
-
}
|
|
811
|
-
try {
|
|
812
|
-
return fn();
|
|
813
|
-
}
|
|
814
|
-
catch (error) {
|
|
815
|
-
if (error instanceof postcss.CssSyntaxError) {
|
|
816
|
-
if (this.seenErrorKeys.has(error.message)) {
|
|
817
|
-
return;
|
|
818
|
-
}
|
|
819
|
-
this.seenErrorKeys.add(error.message);
|
|
820
|
-
this.errors.push(error);
|
|
821
|
-
}
|
|
822
|
-
else {
|
|
823
|
-
// Non-CSS errors (compiler errors) should still throw
|
|
824
|
-
throw error;
|
|
825
|
-
}
|
|
826
|
-
}
|
|
827
|
-
}
|
|
828
|
-
hasErrors() {
|
|
829
|
-
return this.errors.length > 0;
|
|
830
|
-
}
|
|
831
|
-
}
|
|
832
|
-
|
|
833
|
-
/*
|
|
834
|
-
* Copyright (c) 2024, Salesforce, Inc.
|
|
835
|
-
* All rights reserved.
|
|
836
|
-
* SPDX-License-Identifier: MIT
|
|
837
|
-
* For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT
|
|
838
|
-
*/
|
|
839
|
-
/**
|
|
840
|
-
* Transforms CSS for use with LWC components.
|
|
841
|
-
* @param src Contents of the CSS source file
|
|
842
|
-
* @param id Filename of the CSS source file
|
|
843
|
-
* @param config Transformation options
|
|
844
|
-
* @returns Transformed CSS
|
|
845
|
-
* @example
|
|
846
|
-
* const {transform} = require('@lwc/style-compiler');
|
|
847
|
-
* const source = `
|
|
848
|
-
* :host {
|
|
849
|
-
* opacity: 0.4;
|
|
850
|
-
* }
|
|
851
|
-
* span {
|
|
852
|
-
* text-transform: uppercase;
|
|
853
|
-
* }`;
|
|
854
|
-
* const { code } = transform(source, 'example.css');
|
|
855
|
-
*/
|
|
856
|
-
function transform(src, id, config = {}) {
|
|
857
|
-
if (src === '') {
|
|
858
|
-
return { code: 'export default undefined' };
|
|
859
|
-
}
|
|
860
|
-
const scoped = !!config.scoped;
|
|
861
|
-
shared.getAPIVersionFromNumber(config.apiVersion);
|
|
862
|
-
const disableSyntheticShadowSupport = !!config.disableSyntheticShadowSupport;
|
|
863
|
-
const errorRecoveryMode = !!config.experimentalErrorRecoveryMode;
|
|
864
|
-
// Create error recovery context
|
|
865
|
-
const ctx = new StyleCompilerCtx(errorRecoveryMode, id);
|
|
866
|
-
const plugins = [
|
|
867
|
-
postCssLwcPlugin({
|
|
868
|
-
scoped,
|
|
869
|
-
disableSyntheticShadowSupport,
|
|
870
|
-
ctx,
|
|
871
|
-
}),
|
|
872
|
-
];
|
|
873
|
-
// Wrap PostCSS processing with error recovery for parsing errors
|
|
874
|
-
let result;
|
|
875
|
-
try {
|
|
876
|
-
result = postcss(plugins).process(src, { from: id }).sync();
|
|
877
|
-
}
|
|
878
|
-
catch (error) {
|
|
879
|
-
if (errorRecoveryMode && error instanceof postcss.CssSyntaxError) {
|
|
880
|
-
ctx.errors.push(error);
|
|
881
|
-
throw AggregateError(ctx.errors);
|
|
882
|
-
}
|
|
883
|
-
else {
|
|
884
|
-
throw error;
|
|
885
|
-
}
|
|
886
|
-
}
|
|
887
|
-
if (errorRecoveryMode && ctx.hasErrors()) {
|
|
888
|
-
throw AggregateError(ctx.errors);
|
|
889
|
-
}
|
|
890
|
-
return { code: serialize(result, config) };
|
|
891
|
-
}
|
|
892
|
-
|
|
893
|
-
exports.transform = transform;
|
|
894
|
-
/** version: 8.28.2 */
|
|
895
|
-
//# sourceMappingURL=index.cjs.js.map
|