Latest Threat Research:SANDWORM_MODE: Shai-Hulud-Style npm Worm Hijacks CI Workflows and Poisons AI Toolchains.Details
Socket
Book a DemoInstallSign in
Socket

@lwc/style-compiler

Package Overview
Dependencies
Maintainers
14
Versions
862
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@lwc/style-compiler - npm Package Compare versions

Comparing version
8.28.2
to
9.0.0
+16
-14
dist/index.js

@@ -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 @@ {

"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",

@@ -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