diff --git a/package.json b/package.json index 227cf2c..563f0ba 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,7 @@ "autotest": "chokidar src test -c 'npm test'", "cover": "nyc mocha", "travis": "yarn lint && yarn cover", - "prepublish": "yarn test" + "prepublishOnly": "yarn test" }, "repository": { "type": "git", @@ -34,12 +34,12 @@ "chokidar-cli": "^1.0.1", "codecov.io": "^0.1.2", "coveralls": "^3.0.2", - "mocha": "^5.2.0", "eslint": "^5.9.0", + "mocha": "^5.2.0", "nyc": "^13.1.0" }, "dependencies": { - "icss-replace-symbols": "^1.1.0", + "icss-utils": "^4.0.0", "postcss": "^7.0.6" } } diff --git a/src/index.js b/src/index.js index 595c694..9c856bf 100644 --- a/src/index.js +++ b/src/index.js @@ -1,75 +1,74 @@ -'use strict'; +'use strict' -const postcss = require('postcss'); -const ICSSReplaceSymbols = require('icss-replace-symbols'); -const replaceSymbols = require('icss-replace-symbols'); +const postcss = require('postcss') +const ICSSUtils = require('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 matchImports = /^(.+?|\([\s\S]+?\))\s+from\s+("[^"]*"|'[^']*'|[\w-]+)$/ +const matchValueDefinition = /(?:\s+|^)([\w-]+):?\s+(.+?)\s*$/g +const matchImport = /^([\w-]+)(?:\s+as\s+([\w-]+))?/ -let options = {}; -let importIndex = 0; +let options = {} +let importIndex = 0 let createImportedName = (options && options.createImportedName) || ((importName /*, path*/) => - `i__const_${importName.replace(/\W/g, '_')}_${importIndex++}`); + `i__const_${importName.replace(/\W/g, '_')}_${importIndex++}`) module.exports = postcss.plugin( 'postcss-modules-values', () => (css, result) => { - const importAliases = []; - const definitions = {}; + const importAliases = [] + const definitions = {} const addDefinition = atRule => { - let matches; + let matches while ((matches = matchValueDefinition.exec(atRule.params))) { - let [, /*match*/ key, value] = matches; + let [, /*match*/ key, value] = matches // Add to the definitions, knowing that values can refer to each other - definitions[key] = replaceSymbols.replaceAll(definitions, value); - atRule.remove(); + definitions[key] = ICSSUtils.replaceValueSymbols(value, definitions) + atRule.remove() } - }; + } const addImport = atRule => { - const matches = matchImports.exec(atRule.params); + const matches = matchImports.exec(atRule.params) if (matches) { - let [, /*match*/ aliases, path] = matches; + let [, /*match*/ aliases, path] = matches // We can use constants for path names if (definitions[path]) { - path = definitions[path]; + path = definitions[path] } const imports = aliases .replace(/^\(\s*([\s\S]+)\s*\)$/, '$1') .split(/\s*,\s*/) .map(alias => { - const tokens = matchImport.exec(alias); + const tokens = matchImport.exec(alias) if (tokens) { - const [, /*match*/ theirName, myName = theirName] = tokens; - const importedName = createImportedName(myName); - definitions[myName] = importedName; - return { theirName, importedName }; + const [, /*match*/ theirName, myName = theirName] = tokens + const importedName = createImportedName(myName) + definitions[myName] = importedName + return { theirName, importedName } } else { - throw new Error(`@import statement "${alias}" is invalid!`); + throw new Error(`@import statement "${alias}" is invalid!`) } - }); - importAliases.push({ path, imports }); - atRule.remove(); + }) + importAliases.push({ path, imports }) + atRule.remove() } - }; + } /* Look at all the @value statements and treat them as locals or as imports */ css.walkAtRules('value', atRule => { if (matchImports.exec(atRule.params)) { - addImport(atRule); + addImport(atRule) } else { if (atRule.params.indexOf('@value') !== -1) { - result.warn('Invalid value definition: ' + atRule.params); + result.warn('Invalid value definition: ' + atRule.params) } - addDefinition(atRule); + addDefinition(atRule) } - }); + }) /* We want to export anything defined by now, but don't add it to the CSS yet or it well get picked up by the replacement stuff */ @@ -77,43 +76,43 @@ module.exports = postcss.plugin( postcss.decl({ value: definitions[key], prop: key, - raws: { before: '\n ' } + raws: { before: '\n ' }, }) - ); + ) /* If we have no definitions, don't continue */ if (!Object.keys(definitions).length) { - return; + return } /* Perform replacements */ - ICSSReplaceSymbols.default(css, definitions); + ICSSUtils.replaceSymbols(css, definitions) /* Add export rules if any */ if (exportDeclarations.length > 0) { const exportRule = postcss.rule({ selector: ':export', - raws: { after: '\n' } - }); - exportRule.append(exportDeclarations); - css.prepend(exportRule); + raws: { after: '\n' }, + }) + exportRule.append(exportDeclarations) + css.prepend(exportRule) } /* Add import rules */ importAliases.reverse().forEach(({ path, imports }) => { const importRule = postcss.rule({ selector: `:import(${path})`, - raws: { after: '\n' } - }); + raws: { after: '\n' }, + }) imports.forEach(({ theirName, importedName }) => { importRule.append({ value: theirName, prop: importedName, - raws: { before: '\n ' } - }); - }); + raws: { before: '\n ' }, + }) + }) - css.prepend(importRule); - }); + css.prepend(importRule) + }) } -); +) diff --git a/test/index.js b/test/index.js index 635fb01..f79e11f 100644 --- a/test/index.js +++ b/test/index.js @@ -1,73 +1,80 @@ -'use strict'; +'use strict' /* global describe, it */ -const postcss = require('postcss'); -const assert = require('assert'); +const postcss = require('postcss') +const assert = require('assert') -const constants = require('../src'); +const constants = require('../src') const test = (input, expected) => { - const processor = postcss([constants]); - assert.equal(processor.process(input).css, expected); -}; + const processor = postcss([constants]) + assert.equal(processor.process(input).css, expected) +} describe('constants', () => { it('should pass through an empty string', () => { - test('', ''); - }); + test('', '') + }) it('should export a constant', () => { - test('@value red blue;', ':export {\n red: blue\n}'); - }); + test('@value red blue;', ':export {\n red: blue\n}') + }) it('gives an error when there is no semicolon between lines', () => { - const input = '@value red blue\n@value green yellow'; - const processor = postcss([constants]); - const result = processor.process(input); - const warnings = result.warnings(); + const input = '@value red blue\n@value green yellow' + const processor = postcss([constants]) + const result = processor.process(input) + const warnings = result.warnings() - assert.equal(warnings.length, 1); + assert.equal(warnings.length, 1) assert.equal( warnings[0].text, 'Invalid value definition: red blue\n@value green yellow' - ); - }); + ) + }) it('should export a more complex constant', () => { test( '@value small (max-width: 599px);', ':export {\n small: (max-width: 599px)\n}' - ); - }); + ) + }) it('should replace constants within the file', () => { test( '@value blue red; .foo { color: blue; }', ':export {\n blue: red;\n}\n.foo { color: red; }' - ); - }); + ) + }) + + it('should replace selectors within the file', () => { + test( + '@value colorValue red; .colorValue { color: colorValue; }', + ':export {\n colorValue: red;\n}\n.red { color: red; }' + ) + }) it('should import and re-export a simple constant', () => { test( '@value red from "./colors.css";', ':import("./colors.css") {\n i__const_red_0: red\n}\n:export {\n red: i__const_red_0\n}' - ); - }); + ) + }) it('should import a simple constant and replace usages', () => { test( '@value red from "./colors.css"; .foo { color: red; }', ':import("./colors.css") {\n i__const_red_1: red;\n}\n:export {\n red: i__const_red_1;\n}\n.foo { color: i__const_red_1; }' - ); - }); + ) + }) it('should import and alias a constant and replace usages', () => { test( '@value blue as red from "./colors.css"; .foo { color: red; }', ':import("./colors.css") {\n i__const_red_2: blue;\n}\n:export {\n red: i__const_red_2;\n}\n.foo { color: i__const_red_2; }' - ); - }); + ) + }) it('should import multiple from a single file', () => { test( @@ -84,38 +91,38 @@ describe('constants', () => { } .foo { color: i__const_red_4; } .bar { color: i__const_blue_3 }` - ); - }); + ) + }) it('should import from a definition', () => { test( '@value colors: "./colors.css"; @value red from colors;', ':import("./colors.css") {\n i__const_red_5: red\n}\n' + ':export {\n colors: "./colors.css";\n red: i__const_red_5\n}' - ); - }); + ) + }) it('should only allow values for paths if defined in the right order', () => { test( '@value red from colors; @value colors: "./colors.css";', ':import(colors) {\n i__const_red_6: red\n}\n' + ':export {\n red: i__const_red_6;\n colors: "./colors.css"\n}' - ); - }); + ) + }) it('should allow transitive values', () => { test( '@value aaa: red;\n@value bbb: aaa;\n.a { color: bbb; }', ':export {\n aaa: red;\n bbb: red;\n}\n.a { color: red; }' - ); - }); + ) + }) it('should allow transitive values within calc', () => { test( '@value base: 10px;\n@value large: calc(base * 2);\n.a { margin: large; }', ':export {\n base: 10px;\n large: calc(10px * 2);\n}\n.a { margin: calc(10px * 2); }' - ); - }); + ) + }) it('should preserve import order', () => { test( @@ -123,8 +130,8 @@ describe('constants', () => { ':import("./a.css") {\n i__const_a_7: a\n}\n' + ':import("./b.css") {\n i__const_b_8: b\n}\n' + ':export {\n a: i__const_a_7;\n b: i__const_b_8\n}' - ); - }); + ) + }) it('should allow custom-property-style names', () => { test( @@ -132,8 +139,8 @@ describe('constants', () => { ':import("./colors.css") {\n i__const___red_9: --red;\n}\n' + ':export {\n --red: i__const___red_9;\n}\n' + '.foo { color: i__const___red_9; }' - ); - }); + ) + }) it('should allow all colour types', () => { test( @@ -141,8 +148,8 @@ describe('constants', () => { '.foo { color: named; background-color: 3char; border-top-color: 6char; border-bottom-color: rgba; outline-color: hsla; }', ':export {\n named: red;\n 3char: #0f0;\n 6char: #00ff00;\n rgba: rgba(34, 12, 64, 0.3);\n hsla: hsla(220, 13.0%, 18.0%, 1);\n}\n' + '.foo { color: red; background-color: #0f0; border-top-color: #00ff00; border-bottom-color: rgba(34, 12, 64, 0.3); outline-color: hsla(220, 13.0%, 18.0%, 1); }' - ); - }); + ) + }) it('should import multiple from a single file on multiple lines', () => { test( @@ -162,8 +169,8 @@ describe('constants', () => { } .foo { color: i__const_red_11; } .bar { color: i__const_blue_10 }` - ); - }); + ) + }) it('should allow definitions with commas in them', () => { test( @@ -171,13 +178,13 @@ describe('constants', () => { '.foo { box-shadow: coolShadow; }', ':export {\n coolShadow: 0 11px 15px -7px rgba(0,0,0,.2),0 24px 38px 3px rgba(0,0,0,.14);\n}\n' + '.foo { box-shadow: 0 11px 15px -7px rgba(0,0,0,.2),0 24px 38px 3px rgba(0,0,0,.14); }' - ); - }); + ) + }) it('should allow values with nested parantheses', () => { test( '@value aaa: color(red lightness(50%));', ':export {\n aaa: color(red lightness(50%))\n}' - ); - }); -}); + ) + }) +}) diff --git a/yarn.lock b/yarn.lock index 74cc004..bf92ac3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -354,6 +354,15 @@ chalk@^2.0.0, chalk@^2.1.0, chalk@^2.4.1: escape-string-regexp "^1.0.5" supports-color "^5.3.0" +chalk@^2.4.2: + version "2.4.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" + integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== + dependencies: + ansi-styles "^3.2.1" + escape-string-regexp "^1.0.5" + supports-color "^5.3.0" + chardet@^0.7.0: version "0.7.0" resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.7.0.tgz#90094849f0937f2eedc2425d0d28a9e5f0cbad9e" @@ -1141,9 +1150,12 @@ iconv-lite@^0.4.24, iconv-lite@^0.4.4: dependencies: safer-buffer ">= 2.1.2 < 3" -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@^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" @@ -2011,6 +2023,15 @@ posix-character-classes@^0.1.0: version "0.1.1" resolved "https://registry.yarnpkg.com/posix-character-classes/-/posix-character-classes-0.1.1.tgz#01eac0fe3b5af71a2a6c02feabb8c1fef7e00eab" +postcss@^7.0.5: + version "7.0.14" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-7.0.14.tgz#4527ed6b1ca0d82c53ce5ec1a2041c2346bbd6e5" + integrity sha512-NsbD6XUUMZvBxtQAJuWDJeeC4QFsmWsfozWxCJPWf3M55K9iu2iMDaKqyoOdTJ1R4usBXuxlVFAIo8rZPQD4Bg== + dependencies: + chalk "^2.4.2" + source-map "^0.6.1" + supports-color "^6.1.0" + postcss@^7.0.6: version "7.0.6" resolved "https://registry.yarnpkg.com/postcss/-/postcss-7.0.6.tgz#6dcaa1e999cdd4a255dcd7d4d9547f4ca010cdc2" @@ -2505,6 +2526,13 @@ supports-color@^5.3.0, supports-color@^5.4.0, supports-color@^5.5.0: dependencies: has-flag "^3.0.0" +supports-color@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-6.1.0.tgz#0764abc69c63d5ac842dd4867e8d025e880df8f3" + integrity sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ== + dependencies: + has-flag "^3.0.0" + table@^5.0.2: version "5.1.0" resolved "https://registry.yarnpkg.com/table/-/table-5.1.0.tgz#69a54644f6f01ad1628f8178715b408dc6bf11f7"