UNPKG

@dnb/eufemia

Version:

DNB Eufemia Design System UI Library

265 lines 13.4 kB
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