@lwc/style-compiler
Advanced tools
+16
-14
@@ -6,3 +6,3 @@ /** | ||
| import { LWC_VERSION_COMMENT, KEY__SCOPED_CSS, KEY__NATIVE_ONLY_CSS, getAPIVersionFromNumber } from '@lwc/shared'; | ||
| import postCssSelector, { isPseudoClass, isCombinator, isPseudoElement, attribute, combinator } from 'postcss-selector-parser'; | ||
| import postCssSelectorParser from 'postcss-selector-parser'; | ||
| import valueParser from 'postcss-value-parser'; | ||
@@ -404,3 +404,3 @@ | ||
| function isDirPseudoClass(node) { | ||
| return isPseudoClass(node) && node.value === ':dir'; | ||
| return postCssSelectorParser.isPseudoClass(node) && node.value === ':dir'; | ||
| } | ||
@@ -488,3 +488,3 @@ | ||
| function isHostPseudoClass(node) { | ||
| return isPseudoClass(node) && node.value === ':host'; | ||
| return postCssSelectorParser.isPseudoClass(node) && node.value === ':host'; | ||
| } | ||
@@ -502,3 +502,3 @@ /** | ||
| selector.each((node) => { | ||
| if (isCombinator(node)) { | ||
| if (postCssSelectorParser.isCombinator(node)) { | ||
| compoundSelectors.push([]); | ||
@@ -522,7 +522,7 @@ } | ||
| for (const node of compoundSelector) { | ||
| if (!isPseudoElement(node)) { | ||
| if (!postCssSelectorParser.isPseudoElement(node)) { | ||
| nodeToScope = node; | ||
| } | ||
| } | ||
| const shadowAttribute = attribute({ | ||
| const shadowAttribute = postCssSelectorParser.attribute({ | ||
| attribute: SHADOW_ATTRIBUTE, | ||
@@ -567,3 +567,3 @@ value: undefined, | ||
| // Swap the :host pseudo-class with the host scoping token | ||
| const hostAttribute = attribute({ | ||
| const hostAttribute = postCssSelectorParser.attribute({ | ||
| attribute: HOST_ATTRIBUTE, | ||
@@ -638,3 +638,3 @@ value: undefined, | ||
| // `<synthetic_placeholder> .foo<native_placeholder>:not(.bar)` | ||
| const nativeAttribute = attribute({ | ||
| const nativeAttribute = postCssSelectorParser.attribute({ | ||
| attribute: value === 'ltr' ? DIR_ATTRIBUTE_NATIVE_LTR : DIR_ATTRIBUTE_NATIVE_RTL, | ||
@@ -644,3 +644,3 @@ value: undefined, | ||
| }); | ||
| const syntheticAttribute = attribute({ | ||
| const syntheticAttribute = postCssSelectorParser.attribute({ | ||
| attribute: value === 'ltr' ? DIR_ATTRIBUTE_SYNTHETIC_LTR : DIR_ATTRIBUTE_SYNTHETIC_RTL, | ||
@@ -653,5 +653,7 @@ value: undefined, | ||
| // " " combinator, we need to use the descendant selector format | ||
| const shouldAddDescendantCombinator = selector.first && !isCombinator(selector.first) && selector.first.value !== ' '; | ||
| const shouldAddDescendantCombinator = selector.first && | ||
| !postCssSelectorParser.isCombinator(selector.first) && | ||
| selector.first.value !== ' '; | ||
| if (shouldAddDescendantCombinator) { | ||
| selector.insertBefore(selector.first, combinator({ | ||
| selector.insertBefore(selector.first, postCssSelectorParser.combinator({ | ||
| value: ' ', | ||
@@ -743,3 +745,3 @@ })); | ||
| function selectorProcessorFactory(transformConfig, ctx) { | ||
| return postCssSelector((root) => { | ||
| return postCssSelectorParser((root) => { | ||
| validateIdSelectors(root, ctx); | ||
@@ -852,3 +854,3 @@ transformSelector(root, transformConfig, ctx); | ||
| * @example | ||
| * const {transform} = require('@lwc/style-compiler'); | ||
| * import { transform } from '@lwc/style-compiler'; | ||
| * const source = ` | ||
@@ -901,3 +903,3 @@ * :host { | ||
| export { transform }; | ||
| /** version: 8.28.2 */ | ||
| /** version: 9.0.0 */ | ||
| //# sourceMappingURL=index.js.map |
@@ -27,3 +27,3 @@ /** Configuration options for CSS transforms. */ | ||
| * @example | ||
| * const {transform} = require('@lwc/style-compiler'); | ||
| * import { transform } from '@lwc/style-compiler'; | ||
| * const source = ` | ||
@@ -30,0 +30,0 @@ * :host { |
+7
-3
@@ -7,3 +7,3 @@ { | ||
| "name": "@lwc/style-compiler", | ||
| "version": "8.28.2", | ||
| "version": "9.0.0", | ||
| "description": "Transform style sheet to be consumed by the LWC engine", | ||
@@ -23,9 +23,13 @@ "keywords": [ | ||
| "license": "MIT", | ||
| "type": "module", | ||
| "publishConfig": { | ||
| "access": "public" | ||
| }, | ||
| "engines": { | ||
| "node": ">=16.6.0" | ||
| }, | ||
| "volta": { | ||
| "extends": "../../../package.json" | ||
| }, | ||
| "main": "dist/index.cjs.js", | ||
| "main": "dist/index.js", | ||
| "module": "dist/index.js", | ||
@@ -51,3 +55,3 @@ "types": "dist/index.d.ts", | ||
| "dependencies": { | ||
| "@lwc/shared": "8.28.2", | ||
| "@lwc/shared": "9.0.0", | ||
| "postcss": "~8.5.6", | ||
@@ -54,0 +58,0 @@ "postcss-selector-parser": "~7.1.1", |
+1
-1
@@ -23,3 +23,3 @@ # @lwc/style-compiler | ||
| ```js | ||
| const { transform } = require('@lwc/style-compiler'); | ||
| import { transform } from '@lwc/style-compiler'; | ||
@@ -26,0 +26,0 @@ const source = ` |
| /** | ||
| * Copyright (c) 2026 Salesforce, Inc. | ||
| */ | ||
| 'use strict'; | ||
| Object.defineProperty(exports, '__esModule', { value: true }); | ||
| var postcss = require('postcss'); | ||
| var shared = require('@lwc/shared'); | ||
| var postCssSelector = require('postcss-selector-parser'); | ||
| var valueParser = require('postcss-value-parser'); | ||
| const PLUGIN_NAME = '@lwc/style-compiler'; | ||
| const IMPORT_TYPE = 'import'; | ||
| function importMessage(id) { | ||
| return { | ||
| plugin: PLUGIN_NAME, | ||
| type: IMPORT_TYPE, | ||
| id, | ||
| }; | ||
| } | ||
| function isImportMessage(message) { | ||
| return message.type === IMPORT_TYPE && message.id; | ||
| } | ||
| /* | ||
| * Copyright (c) 2018, salesforce.com, inc. | ||
| * All rights reserved. | ||
| * SPDX-License-Identifier: MIT | ||
| * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT | ||
| */ | ||
| const HOST_ATTRIBUTE = '__hostAttribute__'; | ||
| const SHADOW_ATTRIBUTE = '__shadowAttribute__'; | ||
| /* | ||
| * Copyright (c) 2018, salesforce.com, inc. | ||
| * All rights reserved. | ||
| * SPDX-License-Identifier: MIT | ||
| * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT | ||
| */ | ||
| const DIR_ATTRIBUTE_NATIVE_LTR = `__dirAttributeNativeLtr__`; | ||
| const DIR_ATTRIBUTE_NATIVE_RTL = `__dirAttributeNativeRtl__`; | ||
| const DIR_ATTRIBUTE_SYNTHETIC_LTR = `__dirAttributeSyntheticLtr__`; | ||
| const DIR_ATTRIBUTE_SYNTHETIC_RTL = `__dirAttributeSyntheticRtl__`; | ||
| /* | ||
| * Copyright (c) 2018, salesforce.com, inc. | ||
| * All rights reserved. | ||
| * SPDX-License-Identifier: MIT | ||
| * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT | ||
| */ | ||
| var TokenType; | ||
| (function (TokenType) { | ||
| TokenType["text"] = "text"; | ||
| TokenType["expression"] = "expression"; | ||
| TokenType["identifier"] = "identifier"; | ||
| TokenType["divider"] = "divider"; | ||
| })(TokenType || (TokenType = {})); | ||
| // "1400 binary expressions are enough to reach Node.js maximum call stack size" | ||
| // https://github.com/salesforce/lwc/issues/1726 | ||
| // The vast majority of stylesheet functions are much less than this, so we can set the limit lower | ||
| // to play it safe. | ||
| const BINARY_EXPRESSION_LIMIT = 100; | ||
| // Javascript identifiers used for the generation of the style module | ||
| const HOST_SELECTOR_IDENTIFIER = 'hostSelector'; | ||
| const SHADOW_SELECTOR_IDENTIFIER = 'shadowSelector'; | ||
| const SUFFIX_TOKEN_IDENTIFIER = 'suffixToken'; | ||
| const USE_ACTUAL_HOST_SELECTOR = 'useActualHostSelector'; | ||
| const USE_NATIVE_DIR_PSEUDOCLASS = 'useNativeDirPseudoclass'; | ||
| const TOKEN = 'token'; | ||
| const STYLESHEET_IDENTIFIER = 'stylesheet'; | ||
| function serialize(result, config) { | ||
| const { messages } = result; | ||
| const importedStylesheets = messages.filter(isImportMessage).map((message) => message.id); | ||
| const disableSyntheticShadow = Boolean(config.disableSyntheticShadowSupport); | ||
| const scoped = Boolean(config.scoped); | ||
| let buffer = ''; | ||
| for (let i = 0; i < importedStylesheets.length; i++) { | ||
| buffer += `import ${STYLESHEET_IDENTIFIER + i} from "${importedStylesheets[i]}";\n`; | ||
| } | ||
| if (importedStylesheets.length) { | ||
| buffer += '\n'; | ||
| } | ||
| const stylesheetList = importedStylesheets.map((_str, i) => `${STYLESHEET_IDENTIFIER + i}`); | ||
| const serializedStyle = serializeCss(result).trim(); | ||
| if (serializedStyle) { | ||
| // inline function | ||
| if (disableSyntheticShadow && !scoped) { | ||
| // If synthetic shadow DOM support is disabled and this is not a scoped stylesheet, then the | ||
| // function signature will always be: | ||
| // stylesheet(token = undefined, useActualHostSelector = true, useNativeDirPseudoclass = true) | ||
| // This means that we can just have a function that takes no arguments and returns a string, | ||
| // reducing the bundle size when minified. | ||
| buffer += `function ${STYLESHEET_IDENTIFIER}() {\n`; | ||
| buffer += ` var ${TOKEN};\n`; // undefined | ||
| buffer += ` var ${USE_ACTUAL_HOST_SELECTOR} = true;\n`; | ||
| buffer += ` var ${USE_NATIVE_DIR_PSEUDOCLASS} = true;\n`; | ||
| } | ||
| else { | ||
| buffer += `function ${STYLESHEET_IDENTIFIER}(${TOKEN}, ${USE_ACTUAL_HOST_SELECTOR}, ${USE_NATIVE_DIR_PSEUDOCLASS}) {\n`; | ||
| } | ||
| // For scoped stylesheets, we use classes, but for synthetic shadow DOM, we use attributes | ||
| if (scoped) { | ||
| buffer += ` var ${SHADOW_SELECTOR_IDENTIFIER} = ${TOKEN} ? ("." + ${TOKEN}) : "";\n`; | ||
| buffer += ` var ${HOST_SELECTOR_IDENTIFIER} = ${TOKEN} ? ("." + ${TOKEN} + "-host") : "";\n`; | ||
| } | ||
| else { | ||
| buffer += ` var ${SHADOW_SELECTOR_IDENTIFIER} = ${TOKEN} ? ("[" + ${TOKEN} + "]") : "";\n`; | ||
| buffer += ` var ${HOST_SELECTOR_IDENTIFIER} = ${TOKEN} ? ("[" + ${TOKEN} + "-host]") : "";\n`; | ||
| } | ||
| // Used for keyframes | ||
| buffer += ` var ${SUFFIX_TOKEN_IDENTIFIER} = ${TOKEN} ? ("-" + ${TOKEN}) : "";\n`; | ||
| buffer += ` return ${serializedStyle};\n`; | ||
| buffer += ` /*${shared.LWC_VERSION_COMMENT}*/\n`; | ||
| buffer += `}\n`; | ||
| if (scoped) { | ||
| // Mark the stylesheet as scoped so that we can distinguish it later at runtime | ||
| buffer += `${STYLESHEET_IDENTIFIER}.${shared.KEY__SCOPED_CSS} = true;\n`; | ||
| } | ||
| if (disableSyntheticShadow) { | ||
| // Mark the stylesheet as $nativeOnly$ so it can be ignored in synthetic shadow mode | ||
| buffer += `${STYLESHEET_IDENTIFIER}.${shared.KEY__NATIVE_ONLY_CSS} = true;\n`; | ||
| } | ||
| // add import at the end | ||
| stylesheetList.push(STYLESHEET_IDENTIFIER); | ||
| } | ||
| // exports | ||
| if (stylesheetList.length) { | ||
| buffer += `export default [${stylesheetList.join(', ')}];`; | ||
| } | ||
| else { | ||
| buffer += `export default undefined;`; | ||
| } | ||
| return buffer; | ||
| } | ||
| function reduceTokens(tokens) { | ||
| return [{ type: TokenType.text, value: '' }, ...tokens, { type: TokenType.text, value: '' }] | ||
| .reduce((acc, token) => { | ||
| const prev = acc[acc.length - 1]; | ||
| if (token.type === TokenType.text && prev && prev.type === TokenType.text) { | ||
| // clone the previous token to avoid mutating it in-place | ||
| acc[acc.length - 1] = { | ||
| type: prev.type, | ||
| value: prev.value + token.value, | ||
| }; | ||
| return acc; | ||
| } | ||
| else { | ||
| return [...acc, token]; | ||
| } | ||
| }, []) | ||
| .filter((t) => t.value !== ''); | ||
| } | ||
| function normalizeString(str) { | ||
| return str.replace(/(\r\n\t|\n|\r\t)/gm, '').trim(); | ||
| } | ||
| function generateExpressionFromTokens(tokens) { | ||
| const serializedTokens = reduceTokens(tokens).map(({ type, value }) => { | ||
| switch (type) { | ||
| // Note that we don't expect to get a TokenType.divider here. It should be converted into an | ||
| // expression elsewhere. | ||
| case TokenType.text: | ||
| return JSON.stringify(value); | ||
| // Expressions may be concatenated with " + ", in which case we must remove ambiguity | ||
| case TokenType.expression: | ||
| return `(${value})`; | ||
| default: | ||
| return value; | ||
| } | ||
| }); | ||
| if (serializedTokens.length === 0) { | ||
| return ''; | ||
| } | ||
| else if (serializedTokens.length === 1) { | ||
| return serializedTokens[0]; | ||
| } | ||
| else if (serializedTokens.length < BINARY_EXPRESSION_LIMIT) { | ||
| return serializedTokens.join(' + '); | ||
| } | ||
| else { | ||
| // #1726 Using Array.prototype.join() instead of a standard "+" operator to concatenate the | ||
| // string to avoid running into a maximum call stack error when the stylesheet is parsed | ||
| // again by the bundler. | ||
| return `[${serializedTokens.join(', ')}].join('')`; | ||
| } | ||
| } | ||
| function areTokensEqual(left, right) { | ||
| return left.type === right.type && left.value === right.value; | ||
| } | ||
| function calculateNumDuplicatedTokens(left, right) { | ||
| // Walk backwards until we find a token that is different between left and right | ||
| let i = 0; | ||
| for (; i < left.length && i < right.length; i++) { | ||
| const currentLeft = left[left.length - 1 - i]; | ||
| const currentRight = right[right.length - 1 - i]; | ||
| if (!areTokensEqual(currentLeft, currentRight)) { | ||
| break; | ||
| } | ||
| } | ||
| return i; | ||
| } | ||
| // For `:host` selectors, the token lists for native vs synthetic will be identical at the end of | ||
| // each list. So as an optimization, we can de-dup these tokens. | ||
| // See: https://github.com/salesforce/lwc/issues/3224#issuecomment-1353520052 | ||
| function deduplicateHostTokens(nativeHostTokens, syntheticHostTokens) { | ||
| const numDuplicatedTokens = calculateNumDuplicatedTokens(nativeHostTokens, syntheticHostTokens); | ||
| const numUniqueNativeTokens = nativeHostTokens.length - numDuplicatedTokens; | ||
| const numUniqueSyntheticTokens = syntheticHostTokens.length - numDuplicatedTokens; | ||
| const uniqueNativeTokens = nativeHostTokens.slice(0, numUniqueNativeTokens); | ||
| const uniqueSyntheticTokens = syntheticHostTokens.slice(0, numUniqueSyntheticTokens); | ||
| const nativeExpression = generateExpressionFromTokens(uniqueNativeTokens); | ||
| const syntheticExpression = generateExpressionFromTokens(uniqueSyntheticTokens); | ||
| // Generate a conditional ternary to switch between native vs synthetic for the unique tokens | ||
| const conditionalToken = { | ||
| type: TokenType.expression, | ||
| value: `(${USE_ACTUAL_HOST_SELECTOR} ? ${nativeExpression} : ${syntheticExpression})`, | ||
| }; | ||
| return [ | ||
| conditionalToken, | ||
| // The remaining tokens are the same between native and synthetic | ||
| ...syntheticHostTokens.slice(numUniqueSyntheticTokens), | ||
| ]; | ||
| } | ||
| function serializeCss(result) { | ||
| const tokens = []; | ||
| let currentRuleTokens = []; | ||
| let nativeHostTokens; | ||
| // Walk though all nodes in the CSS... | ||
| postcss.stringify(result.root, (part, node, nodePosition) => { | ||
| // When consuming the beginning of a rule, first we tokenize the selector | ||
| if (node && node.type === 'rule' && nodePosition === 'start') { | ||
| currentRuleTokens.push(...tokenizeCss(normalizeString(part))); | ||
| // When consuming the end of a rule we normalize it and produce a new one | ||
| } | ||
| else if (node && node.type === 'rule' && nodePosition === 'end') { | ||
| currentRuleTokens.push({ type: TokenType.text, value: part }); | ||
| // If we are in synthetic shadow or scoped light DOM, we don't want to have native :host selectors | ||
| // Note that postcss-lwc-plugin should ensure that _isNativeHost appears before _isSyntheticHost | ||
| if (node._isNativeHost) { | ||
| // Save native tokens so in the next rule we can apply a conditional ternary | ||
| nativeHostTokens = [...currentRuleTokens]; | ||
| } | ||
| else if (node._isSyntheticHost) { | ||
| /* istanbul ignore if */ | ||
| if (!nativeHostTokens) { | ||
| throw new Error('Unexpected host rules ordering'); | ||
| } | ||
| const hostTokens = deduplicateHostTokens(nativeHostTokens, currentRuleTokens); | ||
| tokens.push(...hostTokens); | ||
| nativeHostTokens = undefined; | ||
| } | ||
| else { | ||
| /* istanbul ignore if */ | ||
| if (nativeHostTokens) { | ||
| throw new Error('Unexpected host rules ordering'); | ||
| } | ||
| tokens.push(...currentRuleTokens); | ||
| } | ||
| // Reset rule | ||
| currentRuleTokens = []; | ||
| // When inside a declaration, tokenize it and push it to the current token list | ||
| } | ||
| else if (node && node.type === 'decl') { | ||
| currentRuleTokens.push(...tokenizeCss(part)); | ||
| } | ||
| else if (node && node.type === 'atrule') { | ||
| // Certain atrules have declaration associated with for example @font-face. We need to add the rules tokens | ||
| // when it's the case. | ||
| if (currentRuleTokens.length) { | ||
| tokens.push(...currentRuleTokens); | ||
| currentRuleTokens = []; | ||
| } | ||
| tokens.push(...tokenizeCss(normalizeString(part))); | ||
| } | ||
| else { | ||
| // When inside anything else but a comment just push it | ||
| if (!node || node.type !== 'comment') { | ||
| currentRuleTokens.push({ type: TokenType.text, value: normalizeString(part) }); | ||
| } | ||
| } | ||
| }); | ||
| return generateExpressionFromTokens(tokens); | ||
| } | ||
| // Given any CSS string, replace the scope tokens from the CSS with code to properly | ||
| // replace it in the stylesheet function. | ||
| function tokenizeCss(data) { | ||
| data = data.replace(/( {2,})/gm, ' '); // remove when there are more than two spaces | ||
| const tokens = []; | ||
| const attributes = [ | ||
| SHADOW_ATTRIBUTE, | ||
| HOST_ATTRIBUTE, | ||
| DIR_ATTRIBUTE_NATIVE_LTR, | ||
| DIR_ATTRIBUTE_NATIVE_RTL, | ||
| DIR_ATTRIBUTE_SYNTHETIC_LTR, | ||
| DIR_ATTRIBUTE_SYNTHETIC_RTL, | ||
| ]; | ||
| const regex = new RegExp(`[[-](${attributes.join('|')})]?`, 'g'); | ||
| let lastIndex = 0; | ||
| for (const match of data.matchAll(regex)) { | ||
| const index = match.index; | ||
| const [matchString, substring] = match; | ||
| if (index > lastIndex) { | ||
| tokens.push({ type: TokenType.text, value: data.substring(lastIndex, index) }); | ||
| } | ||
| const identifier = substring === SHADOW_ATTRIBUTE ? SHADOW_SELECTOR_IDENTIFIER : HOST_SELECTOR_IDENTIFIER; | ||
| if (matchString.startsWith('[')) { | ||
| if (substring === SHADOW_ATTRIBUTE || substring === HOST_ATTRIBUTE) { | ||
| // attribute in a selector, e.g. `[__shadowAttribute__]` or `[__hostAttribute__]` | ||
| tokens.push({ | ||
| type: TokenType.identifier, | ||
| value: identifier, | ||
| }); | ||
| } | ||
| else { | ||
| // :dir pseudoclass placeholder, e.g. `[__dirAttributeNativeLtr__]` or `[__dirAttributeSyntheticRtl__]` | ||
| const native = substring === DIR_ATTRIBUTE_NATIVE_LTR || | ||
| substring === DIR_ATTRIBUTE_NATIVE_RTL; | ||
| const dirValue = substring === DIR_ATTRIBUTE_NATIVE_LTR || | ||
| substring === DIR_ATTRIBUTE_SYNTHETIC_LTR | ||
| ? 'ltr' | ||
| : 'rtl'; | ||
| tokens.push({ | ||
| type: TokenType.expression, | ||
| // use the native :dir() pseudoclass for native shadow, the [dir] attribute otherwise | ||
| value: native | ||
| ? `${USE_NATIVE_DIR_PSEUDOCLASS} ? ':dir(${dirValue})' : ''` | ||
| : `${USE_NATIVE_DIR_PSEUDOCLASS} ? '' : '[dir="${dirValue}"]'`, | ||
| }); | ||
| } | ||
| } | ||
| else { | ||
| // suffix for an at-rule, e.g. `@keyframes spin-__shadowAttribute__` | ||
| tokens.push({ | ||
| type: TokenType.identifier, | ||
| // Suffix the keyframe (i.e. "-" plus the token) | ||
| value: SUFFIX_TOKEN_IDENTIFIER, | ||
| }); | ||
| } | ||
| lastIndex = index + matchString.length; | ||
| } | ||
| if (lastIndex < data.length) { | ||
| tokens.push({ type: TokenType.text, value: data.substring(lastIndex, data.length) }); | ||
| } | ||
| return tokens; | ||
| } | ||
| function validateIdSelectors (root, ctx) { | ||
| root.walkIds((node) => { | ||
| ctx.withErrorRecovery(() => { | ||
| const message = `Invalid usage of id selector '#${node.value}'. Try using a class selector or some other selector.`; | ||
| throw root.error(message, { | ||
| index: node.sourceIndex, | ||
| word: node.value, | ||
| }); | ||
| }); | ||
| }); | ||
| } | ||
| /* | ||
| * Copyright (c) 2018, salesforce.com, inc. | ||
| * All rights reserved. | ||
| * SPDX-License-Identifier: MIT | ||
| * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT | ||
| */ | ||
| function process$1(root, result, isScoped, ctx) { | ||
| root.walkAtRules('import', (node) => { | ||
| ctx.withErrorRecovery(() => { | ||
| if (isScoped) { | ||
| throw node.error(`Invalid import statement, imports are not allowed in *.scoped.css files.`); | ||
| } | ||
| // Ensure @import are at the top of the file | ||
| let prev = node.prev(); | ||
| while (prev) { | ||
| if (prev.type === 'comment' || (prev.type === 'atrule' && prev.name === 'import')) { | ||
| prev = prev.prev(); | ||
| } | ||
| else { | ||
| throw prev.error('@import must precede all other statements'); | ||
| } | ||
| } | ||
| const { nodes: params } = valueParser(node.params); | ||
| // Ensure import match the following syntax: | ||
| // @import "foo"; | ||
| // @import "./foo.css"; | ||
| if (!params.length || params[0].type !== 'string' || !params[0].value) { | ||
| throw node.error(`Invalid import statement, unable to find imported module.`); | ||
| } | ||
| if (params.length > 1) { | ||
| throw node.error(`Invalid import statement, import statement only support a single parameter.`); | ||
| } | ||
| // Add the imported to results messages | ||
| const message = importMessage(params[0].value); | ||
| result.messages.push(message); | ||
| // Remove the import from the generated css | ||
| node.remove(); | ||
| }); | ||
| }); | ||
| } | ||
| /* | ||
| * Copyright (c) 2018, salesforce.com, inc. | ||
| * All rights reserved. | ||
| * SPDX-License-Identifier: MIT | ||
| * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT | ||
| */ | ||
| function isDirPseudoClass(node) { | ||
| return postCssSelector.isPseudoClass(node) && node.value === ':dir'; | ||
| } | ||
| function findNode(container, predicate) { | ||
| return container && container.nodes && container.nodes.find(predicate); | ||
| } | ||
| function replaceNodeWith(oldNode, ...newNodes) { | ||
| if (newNodes.length) { | ||
| const { parent } = oldNode; | ||
| if (!parent) { | ||
| throw new Error(`Impossible to replace root node.`); | ||
| } | ||
| newNodes.forEach((node) => { | ||
| parent.insertBefore(oldNode, node); | ||
| }); | ||
| oldNode.remove(); | ||
| } | ||
| } | ||
| function trimNodeWhitespaces(node) { | ||
| if (node && node.spaces) { | ||
| node.spaces.before = ''; | ||
| node.spaces.after = ''; | ||
| } | ||
| } | ||
| const DEPRECATED_SELECTORS = new Set(['/deep/', '::shadow', '>>>']); | ||
| const UNSUPPORTED_SELECTORS = new Set([':root', ':host-context']); | ||
| const TEMPLATE_DIRECTIVES = [/^key$/, /^lwc:*/, /^if:*/, /^for:*/, /^iterator:*/]; | ||
| function validateSelectors(root, native, ctx) { | ||
| root.walk((node) => { | ||
| ctx.withErrorRecovery(() => { | ||
| const { value, sourceIndex } = node; | ||
| if (value) { | ||
| // Ensure the selector doesn't use a deprecated CSS selector. | ||
| if (DEPRECATED_SELECTORS.has(value)) { | ||
| throw root.error(`Invalid usage of deprecated selector "${value}".`, { | ||
| index: sourceIndex, | ||
| word: value, | ||
| }); | ||
| } | ||
| // Ensure the selector doesn't use an unsupported selector. | ||
| if (!native && UNSUPPORTED_SELECTORS.has(value)) { | ||
| 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.`, { | ||
| index: sourceIndex, | ||
| word: value, | ||
| }); | ||
| } | ||
| } | ||
| }); | ||
| }); | ||
| } | ||
| function validateAttribute(root, ctx) { | ||
| root.walkAttributes((node) => { | ||
| ctx.withErrorRecovery(() => { | ||
| const { attribute: attributeName, sourceIndex } = node; | ||
| const isTemplateDirective = TEMPLATE_DIRECTIVES.some((directive) => { | ||
| return directive.test(attributeName); | ||
| }); | ||
| if (isTemplateDirective) { | ||
| const message = [ | ||
| `Invalid usage of attribute selector "${attributeName}". `, | ||
| `"${attributeName}" is a template directive and therefore not supported in css rules.`, | ||
| ]; | ||
| throw root.error(message.join(''), { | ||
| index: sourceIndex, | ||
| word: attributeName, | ||
| }); | ||
| } | ||
| }); | ||
| }); | ||
| } | ||
| function validate(root, native, ctx) { | ||
| validateSelectors(root, native, ctx); | ||
| validateAttribute(root, ctx); | ||
| } | ||
| /* | ||
| * Copyright (c) 2018, salesforce.com, inc. | ||
| * All rights reserved. | ||
| * SPDX-License-Identifier: MIT | ||
| * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT | ||
| */ | ||
| function isHostPseudoClass(node) { | ||
| return postCssSelector.isPseudoClass(node) && node.value === ':host'; | ||
| } | ||
| /** | ||
| * Add scoping attributes to all the matching selectors: | ||
| * - h1 -> h1[x-foo_tmpl] | ||
| * - p a -> p[x-foo_tmpl] a[x-foo_tmpl] | ||
| * @param selector | ||
| */ | ||
| function scopeSelector(selector) { | ||
| const compoundSelectors = [[]]; | ||
| // Split the selector per compound selector. Compound selectors are interleaved with combinator nodes. | ||
| // https://drafts.csswg.org/selectors-4/#typedef-complex-selector | ||
| selector.each((node) => { | ||
| if (postCssSelector.isCombinator(node)) { | ||
| compoundSelectors.push([]); | ||
| } | ||
| else { | ||
| const current = compoundSelectors[compoundSelectors.length - 1]; | ||
| current.push(node); | ||
| } | ||
| }); | ||
| for (const compoundSelector of compoundSelectors) { | ||
| // Compound selectors with only a single :dir pseudo class should be scoped, the dir pseudo | ||
| // class transform will take care of transforming it properly. | ||
| const containsSingleDirSelector = compoundSelector.length === 1 && isDirPseudoClass(compoundSelector[0]); | ||
| // Compound selectors containing :host have a special treatment and should not be scoped | ||
| // like the rest of the complex selectors. | ||
| const containsHost = compoundSelector.some(isHostPseudoClass); | ||
| if (!containsSingleDirSelector && !containsHost) { | ||
| let nodeToScope; | ||
| // In each compound selector we need to locate the last selector to scope. | ||
| for (const node of compoundSelector) { | ||
| if (!postCssSelector.isPseudoElement(node)) { | ||
| nodeToScope = node; | ||
| } | ||
| } | ||
| const shadowAttribute = postCssSelector.attribute({ | ||
| attribute: SHADOW_ATTRIBUTE, | ||
| value: undefined, | ||
| raws: {}, | ||
| }); | ||
| if (nodeToScope) { | ||
| // Add the scoping attribute right after the node scope | ||
| selector.insertAfter(nodeToScope, shadowAttribute); | ||
| } | ||
| else { | ||
| // Add the scoping token in the first position of the compound selector as a fallback | ||
| // when there is no node to scope. For example: ::after {} | ||
| const [firstSelector] = compoundSelector; | ||
| selector.insertBefore(firstSelector, shadowAttribute); | ||
| // Move any whitespace before the selector (e.g. " ::after") to before the shadow attribute, | ||
| // so that the resulting selector is correct (e.g. " [attr]::after", not "[attr] ::after") | ||
| if (firstSelector && firstSelector.spaces.before) { | ||
| shadowAttribute.spaces.before = firstSelector.spaces.before; | ||
| const clonedFirstSelector = firstSelector.clone({}); | ||
| clonedFirstSelector.spaces.before = ''; | ||
| firstSelector.replaceWith(clonedFirstSelector); | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
| /** | ||
| * Mark the :host selector with a placeholder. If the selector has a list of | ||
| * contextual selector it will generate a rule for each of them. | ||
| * - `:host -> [x-foo_tmpl-host]` | ||
| * - `:host(.foo, .bar) -> [x-foo_tmpl-host].foo, [x-foo_tmpl-host].bar` | ||
| * @param selector | ||
| */ | ||
| function transformHost(selector) { | ||
| // Locate the first :host pseudo-class | ||
| const hostNode = findNode(selector, isHostPseudoClass); | ||
| if (hostNode) { | ||
| // Store the original location of the :host in the selector | ||
| const hostIndex = selector.index(hostNode); | ||
| // Swap the :host pseudo-class with the host scoping token | ||
| const hostAttribute = postCssSelector.attribute({ | ||
| attribute: HOST_ATTRIBUTE, | ||
| value: undefined, | ||
| raws: {}, | ||
| }); | ||
| hostNode.replaceWith(hostAttribute); | ||
| // Generate a unique contextualized version of the selector for each selector pass as argument | ||
| // to the :host | ||
| const contextualSelectors = hostNode.nodes.map((contextSelectors) => { | ||
| const clonedSelector = selector.clone({}); | ||
| const clonedHostNode = clonedSelector.at(hostIndex); | ||
| // Add to the compound selector previously containing the :host pseudo class | ||
| // the contextual selectors. | ||
| contextSelectors.each((node) => { | ||
| trimNodeWhitespaces(node); | ||
| clonedSelector.insertAfter(clonedHostNode, node); | ||
| }); | ||
| return clonedSelector; | ||
| }); | ||
| // Replace the current selector with the different variants | ||
| replaceNodeWith(selector, ...contextualSelectors); | ||
| } | ||
| } | ||
| function transformSelector(root, transformConfig, ctx) { | ||
| validate(root, transformConfig.disableSyntheticShadowSupport && !transformConfig.scoped, ctx); | ||
| root.each(scopeSelector); | ||
| if (transformConfig.transformHost) { | ||
| root.each(transformHost); | ||
| } | ||
| } | ||
| /* | ||
| * Copyright (c) 2018, salesforce.com, inc. | ||
| * All rights reserved. | ||
| * SPDX-License-Identifier: MIT | ||
| * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT | ||
| */ | ||
| function isValidDirValue(value) { | ||
| return value === 'ltr' || value === 'rtl'; | ||
| } | ||
| function transformDirPseudoClass (root, ctx) { | ||
| root.nodes.forEach((selector) => { | ||
| selector.nodes.forEach((node) => { | ||
| ctx.withErrorRecovery(() => { | ||
| if (!isDirPseudoClass(node)) { | ||
| return; | ||
| } | ||
| const value = node.nodes.toString().trim(); | ||
| if (!isValidDirValue(value)) { | ||
| throw root.error(`:dir() pseudo class expects "ltr" or "rtl" for value, but received "${value}".`, { | ||
| index: node.sourceIndex, | ||
| word: node.value, | ||
| }); | ||
| } | ||
| // Set placeholders for `:dir()` so we can keep it for native shadow and | ||
| // replace it with a polyfill for synthetic shadow. | ||
| // | ||
| // Native: `:dir(ltr)` | ||
| // Synthetic: `[dir="ltr"]` | ||
| // | ||
| // The placeholders look like this: `[__dirAttributeNativeLtr__]` | ||
| // The attribute has no value because it's simpler during serialization, and there | ||
| // are only two valid values: "ltr" and "rtl". | ||
| // | ||
| // Now consider a more complex selector: `.foo:dir(ltr):not(.bar)`. | ||
| // For native shadow, we need to leave it as-is. Whereas for synthetic shadow, we need | ||
| // to convert it to: `[dir="ltr"] .foo:not(.bar)`. | ||
| // I.e. we need to use a descendant selector (' ' combinator) relying on a `dir` | ||
| // attribute added to the host element. So we need two placeholders: | ||
| // `<synthetic_placeholder> .foo<native_placeholder>:not(.bar)` | ||
| const nativeAttribute = postCssSelector.attribute({ | ||
| attribute: value === 'ltr' ? DIR_ATTRIBUTE_NATIVE_LTR : DIR_ATTRIBUTE_NATIVE_RTL, | ||
| value: undefined, | ||
| raws: {}, | ||
| }); | ||
| const syntheticAttribute = postCssSelector.attribute({ | ||
| attribute: value === 'ltr' ? DIR_ATTRIBUTE_SYNTHETIC_LTR : DIR_ATTRIBUTE_SYNTHETIC_RTL, | ||
| value: undefined, | ||
| raws: {}, | ||
| }); | ||
| node.replaceWith(nativeAttribute); | ||
| // If the selector is not empty and if the first node in the selector is not already a | ||
| // " " combinator, we need to use the descendant selector format | ||
| const shouldAddDescendantCombinator = selector.first && !postCssSelector.isCombinator(selector.first) && selector.first.value !== ' '; | ||
| if (shouldAddDescendantCombinator) { | ||
| selector.insertBefore(selector.first, postCssSelector.combinator({ | ||
| value: ' ', | ||
| })); | ||
| // Add the [dir] attribute in front of the " " combinator, i.e. as an ancestor | ||
| selector.insertBefore(selector.first, syntheticAttribute); | ||
| } | ||
| else { | ||
| // Otherwise there's no need for the descendant selector, so we can skip adding the | ||
| // space combinator and just put the synthetic placeholder next to the native one | ||
| selector.insertBefore(nativeAttribute, syntheticAttribute); | ||
| } | ||
| }); | ||
| }); | ||
| }); | ||
| } | ||
| /* | ||
| * Copyright (c) 2018, salesforce.com, inc. | ||
| * All rights reserved. | ||
| * SPDX-License-Identifier: MIT | ||
| * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT | ||
| */ | ||
| // Subset of prefixes for animation-related names that we expect people might be using. | ||
| // The most important is -webkit, which is actually part of the spec now. All -webkit prefixes | ||
| // are listed here: https://developer.mozilla.org/en-US/docs/Web/CSS/Webkit_Extensions | ||
| // -moz is also still supported as of Firefox 95. | ||
| // We could probably get away with just doing -webkit and -moz (since -ms never seems | ||
| // to have existed for keyframes/animations, and Opera has used Blink since 2013), but | ||
| // covering all the popular ones will at least make the compiled code more consistent | ||
| // for developers who are using all the variants. | ||
| // List based on a subset from https://github.com/wooorm/vendors/blob/2f489ad/index.js | ||
| const VENDOR_PREFIXES = ['moz', 'ms', 'o', 'webkit']; | ||
| // create a list like ['animation', '-webkit-animation', ...] | ||
| function getAllNames(name) { | ||
| return new Set([name, ...VENDOR_PREFIXES.map((prefix) => `-${prefix}-${name}`)]); | ||
| } | ||
| const ANIMATION = getAllNames('animation'); | ||
| const ANIMATION_NAME = getAllNames('animation-name'); | ||
| function process(root, ctx) { | ||
| const knownNames = new Set(); | ||
| root.walkAtRules((atRule) => { | ||
| ctx.withErrorRecovery(() => { | ||
| // Note that @-webkit-keyframes, @-moz-keyframes, etc. are not actually a thing supported | ||
| // in any browser, even though you'll see it on some StackOverflow answers. | ||
| if (atRule.name === 'keyframes') { | ||
| const { params } = atRule; | ||
| knownNames.add(params); | ||
| atRule.params = `${params}-${SHADOW_ATTRIBUTE}`; | ||
| } | ||
| }); | ||
| }); | ||
| root.walkRules((rule) => { | ||
| rule.walkDecls((decl) => { | ||
| ctx.withErrorRecovery(() => { | ||
| if (ANIMATION.has(decl.prop)) { | ||
| // Use a simple heuristic of breaking up the tokens by whitespace. We could use | ||
| // a dedicated animation prop parser (e.g. | ||
| // https://github.com/hookhookun/parse-animation-shorthand) but it's | ||
| // probably overkill. | ||
| const tokens = decl.value | ||
| .trim() | ||
| .split(/\s+/g) | ||
| .map((token) => knownNames.has(token) ? `${token}-${SHADOW_ATTRIBUTE}` : token); | ||
| decl.value = tokens.join(' '); | ||
| } | ||
| else if (ANIMATION_NAME.has(decl.prop)) { | ||
| if (knownNames.has(decl.value)) { | ||
| decl.value = `${decl.value}-${SHADOW_ATTRIBUTE}`; | ||
| } | ||
| } | ||
| }); | ||
| }); | ||
| }); | ||
| } | ||
| /* | ||
| * Copyright (c) 2018, salesforce.com, inc. | ||
| * All rights reserved. | ||
| * SPDX-License-Identifier: MIT | ||
| * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT | ||
| */ | ||
| function shouldTransformSelector(rule) { | ||
| // @keyframe at-rules are special, rules inside are not standard selectors and should not be | ||
| // scoped like any other rules. | ||
| return rule.parent?.type !== 'atrule' || rule.parent.name !== 'keyframes'; | ||
| } | ||
| function selectorProcessorFactory(transformConfig, ctx) { | ||
| return postCssSelector((root) => { | ||
| validateIdSelectors(root, ctx); | ||
| transformSelector(root, transformConfig, ctx); | ||
| transformDirPseudoClass(root, ctx); | ||
| }); | ||
| } | ||
| function postCssLwcPlugin(options) { | ||
| const { ctx } = options; | ||
| // We need 2 types of selectors processors, since transforming the :host selector make the selector | ||
| // unusable when used in the context of the native shadow and vice-versa. | ||
| // This distinction also applies to light DOM in scoped (synthetic-like) vs unscoped (native-like) mode. | ||
| const nativeShadowSelectorProcessor = selectorProcessorFactory({ | ||
| transformHost: false, | ||
| disableSyntheticShadowSupport: options.disableSyntheticShadowSupport, | ||
| scoped: options.scoped, | ||
| }, ctx); | ||
| const syntheticShadowSelectorProcessor = selectorProcessorFactory({ | ||
| transformHost: true, | ||
| disableSyntheticShadowSupport: options.disableSyntheticShadowSupport, | ||
| scoped: options.scoped, | ||
| }, ctx); | ||
| return (root, result) => { | ||
| process$1(root, result, options.scoped, ctx); | ||
| process(root, ctx); | ||
| // Wrap rule processing with error recovery | ||
| root.walkRules((rule) => { | ||
| ctx.withErrorRecovery(() => { | ||
| if (!shouldTransformSelector(rule)) { | ||
| return; | ||
| } | ||
| // Let transform the selector with the 2 processors. | ||
| const syntheticSelector = syntheticShadowSelectorProcessor.processSync(rule); | ||
| const nativeSelector = nativeShadowSelectorProcessor.processSync(rule); | ||
| rule.selector = syntheticSelector; | ||
| // If the resulting selector are different it means that the selector use the :host selector. In | ||
| // this case we need to duplicate the CSS rule and assign the other selector. | ||
| if (syntheticSelector !== nativeSelector) { | ||
| // The cloned selector is inserted before the currently processed selector to avoid processing | ||
| // again the cloned selector. | ||
| const currentRule = rule; | ||
| const clonedRule = rule.cloneBefore(); | ||
| clonedRule.selector = nativeSelector; | ||
| // Safe a reference to each other | ||
| clonedRule._isNativeHost = true; | ||
| currentRule._isSyntheticHost = true; | ||
| } | ||
| }); | ||
| }); | ||
| }; | ||
| } | ||
| /* | ||
| * Copyright (c) 2025, Salesforce, Inc. | ||
| * All rights reserved. | ||
| * SPDX-License-Identifier: MIT | ||
| * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT | ||
| */ | ||
| class StyleCompilerCtx { | ||
| constructor(errorRecoveryMode, filename) { | ||
| this.errors = []; | ||
| this.seenErrorKeys = new Set(); | ||
| this.errorRecoveryMode = errorRecoveryMode; | ||
| this.filename = filename; | ||
| } | ||
| /** | ||
| * This method recovers from CSS syntax errors that are encountered when fn is invoked. | ||
| * All other errors are considered compiler errors and can not be recovered from. | ||
| * @param fn method to be invoked. | ||
| */ | ||
| withErrorRecovery(fn) { | ||
| if (!this.errorRecoveryMode) { | ||
| return fn(); | ||
| } | ||
| try { | ||
| return fn(); | ||
| } | ||
| catch (error) { | ||
| if (error instanceof postcss.CssSyntaxError) { | ||
| if (this.seenErrorKeys.has(error.message)) { | ||
| return; | ||
| } | ||
| this.seenErrorKeys.add(error.message); | ||
| this.errors.push(error); | ||
| } | ||
| else { | ||
| // Non-CSS errors (compiler errors) should still throw | ||
| throw error; | ||
| } | ||
| } | ||
| } | ||
| hasErrors() { | ||
| return this.errors.length > 0; | ||
| } | ||
| } | ||
| /* | ||
| * Copyright (c) 2024, Salesforce, Inc. | ||
| * All rights reserved. | ||
| * SPDX-License-Identifier: MIT | ||
| * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT | ||
| */ | ||
| /** | ||
| * Transforms CSS for use with LWC components. | ||
| * @param src Contents of the CSS source file | ||
| * @param id Filename of the CSS source file | ||
| * @param config Transformation options | ||
| * @returns Transformed CSS | ||
| * @example | ||
| * const {transform} = require('@lwc/style-compiler'); | ||
| * const source = ` | ||
| * :host { | ||
| * opacity: 0.4; | ||
| * } | ||
| * span { | ||
| * text-transform: uppercase; | ||
| * }`; | ||
| * const { code } = transform(source, 'example.css'); | ||
| */ | ||
| function transform(src, id, config = {}) { | ||
| if (src === '') { | ||
| return { code: 'export default undefined' }; | ||
| } | ||
| const scoped = !!config.scoped; | ||
| shared.getAPIVersionFromNumber(config.apiVersion); | ||
| const disableSyntheticShadowSupport = !!config.disableSyntheticShadowSupport; | ||
| const errorRecoveryMode = !!config.experimentalErrorRecoveryMode; | ||
| // Create error recovery context | ||
| const ctx = new StyleCompilerCtx(errorRecoveryMode, id); | ||
| const plugins = [ | ||
| postCssLwcPlugin({ | ||
| scoped, | ||
| disableSyntheticShadowSupport, | ||
| ctx, | ||
| }), | ||
| ]; | ||
| // Wrap PostCSS processing with error recovery for parsing errors | ||
| let result; | ||
| try { | ||
| result = postcss(plugins).process(src, { from: id }).sync(); | ||
| } | ||
| catch (error) { | ||
| if (errorRecoveryMode && error instanceof postcss.CssSyntaxError) { | ||
| ctx.errors.push(error); | ||
| throw AggregateError(ctx.errors); | ||
| } | ||
| else { | ||
| throw error; | ||
| } | ||
| } | ||
| if (errorRecoveryMode && ctx.hasErrors()) { | ||
| throw AggregateError(ctx.errors); | ||
| } | ||
| return { code: serialize(result, config) }; | ||
| } | ||
| exports.transform = transform; | ||
| /** version: 8.28.2 */ | ||
| //# sourceMappingURL=index.cjs.js.map |
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
Unidentified License
LicenseSomething that seems like a license was found, but its contents could not be matched with a known license.
Found 1 instance in 1 package
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
Unidentified License
LicenseSomething that seems like a license was found, but its contents could not be matched with a known license.
Found 1 instance in 1 package
8
-46.67%Yes
NaN55803
-40.47%20
-4.76%991
-46.89%+ Added
- Removed
Updated