From bc56a46aba1d3b735bb0b93ead3dce143da019ea Mon Sep 17 00:00:00 2001 From: Pat Cavit Date: Mon, 2 Nov 2015 23:10:17 -0800 Subject: [PATCH] Add browserify transform - Rewrite tests to use mock-fs - Improve test coverage - Slight default exports tweaks --- .gitignore | 1 - bin/cli.js | 1 + index.js | 3 +- package.json | 7 +- src/browserify.js | 39 +++++++++ src/imports.js | 29 +++++++ {plugins => src/plugins}/composition.js | 2 +- {plugins => src/plugins}/scoping.js | 0 {plugins => src/plugins}/values.js | 6 +- imports.js => src/processor.js | 42 +++------ test/_file-system.js | 41 +++++++++ test/browserify.js | 43 +++++++++ test/imports.js | 78 +++++++---------- test/plugin-composition.js | 2 +- test/plugin-scoping.js | 9 +- test/plugin-values.js | 2 +- test/processor.js | 87 +++++++++++++++++++ test/specimens/imports/folder/folder.css | 5 -- .../specimens/imports/invalid-composition.css | 3 - test/specimens/imports/invalid-value.css | 1 - test/specimens/imports/local.css | 11 --- test/specimens/imports/node_modules.css | 8 -- .../node_modules/test-styles/styles.css | 3 - test/specimens/imports/start.css | 14 --- 24 files changed, 302 insertions(+), 135 deletions(-) create mode 100644 src/browserify.js create mode 100644 src/imports.js rename {plugins => src/plugins}/composition.js (98%) rename {plugins => src/plugins}/scoping.js (100%) rename {plugins => src/plugins}/values.js (95%) rename imports.js => src/processor.js (69%) create mode 100644 test/_file-system.js create mode 100644 test/browserify.js create mode 100644 test/processor.js delete mode 100644 test/specimens/imports/folder/folder.css delete mode 100644 test/specimens/imports/invalid-composition.css delete mode 100644 test/specimens/imports/invalid-value.css delete mode 100644 test/specimens/imports/local.css delete mode 100644 test/specimens/imports/node_modules.css delete mode 100644 test/specimens/imports/node_modules/test-styles/styles.css delete mode 100644 test/specimens/imports/start.css diff --git a/.gitignore b/.gitignore index 4fd00cf7e..ee601547e 100644 --- a/.gitignore +++ b/.gitignore @@ -21,7 +21,6 @@ build/Release # Dependency directory node_modules -!test/specimens/**/node_modules # Users Environment Variables .lock-wscript diff --git a/bin/cli.js b/bin/cli.js index 8927a6938..1aafa6a8b 100644 --- a/bin/cli.js +++ b/bin/cli.js @@ -1,3 +1,4 @@ +/* eslint no-console:0 */ "use strict"; var fs = require("fs"), diff --git a/index.js b/index.js index b79d166e3..d0468b753 100644 --- a/index.js +++ b/index.js @@ -1,3 +1,4 @@ "use strict"; -exports.process = require("./imports").process; +exports.processor = require("./src/processor"); +exports.browserify = require("./src/browserify"); diff --git a/package.json b/package.json index 263051368..a749e548b 100644 --- a/package.json +++ b/package.json @@ -11,12 +11,14 @@ "author": "Pat Cavit ", "license": "MIT", "devDependencies": { + "browserify": "^12.0.1", "eslint": "^1.7.3", "eslint-config-arenanet": "^1.0.1", "istanbul": "^0.4.0", "jscs": "^2.5.0", "jscs-preset-arenanet": "^1.0.0", - "mocha": "^2.3.3" + "mocha": "^2.3.3", + "mock-fs": "^3.4.0" }, "dependencies": { "dependency-graph": "^0.4.1", @@ -28,7 +30,8 @@ "lodash.uniq": "^3.2.2", "postcss": "^5.0.10", "postcss-selector-parser": "^1.3.0", - "resolve": "^1.1.6" + "resolve": "^1.1.6", + "through2": "^2.0.0" }, "eslintConfig": { "extends": "arenanet", diff --git a/src/browserify.js b/src/browserify.js new file mode 100644 index 000000000..b126795f5 --- /dev/null +++ b/src/browserify.js @@ -0,0 +1,39 @@ +"use strict"; + +var fs = require("fs"), + path = require("path"), + + through = require("through2"), + assign = require("lodash.assign"), + + processor = require("./processor"); + +module.exports = function(file, opts) { + var options = assign({}, { extension : ".css" }, opts), + buffer; + + if(path.extname(file) !== options.extension) { + return through(); + } + + buffer = ""; + + return through( + function(chunk, enc, callback) { + buffer += chunk.toString("utf8"); + callback(); + }, + + function(callback) { + var result = processor.string(file, buffer), + dest = + options.dest || + path.join(path.dirname(file), path.basename(file, options.extension) + "-compiled.css"); + + fs.writeFileSync(dest, result.css, "utf8"); + + this.push("module.exports = " + JSON.stringify(result.exports) + ";"); + callback(); + } + ); +}; diff --git a/src/imports.js b/src/imports.js new file mode 100644 index 000000000..7f5d95a0e --- /dev/null +++ b/src/imports.js @@ -0,0 +1,29 @@ +"use strict"; + +var format = /(.+) from ["']([^'"]+?)["']$/i; + +exports.format = format; + +exports.match = function(text) { + return text.search(format) > -1; +}; + +exports.parse = function(text) { + var parts = text.match(format), + keys, source; + + if(!parts) { + return false; + } + + keys = parts[1].split(","); + source = parts[2]; + + return { + keys : keys.map(function(value) { + return value.trim(); + }), + + source : source + }; +}; diff --git a/plugins/composition.js b/src/plugins/composition.js similarity index 98% rename from plugins/composition.js rename to src/plugins/composition.js index 86ba09415..70c992c83 100644 --- a/plugins/composition.js +++ b/src/plugins/composition.js @@ -98,7 +98,7 @@ module.exports = postcss.plugin(plugin, function() { selectors = identifiers(decl.parent.selector); - if(decl.value.search(imports.format) > -1) { + if(imports.match(decl.value)) { // composes: fooga, wooga from "./some-file.css" if(!options.files) { throw decl.error("Invalid @value reference: " + decl.value, { word : decl.value }); diff --git a/plugins/scoping.js b/src/plugins/scoping.js similarity index 100% rename from plugins/scoping.js rename to src/plugins/scoping.js diff --git a/plugins/values.js b/src/plugins/values.js similarity index 95% rename from plugins/values.js rename to src/plugins/values.js index c883957cf..12bb365eb 100644 --- a/plugins/values.js +++ b/src/plugins/values.js @@ -20,7 +20,7 @@ function parseBase(rule) { return out; } -function parseImports(options, rule) { +function resolveImports(options, rule) { var parsed = imports.parse(rule.params), out = {}, source; @@ -60,8 +60,8 @@ module.exports = postcss.plugin(plugin, function() { if(rule.params.search(format) > -1) { locals = parseBase(rule); - } else if(rule.params.search(imports.format) > -1) { - locals = parseImports(result.opts, rule); + } else if(imports.match(rule.params)) { + locals = resolveImports(result.opts, rule); } else { throw rule.error("Invalid @value declaration", { word : rule.params }); } diff --git a/imports.js b/src/processor.js similarity index 69% rename from imports.js rename to src/processor.js index 075f5d983..01fbd9041 100644 --- a/imports.js +++ b/src/processor.js @@ -12,35 +12,15 @@ var fs = require("fs"), require("./plugins/scoping.js"), require("./plugins/composition.js") ]), - format = /(.+) from ["']([^'"]+?)["']$/i; - -function parseImports(text) { - var parts = text.match(format), - keys, source; - - if(!parts) { - return false; - } - keys = parts[1].split(","); - source = parts[2]; - - return { - keys : keys.map(function(value) { - return value.trim(); - }), - - source : source - }; -} + imports = require("./imports"); -function parseFile(env, file) { - var contents = fs.readFileSync(file, "utf8"), - basedir = path.dirname(file), - css = postcss.parse(contents, { from : file }); +function parseFile(env, file, contents) { + var basedir = path.dirname(file), + css = postcss.parse(contents, { from : file }); function parse(field, rule) { - var parsed = parseImports(rule[field]), + var parsed = imports.parse(rule[field]), source; if(!parsed) { @@ -61,11 +41,15 @@ function parseFile(env, file) { css.walkDecls("composes", parse.bind(null, "value")); env.graph.dependenciesOf(file).forEach(function(dependency) { - parseFile(env, dependency); + parseFile(env, dependency, fs.readFileSync(dependency, "utf8")); }); } -module.exports.process = function(start) { +exports.file = function(file) { + return exports.string(file, fs.readFileSync(file, "utf8")); +}; + +exports.string = function(start, contents) { var files = {}, graph = new Graph(), source = path.resolve(start), @@ -76,7 +60,7 @@ module.exports.process = function(start) { parseFile({ graph : graph, files : files - }, source); + }, source, contents); graph.overallOrder().forEach(function(file) { var parsed = parser.process(files[file].contents, { @@ -102,5 +86,3 @@ module.exports.process = function(start) { }; }; -module.exports.parse = parseImports; -module.exports.format = format; diff --git a/test/_file-system.js b/test/_file-system.js new file mode 100644 index 000000000..0fc37da57 --- /dev/null +++ b/test/_file-system.js @@ -0,0 +1,41 @@ +"use strict"; + +module.exports = { + folder : { + "folder.css" : + "@value folder: white;\n" + + ".folder { margin: 2px; }\n" + }, + + invalid : { + "composition.css" : ".wooga { composes: fake from \"../local.css\"; }\n", + "value.css" : "@value not-real from \"../local.css\";\n" + }, + + node_modules : { + "test-styles" : { + "styles.css" : ".booga { color: white; }\n" + } + }, + + "client.js" : + "\"use strict\";\n" + + "require(\"./start.css\");\n", + + "start.css" : + "@value one, two, folder from \"./local.css\";\n" + + ".wooga { composes: booga from \"./local.css\"; }\n" + + ".booga { color: one; background: two; }\n" + + ".tooga { border: 1px solid folder; }", + + "local.css" : + "@value one: red;\n" + + "@value two: blue;\n" + + "@value folder from \"./folder/folder.css\";\n" + + ".booga { background: green; }\n" + + ".looga { composes: booga; }\n", + + "node_modules.css" : + ".wooga { composes: booga from \"test-styles/styles.css\"; }\n" + + ".booga { color: one; background: two; }\n" +}; diff --git a/test/browserify.js b/test/browserify.js new file mode 100644 index 000000000..46e95e9f3 --- /dev/null +++ b/test/browserify.js @@ -0,0 +1,43 @@ +"use strict"; + +var mock = require("mock-fs"), + + fs = require("fs"), + path = require("path"), + assert = require("assert"), + + browserify = require("browserify"), + + transform = require("../src/browserify"), + + cwd = process.cwd(); + +describe("postcss-css-modules", function() { + describe("browserify", function() { + before(function() { + mock(require("./_file-system")); + }); + + after(mock.restore); + + it("should run browserify", function(done) { + var build = browserify("./client.js"); + + build.transform(transform); + + build.bundle(function(err, out) { + var js = out.toString(), + css = fs.readFileSync("./start-compiled.css", "utf8"); + + assert(js.indexOf("module.exports = {\"wooga\":[\"f5507abd3eea0987714c5d92c3230347_booga\"],\"booga\":[\"2ba8076ec1145293c7e3600dbc63b306_booga\"],\"tooga\":[\"2ba8076ec1145293c7e3600dbc63b306_tooga\"]};") > -1); + + assert(css.indexOf(".dafdfcc7dc876084d352519086f9e6e9_folder { margin: 2px; }") > -1); + assert(css.indexOf(".f5507abd3eea0987714c5d92c3230347_booga { background: green; }") > -1); + assert(css.indexOf(".2ba8076ec1145293c7e3600dbc63b306_booga { color: red; background: blue; }") > -1); + assert(css.indexOf(".2ba8076ec1145293c7e3600dbc63b306_tooga { border: 1px solid white; }") > -1); + + done(); + }); + }); + }); +}); diff --git a/test/imports.js b/test/imports.js index 808c13b86..6d888be7d 100644 --- a/test/imports.js +++ b/test/imports.js @@ -1,61 +1,41 @@ "use strict"; -var path = require("path"), - assert = require("assert"), +var assert = require("assert"), - imports = require("../imports"); + imports = require("../src/imports"); describe("postcss-css-modules", function() { describe("imports", function() { - var start = imports.process("./test/specimens/imports/start.css"); - - it("should walk dependencies", function() { - assert("files" in start); - - assert(path.join(__dirname, "./specimens/imports/start.css") in start.files); - assert(path.join(__dirname, "./specimens/imports/local.css") in start.files); - assert(path.join(__dirname, "./specimens/imports/folder/folder.css") in start.files); + it("should check strings for format correctness", function() { + assert(imports.match("fooga from \"/booga.css\"")); + assert(imports.match("fooga from '/booga.css'")); + assert(imports.match("fooga, booga from '/booga.css'")); }); - it("should walk dependencies into node_modules", function() { - var result = imports.process("./test/specimens/imports/node_modules.css"); + it("should parse strings into their pieces", function() { + assert.deepEqual( + imports.parse("fooga from \"/booga.css\""), + { + keys : [ "fooga" ], + source : "/booga.css" + } + ); - assert(path.join(__dirname, "./specimens/imports/node_modules.css") in result.files); - assert(path.join(__dirname, "./specimens/imports/node_modules/test-styles/styles.css") in result.files); - }); - - it("should export identifiers and their classes", function() { - assert.deepEqual(start.exports, { - wooga : [ "e90a9ca9bb862787d5423d08ffa8a320_booga" ], - booga : [ "624cf273647af7597f5866483cde95d0_booga" ], - tooga : [ "624cf273647af7597f5866483cde95d0_tooga" ] - }); - }); - - it("should generate correct css", function() { - // normalize newlines - var css = start.css.replace(/\r\n/g, "\n"); - - assert.equal(css.indexOf(".b5600c78a155fc10eeddec15127659a8_folder {\n margin: 2px;\n}"), 0); - assert.equal(css.indexOf(".e90a9ca9bb862787d5423d08ffa8a320_booga {\n background: green;\n}"), 63); - assert.equal(css.indexOf(".624cf273647af7597f5866483cde95d0_booga {\n color: red;\n background: blue;\n}"), 131); - assert.equal(css.indexOf(".624cf273647af7597f5866483cde95d0_tooga {\n border: 1px solid white;\n}"), 218); - }); - - describe("values", function() { - it("should fail if a non-existant import is referenced", function() { - assert.throws(function() { - imports.process("./test/specimens/imports/invalid-value.css"); - }); - }); - }); - - describe("composition", function() { - it("should fail if a non-existant import is referenced", function() { - assert.throws(function() { - imports.process("./test/specimens/imports/invalid-composition.css"); - }); - }); + assert.deepEqual( + imports.parse("fooga from '/booga.css'"), + { + keys : [ "fooga" ], + source : "/booga.css" + } + ); + + assert.deepEqual( + imports.parse("fooga, booga from '/booga.css'"), + { + keys : [ "fooga", "booga" ], + source : "/booga.css" + } + ); }); }); }); diff --git a/test/plugin-composition.js b/test/plugin-composition.js index e855cbfe9..f5d0910fa 100644 --- a/test/plugin-composition.js +++ b/test/plugin-composition.js @@ -2,7 +2,7 @@ var assert = require("assert"), - plugin = require("../plugins/composition"); + plugin = require("../src/plugins/composition"); function css(src, options) { return plugin.process(src, options).css; diff --git a/test/plugin-scoping.js b/test/plugin-scoping.js index cadb0c74d..de4e22e0d 100644 --- a/test/plugin-scoping.js +++ b/test/plugin-scoping.js @@ -1,5 +1,5 @@ var assert = require("assert"), - plugin = require("../plugins/scoping"); + plugin = require("../src/plugins/scoping"); function css(src, options) { return plugin.process(src, options).css; @@ -20,6 +20,13 @@ describe("postcss-css-modules", function() { "#5dde9181034d498d7163570eea1e3987_wooga { color: red; }" ); }); + + it("should ignore non-class/non-id selectors", function() { + assert.equal( + css("p { color: red; }"), + "p { color: red; }" + ); + }); it("should remove :global() from non-class/non-id selectors", function() { assert.equal( diff --git a/test/plugin-values.js b/test/plugin-values.js index 330f3522d..a84aaca8f 100644 --- a/test/plugin-values.js +++ b/test/plugin-values.js @@ -1,5 +1,5 @@ var assert = require("assert"), - plugin = require("../plugins/values"); + plugin = require("../src/plugins/values"); function css(src, options) { return plugin.process(src, options).css; diff --git a/test/processor.js b/test/processor.js new file mode 100644 index 000000000..5f7a0bb61 --- /dev/null +++ b/test/processor.js @@ -0,0 +1,87 @@ +"use strict"; + +var path = require("path"), + assert = require("assert"), + + mock = require("mock-fs"), + + processor = require("../src/processor"), + + cwd = process.cwd(); + +describe("postcss-css-modules", function() { + describe("processor", function() { + before(function() { + mock(require("./_file-system")); + }); + + after(mock.restore); + + it("should walk dependencies", function() { + var result = processor.file("./start.css"); + + assert("files" in result); + + assert(path.join(cwd, "./start.css") in result.files); + assert(path.join(cwd, "./local.css") in result.files); + assert(path.join(cwd, "./folder/folder.css") in result.files); + }); + + it("should walk dependencies into node_modules", function() { + var result = processor.file("./node_modules.css"); + + assert(path.join(cwd, "./node_modules.css") in result.files); + assert(path.join(cwd, "./node_modules/test-styles/styles.css") in result.files); + }); + + it("should export identifiers and their classes", function() { + var result = processor.file("./start.css"); + + assert.deepEqual(result.exports, { + wooga : [ "f5507abd3eea0987714c5d92c3230347_booga" ], + booga : [ "2ba8076ec1145293c7e3600dbc63b306_booga" ], + tooga : [ "2ba8076ec1145293c7e3600dbc63b306_tooga" ] + }); + }); + + it("should generate correct css", function() { + var css = processor.file("./start.css").css; + + assert.equal(css.indexOf(".dafdfcc7dc876084d352519086f9e6e9_folder { margin: 2px; }"), 0); + assert.equal(css.indexOf(".f5507abd3eea0987714c5d92c3230347_booga { background: green; }"), 59); + assert.equal(css.indexOf(".2ba8076ec1145293c7e3600dbc63b306_booga { color: red; background: blue; }"), 123); + assert.equal(css.indexOf(".2ba8076ec1145293c7e3600dbc63b306_tooga { border: 1px solid white; }"), 197); + }); + + it("should support being passed a string", function() { + var result = processor.string( + path.join(cwd, "./start.css"), + ".wooga { composes: booga from \"./local.css\"; }" + ), + css = result.css; + + assert(path.join(cwd, "./start.css") in result.files); + assert(path.join(cwd, "./local.css") in result.files); + assert(path.join(cwd, "./folder/folder.css") in result.files); + + assert.equal(css.indexOf(".dafdfcc7dc876084d352519086f9e6e9_folder { margin: 2px; }"), 0); + assert.equal(css.indexOf(".f5507abd3eea0987714c5d92c3230347_booga { background: green; }"), 59); + }); + + describe("values", function() { + it("should fail if a non-existant import is referenced", function() { + assert.throws(function() { + processor.file("./invalid/value.css"); + }); + }); + }); + + describe("composition", function() { + it("should fail if a non-existant import is referenced", function() { + assert.throws(function() { + processor.file("./invalid/composition.css"); + }); + }); + }); + }); +}); diff --git a/test/specimens/imports/folder/folder.css b/test/specimens/imports/folder/folder.css deleted file mode 100644 index 0343d0622..000000000 --- a/test/specimens/imports/folder/folder.css +++ /dev/null @@ -1,5 +0,0 @@ -@value folder: white; - -.folder { - margin: 2px; -} diff --git a/test/specimens/imports/invalid-composition.css b/test/specimens/imports/invalid-composition.css deleted file mode 100644 index 659c51f98..000000000 --- a/test/specimens/imports/invalid-composition.css +++ /dev/null @@ -1,3 +0,0 @@ -.wooga { - composes: fake from "./local.css"; -} diff --git a/test/specimens/imports/invalid-value.css b/test/specimens/imports/invalid-value.css deleted file mode 100644 index 67506c316..000000000 --- a/test/specimens/imports/invalid-value.css +++ /dev/null @@ -1 +0,0 @@ -@value not-real from "./local.css"; diff --git a/test/specimens/imports/local.css b/test/specimens/imports/local.css deleted file mode 100644 index 91869998b..000000000 --- a/test/specimens/imports/local.css +++ /dev/null @@ -1,11 +0,0 @@ -@value one: red; -@value two: blue; -@value folder from "./folder/folder.css"; - -.booga { - background: green; -} - -.looga { - composes: booga; -} diff --git a/test/specimens/imports/node_modules.css b/test/specimens/imports/node_modules.css deleted file mode 100644 index 6393ca31e..000000000 --- a/test/specimens/imports/node_modules.css +++ /dev/null @@ -1,8 +0,0 @@ -.wooga { - composes: booga from "test-styles/styles.css"; -} - -.booga { - color: one; - background: two; -} diff --git a/test/specimens/imports/node_modules/test-styles/styles.css b/test/specimens/imports/node_modules/test-styles/styles.css deleted file mode 100644 index 783bb89d2..000000000 --- a/test/specimens/imports/node_modules/test-styles/styles.css +++ /dev/null @@ -1,3 +0,0 @@ -.booga { - color: white; -} diff --git a/test/specimens/imports/start.css b/test/specimens/imports/start.css deleted file mode 100644 index a5b796427..000000000 --- a/test/specimens/imports/start.css +++ /dev/null @@ -1,14 +0,0 @@ -@value one, two, folder from "./local.css"; - -.wooga { - composes: booga from "./local.css"; -} - -.booga { - color: one; - background: two; -} - -.tooga { - border: 1px solid folder; -}