@lwc/style-compiler
Version:
Transform style sheet to be consumed by the LWC engine
114 lines • 4.88 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
/*
* 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 postcss_selector_parser_1 = require("postcss-selector-parser");
const validate_1 = __importDefault(require("./validate"));
const selector_parser_1 = require("../utils/selector-parser");
const selectors_scoping_1 = require("../utils/selectors-scoping");
function isHostPseudoClass(node) {
return postcss_selector_parser_1.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]
*/
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 (postcss_selector_parser_1.isCombinator(node)) {
compoundSelectors.push([]);
}
else {
const current = compoundSelectors[compoundSelectors.length - 1];
current.push(node);
}
});
for (const compoundSelector of compoundSelectors) {
// Compound selectors containing :host have a special treatment and should not be scoped like the rest of the
// complex selectors.
const shouldScopeCompoundSelector = compoundSelector.every(node => !isHostPseudoClass(node));
if (shouldScopeCompoundSelector) {
let nodeToScope;
// In each compound selector we need to locate the last selector to scope.
for (const node of compoundSelector) {
if (!postcss_selector_parser_1.isPseudoElement(node)) {
nodeToScope = node;
}
}
const shadowAttribute = postcss_selector_parser_1.attribute({
attribute: selectors_scoping_1.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 {}
selector.insertBefore(compoundSelector[0], shadowAttribute);
}
}
}
}
/**
* 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
*/
function transformHost(selector) {
// Locate the first :host pseudo-class
const hostNode = selector_parser_1.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 = postcss_selector_parser_1.attribute({
attribute: selectors_scoping_1.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 => {
selector_parser_1.trimNodeWhitespaces(node);
clonedSelector.insertAfter(clonedHostNode, node);
});
return clonedSelector;
});
// Replace the current selector with the different variants
selector_parser_1.replaceNodeWith(selector, ...contextualSelectors);
}
}
function transformSelector(root, transformConfig) {
validate_1.default(root);
root.each((selector) => {
scopeSelector(selector);
});
if (transformConfig.transformHost) {
root.each((selector) => {
transformHost(selector);
});
}
}
exports.default = transformSelector;
//# sourceMappingURL=transform.js.map