@dnb/eufemia
Version:
DNB Eufemia Design System UI Library
265 lines • 13.4 kB
JavaScript
import "core-js/modules/es.string.replace.js";
import "core-js/modules/web.dom-collections.iterator.js";
import path from 'path';
import selectorParser from 'postcss-selector-parser';
import {findPathToScopeHash, getScopeHashFromFile} from './plugin-utils.js';
import {getStyleScopeHash} from './plugin-scope-hash.js';
const postcssIsolateStyle = function () {
let opts = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
const {
scopeHash = 'auto',
sharedScopeHash = undefined,
defaultScopeHash = 'eufemia-scope--',
skipClassNames = [],
replaceClassNames = undefined,
verbose = false,
runAsCssModule = false
} = opts;
const currentFallbackHash = getStyleScopeHash();
const skipClassNamesSet = new Set(skipClassNames);
const replaceClassNamesMap = replaceClassNames ? new Map(Object.entries(replaceClassNames)) : undefined;
let countBefore = 0;
let countAfter = 0;
if (!verbose && !global.__didLog && process.env.NODE_ENV !== 'test') {
global.__didLog = true;
console.log('✨ @dnb/eufemia/plugins/postcss-isolated-style-scope');
}
return {
postcssPlugin: 'isolated-style-scope-plugin',
Once(root) {
var _root$source$input$fi, _root$source, _root$source$input;
const file = (_root$source$input$fi = (_root$source = root.source) === null || _root$source === void 0 ? void 0 : (_root$source$input = _root$source.input) === null || _root$source$input === void 0 ? void 0 : _root$source$input.file) !== null && _root$source$input$fi !== void 0 ? _root$source$input$fi : '';
const isCssModule = runAsCssModule || file.includes('.module.');
let fileFallbackHash = null;
if (scopeHash === 'auto') {
const scopeHashFromFile = findPathToScopeHash(file);
if (scopeHashFromFile) {
const content = getScopeHashFromFile(scopeHashFromFile);
if (!content.includes(' ')) {
fileFallbackHash = content;
}
}
}
const fallbackHash = fileFallbackHash || currentFallbackHash;
const givenScope = typeof scopeHash === 'function' ? scopeHash(file) : scopeHash;
const scopeWithFallback = (scopeHash === 'auto' ? fallbackHash : givenScope) || fallbackHash;
const sharedHashes = typeof sharedScopeHash === 'function' ? sharedScopeHash(file) : undefined;
root.walkRules(rule => {
var _rule$parent;
countBefore += 1;
const selectorBefore = rule.selector;
if (((_rule$parent = rule.parent) === null || _rule$parent === void 0 ? void 0 : _rule$parent.type) === 'atrule' && ['keyframes'].includes(rule.parent.name)) {
return;
}
if (isCssModule && rule.selectors.length === 1 && rule.selectors[0].trim() === ':global') {
const hasNestedRules = Array.isArray(rule.nodes) && rule.nodes.some(n => n.type === 'rule');
if (!hasNestedRules) {
rule.selector = `:global(.${scopeWithFallback})`;
}
return;
}
if (rule.selectors.length === 1 && (rule.selectors[0].trim() === ':root' || isCssModule && rule.selectors[0].trim() === ':global :root')) {
const scopes = [scopeWithFallback];
if (Array.isArray(sharedHashes)) {
for (const hash of sharedHashes) {
if (!scopes.includes(hash)) {
scopes.push(hash);
}
}
}
if (isCssModule) {
const classList = scopes.map(s => `.${s}`).join(', ');
rule.selector = `:global(${classList})`;
} else {
rule.selector = scopes.map(s => `.${s}`).join(', ');
}
return;
}
const processSelector = (selector, scope) => selectorParser(selectors => {
selectors.each(group => {
var _group$nodes$, _group$nodes$2, _group$nodes$3, _group$nodes$i, _group$nodes$i3;
if (isGlobalSelector(group.nodes)) {
return;
}
if (group.parent && group.parent.type === 'pseudo' && group.parent.value === ':global') {
if (isGlobalSelector(group.nodes)) {
return;
}
}
group.nodes.forEach(n => {
if (n.type === 'class' && replaceClassNamesMap !== null && replaceClassNamesMap !== void 0 && replaceClassNamesMap.has(n.value)) {
n.value = replaceClassNamesMap.get(n.value);
}
});
const alreadyWrappedOrShouldSkip = group.nodes.some(n => {
if (n.type === 'pseudo' && n.value === ':global') {
var _n$nodes, _n$nodes$, _n$nodes$$nodes;
return (_n$nodes = n.nodes) === null || _n$nodes === void 0 ? void 0 : (_n$nodes$ = _n$nodes[0]) === null || _n$nodes$ === void 0 ? void 0 : (_n$nodes$$nodes = _n$nodes$.nodes) === null || _n$nodes$$nodes === void 0 ? void 0 : _n$nodes$$nodes.some(sub => sub.type === 'class' && (sub.value === scope || sub.value.startsWith(defaultScopeHash) || skipClassNamesSet.has(sub.value)));
}
return n.type === 'class' && (n.value === scope || n.value.startsWith(defaultScopeHash) || skipClassNamesSet.has(n.value)) || n.type === 'attribute' && skipClassNamesSet.has(n.attribute);
});
let hadGlobalWrapper = false;
let didRemoveExistingScope = false;
if (alreadyWrappedOrShouldSkip) {
if (scopeHash !== 'auto' && givenScope) {
for (let i = 0; i < group.nodes.length; i++) {
const n = group.nodes[i];
if (n.type === 'pseudo' && n.value === ':global') {
var _n$nodes2, _n$nodes$every, _n$nodes3;
hadGlobalWrapper = true;
(_n$nodes2 = n.nodes) === null || _n$nodes2 === void 0 ? void 0 : _n$nodes2.forEach(sel => {
var _sel$nodes;
sel.nodes = (_sel$nodes = sel.nodes) === null || _sel$nodes === void 0 ? void 0 : _sel$nodes.filter(sub => {
const match = sub.type === 'class' && sub.value.startsWith(defaultScopeHash);
if (match) {
didRemoveExistingScope = true;
}
return !match;
});
});
const isEmpty = (_n$nodes$every = (_n$nodes3 = n.nodes) === null || _n$nodes3 === void 0 ? void 0 : _n$nodes3.every(sel => {
var _sel$nodes2;
return ((_sel$nodes2 = sel.nodes) === null || _sel$nodes2 === void 0 ? void 0 : _sel$nodes2.length) === 0;
})) !== null && _n$nodes$every !== void 0 ? _n$nodes$every : false;
if (didRemoveExistingScope) {
if (isEmpty) group.nodes.splice(i, 1);
break;
}
}
if (n.type === 'class' && n.value.startsWith(defaultScopeHash)) {
group.nodes.splice(i, 1);
didRemoveExistingScope = true;
break;
}
}
}
if (alreadyWrappedOrShouldSkip && !didRemoveExistingScope) {
return;
}
}
let i = 0;
if (((_group$nodes$ = group.nodes[0]) === null || _group$nodes$ === void 0 ? void 0 : _group$nodes$.type) === 'pseudo' && group.nodes[0].value === ':global' && (!group.nodes[0].nodes || group.nodes[0].nodes.length === 0) && ((_group$nodes$2 = group.nodes[1]) === null || _group$nodes$2 === void 0 ? void 0 : _group$nodes$2.type) === 'combinator' && ((_group$nodes$3 = group.nodes[2]) === null || _group$nodes$3 === void 0 ? void 0 : _group$nodes$3.type) === 'tag' && ['html', 'body'].includes(group.nodes[2].value)) {
i = 2;
while (group.nodes[i] && group.nodes[i].type === 'combinator') {
i++;
}
}
if (((_group$nodes$i = group.nodes[i]) === null || _group$nodes$i === void 0 ? void 0 : _group$nodes$i.type) === 'tag' && group.nodes[i].value === 'html') {
var _group$nodes$i2, _group$nodes;
i++;
while (group.nodes[i] && (group.nodes[i].type === 'pseudo' || group.nodes[i].type === 'attribute')) {
i++;
}
if (((_group$nodes$i2 = group.nodes[i]) === null || _group$nodes$i2 === void 0 ? void 0 : _group$nodes$i2.type) === 'combinator' && ((_group$nodes = group.nodes[i + 1]) === null || _group$nodes === void 0 ? void 0 : _group$nodes.type) === 'tag' && group.nodes[i + 1].value === 'body') {
i += 2;
while (group.nodes[i] && (group.nodes[i].type === 'pseudo' || group.nodes[i].type === 'attribute')) {
i++;
}
}
} else if (((_group$nodes$i3 = group.nodes[i]) === null || _group$nodes$i3 === void 0 ? void 0 : _group$nodes$i3.type) === 'tag' && group.nodes[i].value === 'body') {
i++;
while (group.nodes[i] && (group.nodes[i].type === 'pseudo' || group.nodes[i].type === 'attribute')) {
i++;
}
}
const insertIndex = i;
const space = selectorParser.combinator({
value: ' '
});
const scopeClass = selectorParser.className({
value: scope
});
const asGlobal = hadGlobalWrapper || isCssModule;
if (asGlobal) {
const inner = selectorParser.selector();
inner.append(scopeClass);
const globalPseudo = selectorParser.pseudo({
value: ':global',
nodes: [inner]
});
group.nodes.splice(insertIndex, 0, space, globalPseudo, space);
} else {
group.nodes.splice(insertIndex, 0, space, scopeClass, space);
}
});
}).processSync(selector).replace(/\s+/g, ' ').trim();
const processedSelectors = [];
rule.selectors.forEach(selector => {
if (rule.parent && rule.parent.type === 'rule' && rule.parent.selector && rule.parent.selector.trim() === ':global') {
const ast = selectorParser().astSync(selector.trim());
const isAllGlobal = ast.nodes.some(group => isGlobalSelector(group.nodes));
if (isAllGlobal) {
processedSelectors.push(selector);
return;
}
}
if (selector.includes('[skip-isolation] ')) {
processedSelectors.push(selector.replace(/\[skip-isolation\]\s*/g, '').trim());
return;
}
if (selector.includes('[scope-placeholder]')) {
selector = selector.replace(/\[scope-placeholder\]/g, isCssModule ? `:global(.${scopeWithFallback})` : `.${scopeWithFallback}`);
}
processedSelectors.push(processSelector(selector, scopeWithFallback));
if (Array.isArray(sharedHashes) && sharedHashes.length > 0) {
const uniqueSharedHashes = sharedHashes.filter(hash => hash !== scopeWithFallback);
uniqueSharedHashes.forEach(hash => {
processedSelectors.push(processSelector(selector, hash));
});
}
});
rule.selectors = processedSelectors;
if (verbose) {
console.log(`
✨ @dnb/eufemia/plugins/postcss-isolated-style-scope
Scope: ${givenScope} → ${scopeWithFallback}
${Array.isArray(sharedHashes) ? `Shared Scopes: ${sharedHashes.join(', ')}` : ''}
Before: ${selectorBefore.replace(/\n/g, ' ')}
After: ${rule.selector.replace(/\n/g, ' ')}
File: ${file}
Settings: ${JSON.stringify({
isCssModule
}, null, 2)}
`);
}
countAfter += 1;
});
if (verbose) {
console.log(`
✨ @dnb/eufemia/plugins/postcss-isolated-style-scope
- File: ${path.relative(process.cwd(), file)}
- Selectors processed: ${countBefore} → ${countAfter}
`);
}
}
};
};
postcssIsolateStyle.postcss = true;
export default postcssIsolateStyle;
function isGlobalSelector(nodes) {
if (!nodes || nodes.length === 0) return false;
const parts = nodes.filter(n => !(n.type === 'combinator' && n.value.trim() === ''));
if (parts.length === 1 && parts[0].type === 'pseudo' && parts[0].value === ':global' && parts[0].nodes) {
return isGlobalSelector(parts[0].nodes[0].nodes);
}
if (parts.length >= 2 && parts[0].type === 'pseudo' && parts[0].value === ':global') {
return isGlobalSelector(parts.slice(1));
}
if (parts[0].type === 'tag' && parts[0].value === 'html') {
if (parts.length === 1) return true;
if (parts.length === 2 && parts[1].type === 'tag' && parts[1].value === 'body') {
return true;
}
if (parts.length === 2 && parts[1].type === 'universal') {
return true;
}
}
if (parts.length === 1 && parts[0].type === 'tag' && parts[0].value === 'body') {
return true;
}
if (parts.length === 2 && parts[0].type === 'pseudo' && parts[0].value === ':global' && parts[1].type === 'tag' && ['html', 'body'].includes(parts[1].value)) {
return true;
}
return false;
}
//# sourceMappingURL=isolated-style-scope-plugin.js.map