diff --git a/index.js b/index.js index c6f963f..2acedea 100644 --- a/index.js +++ b/index.js @@ -3,9 +3,24 @@ const postcss = require('postcss'); const selectorParser = require('postcss-selector-parser'); const valueParser = require('postcss-value-parser'); +const { extractICSS } = require('icss-utils'); const isSpacing = node => node.type === 'combinator' && node.value === ' '; +function getImportLocalAliases(icssImports) { + const localAliases = new Map(); + Object.keys(icssImports).forEach(key => { + Object.keys(icssImports[key]).forEach(prop => { + localAliases.set(prop, icssImports[key][prop]); + }); + }); + return localAliases; +} + +function maybeLocalizeValue(value, localAliasMap) { + if (localAliasMap.has(value)) return value; +} + function normalizeNodeArray(nodes) { const array = []; @@ -25,7 +40,7 @@ function normalizeNodeArray(nodes) { return array; } -function localizeNode(rule, mode, options) { +function localizeNode(rule, mode, localAliasMap) { const isScopePseudo = node => node.value === ':local' || node.value === ':global'; @@ -191,7 +206,14 @@ function localizeNode(rule, mode, options) { } case 'id': case 'class': { - if (!context.global) { + if (context.global) { + break; + } + + const isImportedValue = localAliasMap.has(node.value); + const isImportedWithExplicitScope = isImportedValue && context.explicit; + + if (!isImportedValue || isImportedWithExplicitScope) { const innerNode = node.clone(); innerNode.spaces = { before: '', after: '' }; @@ -231,8 +253,10 @@ function localizeDeclNode(node, context) { switch (node.type) { case 'word': if (context.localizeNextItem) { - node.value = ':local(' + node.value + ')'; - context.localizeNextItem = false; + if (!context.localAliasMap.has(node.value)) { + node.value = ':local(' + node.value + ')'; + context.localizeNextItem = false; + } } break; @@ -360,6 +384,7 @@ function localizeAnimationShorthandDeclValues(decl, context) { options: context.options, global: context.global, localizeNextItem: shouldParseAnimationName && !context.global, + localAliasMap: context.localAliasMap, }; return localizeDeclNode(node, subContext); }); @@ -374,6 +399,7 @@ function localizeDeclValues(localize, decl, context) { options: context.options, global: context.global, localizeNextItem: localize && !context.global, + localAliasMap: context.localAliasMap, }; nodes[index] = localizeDeclNode(node, subContext); }); @@ -423,6 +449,9 @@ module.exports = postcss.plugin('postcss-modules-local-by-default', function( const globalMode = options && options.mode === 'global'; return function(css) { + const { icssImports } = extractICSS(css, false); + const localAliasMap = getImportLocalAliases(icssImports); + css.walkAtRules(function(atrule) { if (/keyframes$/i.test(atrule.name)) { const globalMatch = /^\s*:global\s*\((.+)\)\s*$/.exec(atrule.params); @@ -440,10 +469,12 @@ module.exports = postcss.plugin('postcss-modules-local-by-default', function( atrule.params = localMatch[0]; globalKeyframes = false; } else if (!globalMode) { - atrule.params = ':local(' + atrule.params + ')'; + if (atrule.params && !localAliasMap.has(atrule.params)) + atrule.params = ':local(' + atrule.params + ')'; } atrule.walkDecls(function(decl) { localizeDecl(decl, { + localAliasMap, options: options, global: globalKeyframes, }); @@ -452,6 +483,7 @@ module.exports = postcss.plugin('postcss-modules-local-by-default', function( atrule.nodes.forEach(function(decl) { if (decl.type === 'decl') { localizeDecl(decl, { + localAliasMap, options: options, global: globalMode, }); @@ -478,9 +510,10 @@ module.exports = postcss.plugin('postcss-modules-local-by-default', function( return; } - const context = localizeNode(rule, options.mode); + const context = localizeNode(rule, options.mode, localAliasMap); context.options = options; + context.localAliasMap = localAliasMap; if (pureMode && context.hasPureGlobals) { throw rule.error( diff --git a/package.json b/package.json index a1f0f2a..77c54cc 100644 --- a/package.json +++ b/package.json @@ -23,6 +23,7 @@ "trailingComma": "es5" }, "dependencies": { + "icss-utils": "^4.0.0", "postcss": "^7.0.6", "postcss-selector-parser": "^6.0.0", "postcss-value-parser": "^3.3.1" diff --git a/test.js b/test.js index c71bea6..bdcb9ce 100644 --- a/test.js +++ b/test.js @@ -529,8 +529,92 @@ const tests = [ }, { should: 'not ignore custom property set', - input: ':root { --title-align: center; --sr-only: { position: absolute; } }', - expected: ':root { --title-align: center; --sr-only: { position: absolute; } }', + input: + ':root { --title-align: center; --sr-only: { position: absolute; } }', + expected: + ':root { --title-align: center; --sr-only: { position: absolute; } }', + }, + /** + * Imported aliases + */ + { + should: 'not localize imported alias', + input: ` + :import(foo) { a_value: some-value; } + + .foo > .a_value { } + `, + expected: ` + :import(foo) { a_value: some-value; } + + :local(.foo) > .a_value { } + `, + }, + { + should: 'not localize nested imported alias', + input: ` + :import(foo) { a_value: some-value; } + + .foo > .a_value > .bar { } + `, + expected: ` + :import(foo) { a_value: some-value; } + + :local(.foo) > .a_value > :local(.bar) { } + `, + }, + + { + should: 'ignore imported in explicit local', + input: ` + :import(foo) { a_value: some-value; } + + :local(.a_value) { } + `, + expected: ` + :import(foo) { a_value: some-value; } + + :local(.a_value) { } + `, + }, + { + should: 'escape local context with explict global', + input: ` + :import(foo) { a_value: some-value; } + + :local .foo :global(.a_value) .bar { } + `, + expected: ` + :import(foo) { a_value: some-value; } + + :local(.foo) .a_value :local(.bar) { } + `, + }, + { + should: 'respect explicit local', + input: ` + :import(foo) { a_value: some-value; } + + .a_value :local .a_value .foo :global .a_value { } + `, + expected: ` + :import(foo) { a_value: some-value; } + + .a_value :local(.a_value) :local(.foo) .a_value { } + `, + }, + { + should: 'not localize imported animation-name', + input: ` + :import(file) { a_value: some-value; } + + .foo { animation-name: a_value; } + `, + expected: ` + :import(file) { a_value: some-value; } + + :local(.foo) { animation-name: a_value; } + `, }, ]; diff --git a/yarn.lock b/yarn.lock index 62bce72..90d5743 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1221,6 +1221,13 @@ iconv-lite@^0.4.24, iconv-lite@^0.4.4: dependencies: safer-buffer ">= 2.1.2 < 3" +icss-utils@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/icss-utils/-/icss-utils-4.0.0.tgz#d52cf4bcdcfa1c45c2dbefb4ffdf6b00ef608098" + integrity sha512-bA/xGiwWM17qjllIs9X/y0EjsB7e0AV08F3OL8UPsoNkNRibIuu8f1eKTnQ8QO1DteKKTxTUAn+IEWUToIwGOA== + dependencies: + postcss "^7.0.5" + ignore-walk@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/ignore-walk/-/ignore-walk-3.0.1.tgz#a83e62e7d272ac0e3b551aaa82831a19b69f82f8" @@ -2098,7 +2105,7 @@ postcss-value-parser@^3.3.1: resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz#9ff822547e2893213cf1c30efa51ac5fd1ba8281" integrity sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ== -postcss@^7.0.6: +postcss@^7.0.5, postcss@^7.0.6: version "7.0.14" resolved "https://registry.yarnpkg.com/postcss/-/postcss-7.0.14.tgz#4527ed6b1ca0d82c53ce5ec1a2041c2346bbd6e5" integrity sha512-NsbD6XUUMZvBxtQAJuWDJeeC4QFsmWsfozWxCJPWf3M55K9iu2iMDaKqyoOdTJ1R4usBXuxlVFAIo8rZPQD4Bg==