From fdc802fb8ef525d3f35510681d375813357e57d7 Mon Sep 17 00:00:00 2001 From: Alexander Krasnoyarov Date: Thu, 8 Oct 2020 22:45:55 +0300 Subject: [PATCH] chore(release): 4.0.0-rc.4 (#14) --- CHANGELOG.md | 8 ++ package.json | 2 +- src/index.js | 124 +++++++++++++------------ test/index.test.js | 223 +++++++++++++++++++++++++-------------------- 4 files changed, 198 insertions(+), 159 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0a07924..b27502f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org/). +## [4.0.0-rc.4](https://github.com/postcss-modules-local-by-default/compare/v4.0.0-rc.3...v4.0.0-rc.4) - 2020-10-08 + +### Fixes + +- perf +- compatibility with empty custom properties +- works with `options.createImportedName` + ## [4.0.0-rc.3](https://github.com/postcss-modules-local-by-default/compare/v4.0.0-rc.2...v4.0.0-rc.3) - 2020-10-08 ### BREAKING CHANGE diff --git a/package.json b/package.json index e0c4d2d..3455a2c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "postcss-modules-values", - "version": "4.0.0-rc.3", + "version": "4.0.0-rc.4", "description": "PostCSS plugin for CSS Modules to pass arbitrary values between your module files", "main": "src/index.js", "files": [ diff --git a/src/index.js b/src/index.js index 4ad6360..412953c 100644 --- a/src/index.js +++ b/src/index.js @@ -3,17 +3,16 @@ const ICSSUtils = require("icss-utils"); const matchImports = /^(.+?|\([\s\S]+?\))\s+from\s+("[^"]*"|'[^']*'|[\w-]+)$/; -const matchValueDefinition = /(?:\s+|^)([\w-]+):?\s+(.+?)\s*$/g; +const matchValueDefinition = /(?:\s+|^)([\w-]+):?(.*?)$/; const matchImport = /^([\w-]+)(?:\s+as\s+([\w-]+))?/; -let options = {}; -let importIndex = 0; -let createImportedName = - (options && options.createImportedName) || - ((importName /*, path*/) => - `i__const_${importName.replace(/\W/g, "_")}_${importIndex++}`); +module.exports = (options) => { + let importIndex = 0; + let createImportedName = + (options && options.createImportedName) || + ((importName /*, path*/) => + `i__const_${importName.replace(/\W/g, "_")}_${importIndex++}`); -module.exports = () => { return { postcssPlugin: "postcss-modules-values", prepare(result) { @@ -21,65 +20,72 @@ module.exports = () => { const definitions = {}; return { - /* Look at all the @value statements and treat them as locals or as imports */ AtRule: { value(atRule) { - if (matchImports.exec(atRule.params)) { - const matches = matchImports.exec(atRule.params); - - if (matches) { - let [, /*match*/ aliases, path] = matches; - - // We can use constants for path names - if (definitions[path]) { - path = definitions[path]; - } - - const imports = aliases - .replace(/^\(\s*([\s\S]+)\s*\)$/, "$1") - .split(/\s*,\s*/) - .map((alias) => { - const tokens = matchImport.exec(alias); - - if (tokens) { - 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!` - ); - } - }); - - importAliases.push({ path, imports }); - - atRule.remove(); - } - } else { - if (atRule.params.indexOf("@value") !== -1) { - result.warn("Invalid value definition: " + atRule.params); + const matches = atRule.params.match(matchImports); + + if (matches) { + let [, /*match*/ aliases, path] = matches; + + // We can use constants for path names + if (definitions[path]) { + path = definitions[path]; } - let matches; + const imports = aliases + .replace(/^\(\s*([\s\S]+)\s*\)$/, "$1") + .split(/\s*,\s*/) + .map((alias) => { + const tokens = matchImport.exec(alias); - while ((matches = matchValueDefinition.exec(atRule.params))) { - let [, /*match*/ key, value] = matches; + if (tokens) { + 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!`); + } + }); - // Add to the definitions, knowing that values can refer to each other - definitions[key] = ICSSUtils.replaceValueSymbols( - value, - definitions - ); + importAliases.push({ path, imports }); - atRule.remove(); - } + atRule.remove(); + + return; + } + + if (atRule.params.indexOf("@value") !== -1) { + result.warn("Invalid value definition: " + atRule.params); + } + + let [, key, value] = `${atRule.params}${atRule.raws.between}`.match( + matchValueDefinition + ); + + const normalizedValue = value.replace(/\/\*((?!\*\/).*?)\*\//g, ""); + + if (normalizedValue.length === 0) { + result.warn("Invalid value definition: " + atRule.params); + + atRule.remove(); + + return; } + + let isOnlySpace = /^\s+$/.test(normalizedValue); + + if (!isOnlySpace) { + value = value.trim(); + } + + // Add to the definitions, knowing that values can refer to each other + definitions[key] = ICSSUtils.replaceValueSymbols( + value, + definitions + ); + + atRule.remove(); }, }, OnceExit(root, postcss) { diff --git a/test/index.test.js b/test/index.test.js index a403ae2..d7f298c 100644 --- a/test/index.test.js +++ b/test/index.test.js @@ -1,167 +1,171 @@ "use strict"; const postcss = require("postcss"); -const constants = require("../src"); +const plugin = require("../src"); -const test = (input, expected) => { - const processor = postcss([constants]); +const test = async (input, expected) => { + const processor = await postcss([plugin]); - expect(expected).toBe(processor.process(input).css); + expect(processor.process(input).css).toBe(expected); }; describe("constants", () => { - it("should pass through an empty string", () => { - test("", ""); - }); + it("should pass through an empty string", () => test("", "")); - it("should export a constant", () => { - test("@value red blue;", ":export {\n red: blue\n}"); - }); + it("should export a constant", () => + test("@value red blue;", ":export {\n red: blue\n}")); - it("gives an error when there is no semicolon between lines", () => { + it("gives a warnings when there is no semicolon between lines", async () => { const input = "@value red blue\n@value green yellow"; - const processor = postcss([constants]); - const result = processor.process(input); + const processor = postcss([plugin]); + const result = await processor.process(input, { from: undefined }); const warnings = result.warnings(); + expect(result.css).toBe(":export {\n green: yellow\n}"); expect(warnings).toHaveLength(1); expect(warnings[0].text).toBe( "Invalid value definition: red blue\n@value green yellow" ); }); - it("should export a more complex constant", () => { + it("gives a warnings on empty value", async () => { + const input = "@value v-comment:;"; + const processor = postcss([plugin]); + const result = await processor.process(input, { from: undefined }); + const warnings = result.warnings(); + + expect(result.css).toBe(""); + expect(warnings).toHaveLength(1); + expect(warnings[0].text).toBe("Invalid value definition: v-comment:"); + }); + + it("gives a warnings on empty value with comment", async () => { + const input = "@value v-comment:/* comment */;"; + const processor = postcss([plugin]); + const result = await processor.process(input, { from: undefined }); + const warnings = result.warnings(); + + expect(result.css).toBe(""); + expect(warnings).toHaveLength(1); + expect(warnings[0].text).toBe("Invalid value definition: v-comment:"); + }); + + it("should export a more complex constant", () => test( - "@value small (max-width: 599px);", + "@value small: (max-width: 599px);", ":export {\n small: (max-width: 599px)\n}" - ); - }); + )); - it("should replace constants within the file", () => { + it("should replace constants within the file", () => test( - "@value blue red; .foo { color: blue; }", + "@value blue: red; .foo { color: blue; }", ":export {\n blue: red;\n}\n.foo { color: red; }" - ); - }); + )); - it("should replace selectors within the file", () => { + it("should replace selectors within the file", () => test( - "@value colorValue red; .colorValue { color: colorValue; }", + "@value colorValue: red; .colorValue { color: colorValue; }", ":export {\n colorValue: red;\n}\n.red { color: red; }" - ); - }); + )); - it("should replace selectors within the file #1", () => { + it("should replace selectors within the file #1", () => test( - "@value colorValue red; #colorValue { color: colorValue; }", + "@value colorValue: red; #colorValue { color: colorValue; }", ":export {\n colorValue: red;\n}\n#red { color: red; }" - ); - }); + )); - it("should replace selectors within the file #2", () => { + it("should replace selectors within the file #2", () => test( - "@value colorValue red; .colorValue > .colorValue { color: colorValue; }", + "@value colorValue: red; .colorValue > .colorValue { color: colorValue; }", ":export {\n colorValue: red;\n}\n.red > .red { color: red; }" - ); - }); + )); - it("should import and re-export a simple constant", () => { + 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", () => { + 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; }' - ); - }); + ':import("./colors.css") {\n i__const_red_0: red;\n}\n:export {\n red: i__const_red_0;\n}\n.foo { color: i__const_red_0; }' + )); - it("should import and alias a constant and replace usages", () => { + 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; }' - ); - }); + ':import("./colors.css") {\n i__const_red_0: blue;\n}\n:export {\n red: i__const_red_0;\n}\n.foo { color: i__const_red_0; }' + )); - it("should import multiple from a single file", () => { + it("should import multiple from a single file", () => test( `@value blue, red from "./colors.css"; .foo { color: red; } .bar { color: blue }`, `:import("./colors.css") { - i__const_blue_3: blue; - i__const_red_4: red; + i__const_blue_0: blue; + i__const_red_1: red; } :export { - blue: i__const_blue_3; - red: i__const_red_4; + blue: i__const_blue_0; + red: i__const_red_1; } -.foo { color: i__const_red_4; } -.bar { color: i__const_blue_3 }` - ); - }); +.foo { color: i__const_red_1; } +.bar { color: i__const_blue_0 }` + )); - it("should import from a definition", () => { + 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}' - ); - }); + ':import("./colors.css") {\n i__const_red_0: red\n}\n' + + ':export {\n colors: "./colors.css";\n red: i__const_red_0\n}' + )); - it("should only allow values for paths if defined in the right order", () => { + 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}' - ); - }); + ":import(colors) {\n i__const_red_0: red\n}\n" + + ':export {\n red: i__const_red_0;\n colors: "./colors.css"\n}' + )); - it("should allow transitive values", () => { + 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", () => { + 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", () => { + it("should preserve import order", () => test( '@value a from "./a.css"; @value b from "./b.css";', - ':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}" - ); - }); + ':import("./a.css") {\n i__const_a_0: a\n}\n' + + ':import("./b.css") {\n i__const_b_1: b\n}\n' + + ":export {\n a: i__const_a_0;\n b: i__const_b_1\n}" + )); - it("should allow custom-property-style names", () => { + it("should allow custom-property-style names", () => test( '@value --red from "./colors.css"; .foo { color: --red; }', - ':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; }" - ); - }); + ':import("./colors.css") {\n i__const___red_0: --red;\n}\n' + + ":export {\n --red: i__const___red_0;\n}\n" + + ".foo { color: i__const___red_0; }" + )); - it("should allow all colour types", () => { + it("should allow all colour types", () => test( "@value named: red; @value 3char #0f0; @value 6char #00ff00; @value rgba rgba(34, 12, 64, 0.3); @value hsla hsla(220, 13.0%, 18.0%, 1);\n" + ".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", () => { + it("should import multiple from a single file on multiple lines", () => test( `@value ( blue, @@ -170,31 +174,52 @@ describe("constants", () => { .foo { color: red; } .bar { color: blue }`, `:import("./colors.css") { - i__const_blue_10: blue; - i__const_red_11: red; + i__const_blue_0: blue; + i__const_red_1: red; } :export { - blue: i__const_blue_10; - red: i__const_red_11; + blue: i__const_blue_0; + red: i__const_red_1; } -.foo { color: i__const_red_11; } -.bar { color: i__const_blue_10 }` - ); - }); +.foo { color: i__const_red_1; } +.bar { color: i__const_blue_0 }` + )); - it("should allow definitions with commas in them", () => { + it("should allow definitions with commas in them", () => test( "@value coolShadow: 0 11px 15px -7px rgba(0,0,0,.2),0 24px 38px 3px rgba(0,0,0,.14) ;\n" + ".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", () => { + it("should allow values with nested parantheses", () => test( "@value aaa: color(red lightness(50%));", ":export {\n aaa: color(red lightness(50%))\n}" - ); - }); + )); + + it("should work with custom properties", () => + test( + "@value v-color: red;\n:root { --color: v-color; }", + ":export {\n v-color: red;\n}\n:root { --color: red; }" + )); + + it("should work with empty custom properties", () => + test( + "@value v-empty: ;\n:root { --color:v-empty; }", + ":export {\n v-empty: ;\n}\n:root { --color: ; }" + )); + + it("should work with empty custom properties #2", () => + test( + "@value v-empty: ;\n:root { --color:v-empty; }", + ":export {\n v-empty: ;\n}\n:root { --color: ; }" + )); + + it("should work with empty custom properties #3", () => + test( + "@value v-empty: /* comment */;\n:root { --color:v-empty; }", + ":export {\n v-empty: /* comment */;\n}\n:root { --color: /* comment */; }" + )); });