diff --git a/package.json b/package.json index ac4ef21..c68b6f2 100644 --- a/package.json +++ b/package.json @@ -56,7 +56,7 @@ "strip-indent": "^2.0.0" }, "dependencies": { - "icss-replace-symbols": "^1.1.0", + "icss-utils": "^2.0.0", "postcss": "^6.0.1" } } diff --git a/src/index.js b/src/index.js index 2ebd59c..d839301 100644 --- a/src/index.js +++ b/src/index.js @@ -1,63 +1,41 @@ -const postcss = require('postcss') -const { default: replaceSymbols, replaceAll } = require('icss-replace-symbols') +import postcss from 'postcss' +import { + replaceSymbols, + replaceValueSymbols, + extractICSS, + createICSSRules +} from 'icss-utils' const matchImports = /^(.+?|\([\s\S]+?\))\s+from\s+("[^"]*"|'[^']*'|[\w-]+)$/ const matchValueDefinition = /(?:\s+|^)([\w-]+):?\s+(.+?)\s*$/g const matchImport = /^([\w-]+)(?:\s+as\s+([\w-]+))?/ -const addImportsRules = (css, imports) => { - const rules = imports.map(({ path, aliases }) => { - const declarations = Object.keys(aliases).map(key => - postcss.decl({ - prop: key, - value: aliases[key], - raws: { before: '\n ' } - }) - ) - return postcss - .rule({ - selector: `:import(${path})`, - raws: { after: '\n' } - }) - .append(declarations) - }) - css.prepend(rules) -} - -const addExportsRule = (css, exports) => { - const declarations = Object.keys(exports).map(key => - postcss.decl({ - prop: key, - value: exports[key], - raws: { before: '\n ' } - }) - ) - const rule = postcss - .rule({ - selector: `:export`, - raws: { after: '\n' } - }) - .append(declarations) - css.prepend(rule) -} - -let importIndex = 0 -const createImportedName = importName => - `i__const_${importName.replace(/\W/g, '_')}_${importIndex++}` +// 'i' prefix to prevent postcss parsing "_" as css hook +const getAliasName = (name, index) => + `i__value_${name.replace(/\W/g, '_')}_${index}` module.exports = postcss.plugin('postcss-modules-values', () => ( css, result ) => { - let importAliases = [] - let definitions = {} + const { icssImports, icssExports } = extractICSS(css) + let importIndex = 0 + const createImportedName = (path, name) => { + const importedName = getAliasName(name, importIndex) + if (icssImports[path] && icssImports[path][importedName]) { + importIndex += 1 + return createImportedName(path, name) + } + importIndex += 1 + return importedName + } const addDefinition = atRule => { let matches while ((matches = matchValueDefinition.exec(atRule.params))) { let [, key, value] = matches // Add to the definitions, knowing that values can refer to each other - definitions[key] = replaceAll(definitions, value) + icssExports[key] = replaceValueSymbols(value, icssExports) atRule.remove() } } @@ -67,16 +45,16 @@ module.exports = postcss.plugin('postcss-modules-values', () => ( if (matches) { let [, aliasesString, path] = matches // We can use constants for path names - if (definitions[path]) path = definitions[path] + if (icssExports[path]) path = icssExports[path] let aliases = aliasesString .replace(/^\(\s*([\s\S]+)\s*\)$/, '$1') .split(/\s*,\s*/) .map(alias => { let tokens = matchImport.exec(alias) if (tokens) { - let [, /*match*/ theirName, myName = theirName] = tokens - let importedName = createImportedName(myName) - definitions[myName] = importedName + let [, theirName, myName = theirName] = tokens + let importedName = createImportedName(path, myName) + icssExports[myName] = importedName return { theirName, importedName } } else { throw new Error(`@import statement "${alias}" is invalid!`) @@ -86,7 +64,7 @@ module.exports = postcss.plugin('postcss-modules-values', () => ( acc[importedName] = theirName return acc }, {}) - importAliases.push({ path, aliases }) + icssImports[path] = Object.assign({}, icssImports[path], aliases) atRule.remove() } } @@ -104,13 +82,9 @@ module.exports = postcss.plugin('postcss-modules-values', () => ( } }) - /* If we have no definitions, don't continue */ - if (Object.keys(definitions).length === 0) return - - /* Perform replacements */ - replaceSymbols(css, definitions) + if (Object.keys(icssExports).length === 0) return - addExportsRule(css, definitions) + replaceSymbols(css, icssExports) - addImportsRules(css, importAliases) + css.prepend(createICSSRules(icssImports, icssExports)) }) diff --git a/test/test.js b/test/test.js index 4bb7507..aa5654a 100644 --- a/test/test.js +++ b/test/test.js @@ -76,10 +76,10 @@ test('should import and re-export a simple constant', () => { ).resolves.toEqual( strip(` :import("./colors.css") { - i__const_red_0: red + i__value_red_0: red } :export { - red: i__const_red_0 + red: i__value_red_0 } `) ) @@ -94,12 +94,12 @@ test('should import a simple constant and replace usages', () => { ).resolves.toEqual( strip(` :import("./colors.css") { - i__const_red_1: red; + i__value_red_0: red; } :export { - red: i__const_red_1; + red: i__value_red_0; } - .foo { color: i__const_red_1; } + .foo { color: i__value_red_0; } `) ) }) @@ -113,12 +113,12 @@ test('should import and alias a constant and replace usages', () => { ).resolves.toEqual( strip(` :import("./colors.css") { - i__const_red_2: blue; + i__value_red_0: blue; } :export { - red: i__const_red_2; + red: i__value_red_0; } - .foo { color: i__const_red_2; } + .foo { color: i__value_red_0; } `) ) }) @@ -133,15 +133,15 @@ test('should import multiple from a single file', () => { ).resolves.toEqual( strip(` :import("./colors.css") { - i__const_blue_3: blue; - i__const_red_4: red; + i__value_blue_0: blue; + i__value_red_1: red; } :export { - blue: i__const_blue_3; - red: i__const_red_4; + blue: i__value_blue_0; + red: i__value_red_1; } - .foo { color: i__const_red_4; } - .bar { color: i__const_blue_3 } + .foo { color: i__value_red_1; } + .bar { color: i__value_blue_0 } `) ) }) @@ -154,11 +154,11 @@ test('should import from a definition', () => { ).resolves.toEqual( strip(` :import("./colors.css") { - i__const_red_5: red + i__value_red_0: red } :export { colors: "./colors.css"; - red: i__const_red_5 + red: i__value_red_0 } `) ) @@ -172,10 +172,10 @@ test('should only allow values for paths if defined in the right order', () => { ).resolves.toEqual( strip(` :import(colors) { - i__const_red_6: red + i__value_red_0: red } :export { - red: i__const_red_6; + red: i__value_red_0; colors: "./colors.css" } `) @@ -226,14 +226,14 @@ test('should preserve import order', () => { ).resolves.toEqual( strip(` :import("./a.css") { - i__const_a_7: a + i__value_a_0: a } :import("./b.css") { - i__const_b_8: b + i__value_b_1: b } :export { - a: i__const_a_7; - b: i__const_b_8 + a: i__value_a_0; + b: i__value_b_1 } `) ) @@ -247,12 +247,12 @@ test('should allow custom-property-style names', () => { ).resolves.toEqual( strip(` :import("./colors.css") { - i__const___red_9: --red; + i__value___red_0: --red; } :export { - --red: i__const___red_9; + --red: i__value___red_0; } - .foo { color: i__const___red_9; } + .foo { color: i__value___red_0; } `) ) }) @@ -306,15 +306,15 @@ test('should import multiple from a single file on multiple lines', () => { ).resolves.toEqual( strip(` :import("./colors.css") { - i__const_blue_10: blue; - i__const_red_11: red; + i__value_blue_0: blue; + i__value_red_1: red; } :export { - blue: i__const_blue_10; - red: i__const_red_11; + blue: i__value_blue_0; + red: i__value_red_1; } - .foo { color: i__const_red_11; } - .bar { color: i__const_blue_10 } + .foo { color: i__value_red_1; } + .bar { color: i__value_blue_0 } `) ) }) @@ -348,3 +348,49 @@ test('should allow values with nested parantheses', () => { `) ) }) + +test('reuse existing :import with same name and :export', () => { + return expect( + runCSS(` + :import('./colors.css') { + i__some_import: blue; + } + :export { + b: i__c; + } + @value a from './colors.css'; + `) + ).resolves.toEqual( + strip(` + :import('./colors.css') { + i__some_import: blue; + i__value_a_0: a + } + :export { + b: i__c; + a: i__value_a_0 + } + `) + ) +}) + +test('prevent imported names collision', () => { + return expect( + runCSS(` + :import(colors) { + i__value_a_0: a; + } + @value a from colors; + `) + ).resolves.toEqual( + strip(` + :import(colors) { + i__value_a_0: a; + i__value_a_1: a + } + :export { + a: i__value_a_1 + } + `) + ) +}) diff --git a/yarn.lock b/yarn.lock index bb92687..929966c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1370,9 +1370,11 @@ iconv-lite@0.4.13: version "0.4.13" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.13.tgz#1f88aba4ab0b1508e8312acc39345f36e992e2f2" -icss-replace-symbols@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/icss-replace-symbols/-/icss-replace-symbols-1.1.0.tgz#06ea6f83679a7749e386cfe1fe812ae5db223ded" +icss-utils@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/icss-utils/-/icss-utils-2.0.0.tgz#9eb8432af871adc003e4ac7a574d24169398317d" + dependencies: + postcss "^6.0.1" indent-string@^2.1.0: version "2.1.0"