From 8682c1f0b6a8c1d7ab288bd456dee1efcd53ea4b Mon Sep 17 00:00:00 2001 From: Alexey Litvinov Date: Sun, 2 Aug 2015 22:11:28 +0300 Subject: [PATCH 01/11] necessary dependencies --- package.json | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index 58d91c1..8987e3a 100644 --- a/package.json +++ b/package.json @@ -4,17 +4,15 @@ "description": "A require hook to compile CSS Modules on the fly", "main": "index.js", "dependencies": { - "babel": "^5.6.14", - "object-assign": "^3.0.0" + "css-modules-loader-core": "0.0.11", + "postcss-modules-extract-imports": "0.0.5", + "postcss-modules-local-by-default": "0.0.11" }, "devDependencies": { + "babel": "^5.8.20", "mocha": "^2.2.5" }, "scripts": { - "clone": "git clone https://github.com/sullenor/css-modules-loader-core.git node_modules/css-modules-loader-core", - "deps": "cd node_modules/css-modules-loader-core && npm i", - "postinstall": "npm run clone && npm run deps", - "preinstall": "rm -rf node_modules/css-modules-loader-core", "test": "mocha test" }, "repository": { From 01e8eb6d366a94e4d5beb579f4ab219f78ce4cf7 Mon Sep 17 00:00:00 2001 From: Alexey Litvinov Date: Sun, 2 Aug 2015 22:36:29 +0300 Subject: [PATCH 02/11] ported tests from css-modules-loader-core to find possible regression --- package.json | 2 +- test/cssi/interchange-format/colors.css | 7 +++ test/cssi/interchange-format/expected.css | 6 ++ test/cssi/interchange-format/expected.json | 3 + test/cssi/interchange-format/source.css | 11 ++++ test/cssi/pseudo-variables/colors.css | 4 ++ test/cssi/pseudo-variables/expected.css | 5 ++ test/cssi/pseudo-variables/expected.json | 3 + test/cssi/pseudo-variables/source.css | 13 ++++ test/example.css | 4 -- test/index.js | 31 ---------- test/test-cases.js | 59 +++++++++++++++++++ test/test-cases/localise-export/expected.css | 9 +++ test/test-cases/localise-export/expected.json | 5 ++ test/test-cases/localise-export/source.css | 9 +++ test/test-cases/multiple-dependencies/b.css | 8 +++ test/test-cases/multiple-dependencies/c.css | 3 + test/test-cases/multiple-dependencies/d.css | 6 ++ .../multiple-dependencies/expected.css | 20 +++++++ .../multiple-dependencies/expected.json | 3 + .../multiple-dependencies/source.css | 6 ++ test/test-cases/multiple-sources/b.css | 4 ++ test/test-cases/multiple-sources/c.css | 3 + test/test-cases/multiple-sources/d.css | 3 + test/test-cases/multiple-sources/expected.css | 15 +++++ .../test-cases/multiple-sources/expected.json | 4 ++ test/test-cases/multiple-sources/source1.css | 5 ++ test/test-cases/multiple-sources/source2.css | 4 ++ test/test-cases/simple-export/expected.css | 7 +++ test/test-cases/simple-export/expected.json | 3 + test/test-cases/simple-export/source.css | 7 +++ .../single-import-export/colors.css | 7 +++ .../single-import-export/expected.css | 10 ++++ .../single-import-export/expected.json | 3 + .../single-import-export/source.css | 4 ++ 35 files changed, 260 insertions(+), 36 deletions(-) create mode 100644 test/cssi/interchange-format/colors.css create mode 100644 test/cssi/interchange-format/expected.css create mode 100644 test/cssi/interchange-format/expected.json create mode 100644 test/cssi/interchange-format/source.css create mode 100644 test/cssi/pseudo-variables/colors.css create mode 100644 test/cssi/pseudo-variables/expected.css create mode 100644 test/cssi/pseudo-variables/expected.json create mode 100644 test/cssi/pseudo-variables/source.css delete mode 100644 test/example.css delete mode 100644 test/index.js create mode 100644 test/test-cases.js create mode 100644 test/test-cases/localise-export/expected.css create mode 100644 test/test-cases/localise-export/expected.json create mode 100644 test/test-cases/localise-export/source.css create mode 100644 test/test-cases/multiple-dependencies/b.css create mode 100644 test/test-cases/multiple-dependencies/c.css create mode 100644 test/test-cases/multiple-dependencies/d.css create mode 100644 test/test-cases/multiple-dependencies/expected.css create mode 100644 test/test-cases/multiple-dependencies/expected.json create mode 100644 test/test-cases/multiple-dependencies/source.css create mode 100644 test/test-cases/multiple-sources/b.css create mode 100644 test/test-cases/multiple-sources/c.css create mode 100644 test/test-cases/multiple-sources/d.css create mode 100644 test/test-cases/multiple-sources/expected.css create mode 100644 test/test-cases/multiple-sources/expected.json create mode 100644 test/test-cases/multiple-sources/source1.css create mode 100644 test/test-cases/multiple-sources/source2.css create mode 100644 test/test-cases/simple-export/expected.css create mode 100644 test/test-cases/simple-export/expected.json create mode 100644 test/test-cases/simple-export/source.css create mode 100644 test/test-cases/single-import-export/colors.css create mode 100644 test/test-cases/single-import-export/expected.css create mode 100644 test/test-cases/single-import-export/expected.json create mode 100644 test/test-cases/single-import-export/source.css diff --git a/package.json b/package.json index 8987e3a..5db45c9 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,7 @@ "mocha": "^2.2.5" }, "scripts": { - "test": "mocha test" + "test": "mocha --compilers js:babel/register" }, "repository": { "type": "git", diff --git a/test/cssi/interchange-format/colors.css b/test/cssi/interchange-format/colors.css new file mode 100644 index 0000000..4057aae --- /dev/null +++ b/test/cssi/interchange-format/colors.css @@ -0,0 +1,7 @@ +:export { + blackShadow: x__single_import_export_colors__blackShadow; +} + +.x__single_import_export_colors__blackShadow { + box-shadow: 0 0 10px -2px black; +} diff --git a/test/cssi/interchange-format/expected.css b/test/cssi/interchange-format/expected.css new file mode 100644 index 0000000..36f1092 --- /dev/null +++ b/test/cssi/interchange-format/expected.css @@ -0,0 +1,6 @@ +.x__single_import_export_colors__blackShadow { + box-shadow: 0 0 10px -2px black; +} +.x__single_import_export_source__localName { + color: red; +} diff --git a/test/cssi/interchange-format/expected.json b/test/cssi/interchange-format/expected.json new file mode 100644 index 0000000..43311c7 --- /dev/null +++ b/test/cssi/interchange-format/expected.json @@ -0,0 +1,3 @@ +{ + "localName": "x__single_import_export_source__localName x__single_import_export_colors__blackShadow" +} diff --git a/test/cssi/interchange-format/source.css b/test/cssi/interchange-format/source.css new file mode 100644 index 0000000..d7125a0 --- /dev/null +++ b/test/cssi/interchange-format/source.css @@ -0,0 +1,11 @@ +:import("./colors.css") { + i__tmp_import_djhgdsag: blackShadow; +} + +:export { + localName: x__single_import_export_source__localName i__tmp_import_djhgdsag; +} + +.x__single_import_export_source__localName { + color: red; +} diff --git a/test/cssi/pseudo-variables/colors.css b/test/cssi/pseudo-variables/colors.css new file mode 100644 index 0000000..3ac72d9 --- /dev/null +++ b/test/cssi/pseudo-variables/colors.css @@ -0,0 +1,4 @@ +:export { + black: #222; + white: #ddd; +} diff --git a/test/cssi/pseudo-variables/expected.css b/test/cssi/pseudo-variables/expected.css new file mode 100644 index 0000000..371497c --- /dev/null +++ b/test/cssi/pseudo-variables/expected.css @@ -0,0 +1,5 @@ + +.x__lol { + color: #222; + background: #ddd; +} diff --git a/test/cssi/pseudo-variables/expected.json b/test/cssi/pseudo-variables/expected.json new file mode 100644 index 0000000..b8c3c23 --- /dev/null +++ b/test/cssi/pseudo-variables/expected.json @@ -0,0 +1,3 @@ +{ + "lol": "x__lol" +} diff --git a/test/cssi/pseudo-variables/source.css b/test/cssi/pseudo-variables/source.css new file mode 100644 index 0000000..cc4b95a --- /dev/null +++ b/test/cssi/pseudo-variables/source.css @@ -0,0 +1,13 @@ +:import("./colors.css") { + i__black: black; + i__white: white; +} + +:export { + lol: x__lol; +} + +.x__lol { + color: i__black; + background: i__white; +} diff --git a/test/example.css b/test/example.css deleted file mode 100644 index c28336c..0000000 --- a/test/example.css +++ /dev/null @@ -1,4 +0,0 @@ -.app -{ - background: #999; -} diff --git a/test/index.js b/test/index.js deleted file mode 100644 index 0c09386..0000000 --- a/test/index.js +++ /dev/null @@ -1,31 +0,0 @@ -describe('css-modules-require-hook', function () { - 'use strict'; - - var assert = require('assert'); - var styles; - - before(function (done) { - // loading hook - require('..'); - - try { - styles = require('./example.css'); - done(); - } catch(e) { - done(e); - } - }); - - it('loaded an object from "example.css" as a module', function () { - assert.strictEqual(typeof styles, 'object'); - }); - - it('module has the "app" key', function () { - assert(styles.hasOwnProperty('app')); - }); - - it('the value of the "app" key is not an empty string', function () { - assert.strictEqual(typeof styles.app, 'string', 'app\'s value is not a string'); - assert(styles.app, 'app contains an empty string'); - }); -}); diff --git a/test/test-cases.js b/test/test-cases.js new file mode 100644 index 0000000..bd93e8a --- /dev/null +++ b/test/test-cases.js @@ -0,0 +1,59 @@ +'use strict'; + +import assert from 'assert' +import fs from 'fs' +import path from 'path' +import FileSystemLoader from 'css-modules-loader-core/lib/file-system-loader' + +let normalize = ( str ) => { + return str.replace( /\r\n?/g, '\n' ); +} + +const pipelines = { + 'test-cases': undefined, + 'cssi': [] +} + +Object.keys( pipelines ).forEach( dirname => { + describe( dirname, () => { + let testDir = path.join( __dirname, dirname ) + + fs.readdirSync( testDir ).forEach( testCase => { + if ( fs.existsSync( path.join( testDir, testCase, 'source.css' ) ) ) { + it( 'should ' + testCase.replace( /-/g, ' ' ), done => { + let expected = normalize( fs.readFileSync( path.join( testDir, testCase, 'expected.css' ), 'utf-8' ) ) + let loader = new FileSystemLoader( testDir, pipelines[dirname] ) + let expectedTokens = JSON.parse( fs.readFileSync( path.join( testDir, testCase, 'expected.json' ), 'utf-8' ) ) + + loader.fetch( `${testCase}/source.css`, '/' ).then( tokens => { + assert.equal( loader.finalSource, expected ) + assert.equal( JSON.stringify( tokens ), JSON.stringify( expectedTokens ) ) + } ).then( done, done ) + } ); + } + } ); + } ); +} ) + +// special case for testing multiple sources +describe( 'multiple sources', () => { + let testDir = path.join( __dirname, 'test-cases' ) + let testCase = 'multiple-sources'; + let dirname = 'test-cases'; + + if ( fs.existsSync( path.join( testDir, testCase, 'source1.css' ) ) ) { + it( 'should ' + testCase.replace( /-/g, ' ' ), done => { + let expected = normalize( fs.readFileSync( path.join( testDir, testCase, 'expected.css' ), 'utf-8' ) ) + let loader = new FileSystemLoader( testDir, pipelines[dirname] ) + let expectedTokens = JSON.parse( fs.readFileSync( path.join( testDir, testCase, 'expected.json' ), 'utf-8' ) ) + + loader.fetch( `${testCase}/source1.css`, '/' ).then( tokens1 => { + loader.fetch( `${testCase}/source2.css`, '/' ).then( tokens2 => { + assert.equal( loader.finalSource, expected ) + const tokens = Object.assign({}, tokens1, tokens2); + assert.equal( JSON.stringify( tokens ), JSON.stringify( expectedTokens ) ) + } ).then( done, done ) + }) + } ); + } +} ); diff --git a/test/test-cases/localise-export/expected.css b/test/test-cases/localise-export/expected.css new file mode 100644 index 0000000..c4be0d9 --- /dev/null +++ b/test/test-cases/localise-export/expected.css @@ -0,0 +1,9 @@ +._localise_export_source__one { + color: red; +} +._localise_export_source__two { + color: green; +} +._localise_export_source__three { + color: blue; +} diff --git a/test/test-cases/localise-export/expected.json b/test/test-cases/localise-export/expected.json new file mode 100644 index 0000000..85eb15c --- /dev/null +++ b/test/test-cases/localise-export/expected.json @@ -0,0 +1,5 @@ +{ + "one": "_localise_export_source__one", + "two": "_localise_export_source__two", + "three": "_localise_export_source__three" +} diff --git a/test/test-cases/localise-export/source.css b/test/test-cases/localise-export/source.css new file mode 100644 index 0000000..8fdc200 --- /dev/null +++ b/test/test-cases/localise-export/source.css @@ -0,0 +1,9 @@ +:local(.one) { + color: red; +} +:local(.two) { + color: green; +} +:local(.three) { + color: blue; +} diff --git a/test/test-cases/multiple-dependencies/b.css b/test/test-cases/multiple-dependencies/b.css new file mode 100644 index 0000000..e29560a --- /dev/null +++ b/test/test-cases/multiple-dependencies/b.css @@ -0,0 +1,8 @@ +.b1 { + composes: d1 d2 from "./d.css"; + color: #b1b1b1; +} + +.b2 { + color: #b2b2b2; +} diff --git a/test/test-cases/multiple-dependencies/c.css b/test/test-cases/multiple-dependencies/c.css new file mode 100644 index 0000000..e5a7b52 --- /dev/null +++ b/test/test-cases/multiple-dependencies/c.css @@ -0,0 +1,3 @@ +.c { + color: #ccc; +} diff --git a/test/test-cases/multiple-dependencies/d.css b/test/test-cases/multiple-dependencies/d.css new file mode 100644 index 0000000..4e7ec24 --- /dev/null +++ b/test/test-cases/multiple-dependencies/d.css @@ -0,0 +1,6 @@ +.d1 { + color: #d1d1d1; +} +.d2 { + color: #d2d2d2; +} diff --git a/test/test-cases/multiple-dependencies/expected.css b/test/test-cases/multiple-dependencies/expected.css new file mode 100644 index 0000000..01b0d9f --- /dev/null +++ b/test/test-cases/multiple-dependencies/expected.css @@ -0,0 +1,20 @@ +._multiple_dependencies_d__d1 { + color: #d1d1d1; +} +._multiple_dependencies_d__d2 { + color: #d2d2d2; +} +._multiple_dependencies_b__b1 { + color: #b1b1b1; +} + +._multiple_dependencies_b__b2 { + color: #b2b2b2; +} +._multiple_dependencies_c__c { + color: #ccc; +} +._multiple_dependencies_source__a { + color: #aaa; +} + diff --git a/test/test-cases/multiple-dependencies/expected.json b/test/test-cases/multiple-dependencies/expected.json new file mode 100644 index 0000000..047434d --- /dev/null +++ b/test/test-cases/multiple-dependencies/expected.json @@ -0,0 +1,3 @@ +{ + "a": "_multiple_dependencies_source__a _multiple_dependencies_b__b1 _multiple_dependencies_d__d1 _multiple_dependencies_d__d2 _multiple_dependencies_b__b2 _multiple_dependencies_c__c" +} diff --git a/test/test-cases/multiple-dependencies/source.css b/test/test-cases/multiple-dependencies/source.css new file mode 100644 index 0000000..46e1595 --- /dev/null +++ b/test/test-cases/multiple-dependencies/source.css @@ -0,0 +1,6 @@ +.a { + composes: b1 b2 from "./b.css"; + composes: c from "./c.css"; + color: #aaa; +} + diff --git a/test/test-cases/multiple-sources/b.css b/test/test-cases/multiple-sources/b.css new file mode 100644 index 0000000..c4dcd92 --- /dev/null +++ b/test/test-cases/multiple-sources/b.css @@ -0,0 +1,4 @@ +.b { + composes: d from "./d.css"; + color: #bbb; +} diff --git a/test/test-cases/multiple-sources/c.css b/test/test-cases/multiple-sources/c.css new file mode 100644 index 0000000..e5a7b52 --- /dev/null +++ b/test/test-cases/multiple-sources/c.css @@ -0,0 +1,3 @@ +.c { + color: #ccc; +} diff --git a/test/test-cases/multiple-sources/d.css b/test/test-cases/multiple-sources/d.css new file mode 100644 index 0000000..4638a27 --- /dev/null +++ b/test/test-cases/multiple-sources/d.css @@ -0,0 +1,3 @@ +.d { + color: #ddd; +} diff --git a/test/test-cases/multiple-sources/expected.css b/test/test-cases/multiple-sources/expected.css new file mode 100644 index 0000000..da64401 --- /dev/null +++ b/test/test-cases/multiple-sources/expected.css @@ -0,0 +1,15 @@ +._multiple_sources_d__d { + color: #ddd; +} +._multiple_sources_b__b { + color: #bbb; +} +._multiple_sources_c__c { + color: #ccc; +} +._multiple_sources_source1__a { + color: #aaa; +} +._multiple_sources_source2__foo { + color: #f00; +} diff --git a/test/test-cases/multiple-sources/expected.json b/test/test-cases/multiple-sources/expected.json new file mode 100644 index 0000000..074abf9 --- /dev/null +++ b/test/test-cases/multiple-sources/expected.json @@ -0,0 +1,4 @@ +{ + "a": "_multiple_sources_source1__a _multiple_sources_b__b _multiple_sources_d__d _multiple_sources_c__c", + "foo": "_multiple_sources_source2__foo _multiple_sources_b__b _multiple_sources_d__d" +} diff --git a/test/test-cases/multiple-sources/source1.css b/test/test-cases/multiple-sources/source1.css new file mode 100644 index 0000000..983c7fd --- /dev/null +++ b/test/test-cases/multiple-sources/source1.css @@ -0,0 +1,5 @@ +.a { + composes: b from "./b.css"; + composes: c from "./c.css"; + color: #aaa; +} diff --git a/test/test-cases/multiple-sources/source2.css b/test/test-cases/multiple-sources/source2.css new file mode 100644 index 0000000..151a720 --- /dev/null +++ b/test/test-cases/multiple-sources/source2.css @@ -0,0 +1,4 @@ +.foo { + composes: b from "./b.css"; + color: #f00; +} diff --git a/test/test-cases/simple-export/expected.css b/test/test-cases/simple-export/expected.css new file mode 100644 index 0000000..2f8e4da --- /dev/null +++ b/test/test-cases/simple-export/expected.css @@ -0,0 +1,7 @@ +._simple_export_source__localName { + color: red; +} + +._simple_export_source__localName:hover { + color: blue; +} diff --git a/test/test-cases/simple-export/expected.json b/test/test-cases/simple-export/expected.json new file mode 100644 index 0000000..c8ee742 --- /dev/null +++ b/test/test-cases/simple-export/expected.json @@ -0,0 +1,3 @@ +{ + "localName": "_simple_export_source__localName" +} diff --git a/test/test-cases/simple-export/source.css b/test/test-cases/simple-export/source.css new file mode 100644 index 0000000..fbb8713 --- /dev/null +++ b/test/test-cases/simple-export/source.css @@ -0,0 +1,7 @@ +.localName { + color: red; +} + +.localName:hover { + color: blue; +} diff --git a/test/test-cases/single-import-export/colors.css b/test/test-cases/single-import-export/colors.css new file mode 100644 index 0000000..a7c1ce1 --- /dev/null +++ b/test/test-cases/single-import-export/colors.css @@ -0,0 +1,7 @@ +.blackShadow { + box-shadow: 0 0 10px -2px black; +} + +.redBorder { + border: 1px solid red; +} diff --git a/test/test-cases/single-import-export/expected.css b/test/test-cases/single-import-export/expected.css new file mode 100644 index 0000000..74398b2 --- /dev/null +++ b/test/test-cases/single-import-export/expected.css @@ -0,0 +1,10 @@ +._single_import_export_colors__blackShadow { + box-shadow: 0 0 10px -2px black; +} + +._single_import_export_colors__redBorder { + border: 1px solid red; +} +._single_import_export_source__localName { + color: red; +} diff --git a/test/test-cases/single-import-export/expected.json b/test/test-cases/single-import-export/expected.json new file mode 100644 index 0000000..802cd4e --- /dev/null +++ b/test/test-cases/single-import-export/expected.json @@ -0,0 +1,3 @@ +{ + "localName": "_single_import_export_source__localName _single_import_export_colors__blackShadow _single_import_export_colors__redBorder" +} diff --git a/test/test-cases/single-import-export/source.css b/test/test-cases/single-import-export/source.css new file mode 100644 index 0000000..57dbc17 --- /dev/null +++ b/test/test-cases/single-import-export/source.css @@ -0,0 +1,4 @@ +.localName { + composes: blackShadow redBorder from "./colors.css"; + color: red; +} From 727173b098b49da5bff8124c08bb59551f080a9a Mon Sep 17 00:00:00 2001 From: Alexey Litvinov Date: Sun, 2 Aug 2015 23:02:32 +0300 Subject: [PATCH 03/11] simple implementation based on postcss --- package.json | 4 +++- src/guard.js | 7 +++++++ src/hook.js | 11 +++++++++++ src/index.js | 21 +++++++++++++++++++++ 4 files changed, 42 insertions(+), 1 deletion(-) create mode 100644 src/guard.js create mode 100644 src/hook.js create mode 100644 src/index.js diff --git a/package.json b/package.json index 5db45c9..7fc89b6 100644 --- a/package.json +++ b/package.json @@ -5,8 +5,10 @@ "main": "index.js", "dependencies": { "css-modules-loader-core": "0.0.11", + "postcss": "^4.1.16", "postcss-modules-extract-imports": "0.0.5", - "postcss-modules-local-by-default": "0.0.11" + "postcss-modules-local-by-default": "0.0.11", + "postcss-modules-scope": "0.0.8" }, "devDependencies": { "babel": "^5.8.20", diff --git a/src/guard.js b/src/guard.js new file mode 100644 index 0000000..c4a1c14 --- /dev/null +++ b/src/guard.js @@ -0,0 +1,7 @@ +'use strict'; + +if (global._cssModulesPolyfill) { + throw new Error('only one instance of css-modules/polyfill is allowed'); +} + +global._cssModulesPolyfill = true; diff --git a/src/hook.js b/src/hook.js new file mode 100644 index 0000000..d7bbd81 --- /dev/null +++ b/src/hook.js @@ -0,0 +1,11 @@ +'use strict'; + +/** + * @param {function} compile + */ +module.exports = function (compile) { + require.extensions['.css'] = function (m, filename) { + var tokens = compile(filename); + return m._compile('module.exports = ' + JSON.stringify(tokens), filename); + }; +}; diff --git a/src/index.js b/src/index.js new file mode 100644 index 0000000..43c36d3 --- /dev/null +++ b/src/index.js @@ -0,0 +1,21 @@ +'use strict'; + +import './guard'; +import fs from 'fs'; +import hook from './hook'; +import postcss from 'postcss'; + +import extractImports from 'postcss-modules-extract-imports'; +import localByDefault from 'postcss-modules-local-by-default'; +import scope from 'postcss-modules-scope'; + +let plugins = [localByDefault, extractImports, scope]; + +hook(filename => postcss(plugins).process(fs.readFileSync(filename, 'utf8')).css); + +/** + * @param {object} opts + * @param {array} opts.u + * @param {array} opts.use + */ +export default function configure(opts) {} From afce4cd0910ca2b7a01ea13f9d988f65f7ec2234 Mon Sep 17 00:00:00 2001 From: Alexey Litvinov Date: Sun, 2 Aug 2015 23:30:54 +0300 Subject: [PATCH 04/11] adopted tests from the css-modules-loader-core to the require-hook --- test/test-hook.js | 46 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 test/test-hook.js diff --git a/test/test-hook.js b/test/test-hook.js new file mode 100644 index 0000000..ea9bb15 --- /dev/null +++ b/test/test-hook.js @@ -0,0 +1,46 @@ +'use strict'; + +import '../src'; +import fs from 'fs' +import path from 'path' +import { equal } from 'assert'; + +const pipelines = { + 'test-cases': undefined, + 'cssi': [] +} + +Object.keys(pipelines).forEach(dirname => { + describe(dirname, () => { + let testDir = path.join(__dirname, dirname); + + fs.readdirSync(testDir).forEach(testCase => { + if (fs.existsSync(path.join(testDir, testCase, 'source.css'))) { + it('should ' + testCase.replace(/-/g, ' '), done => { + let expectedTokens = JSON.parse(fs.readFileSync(path.join(testDir, testCase, 'expected.json'), 'utf-8')); + let tokens = require(`${testDir}/${testCase}/source.css`); + + equal(JSON.stringify(tokens), JSON.stringify(expectedTokens)); + }); + } + }); + }); +}); + +// special case for testing multiple sources +describe( 'multiple sources', () => { + let testDir = path.join(__dirname, 'test-cases'); + let testCase = 'multiple-sources'; + let dirname = 'test-cases'; + + if (fs.existsSync(path.join(testDir, testCase, 'source1.css'))) { + it('should ' + testCase.replace(/-/g, ' '), done => { + let expectedTokens = JSON.parse(fs.readFileSync(path.join(testDir, testCase, 'expected.json'), 'utf-8')); + let tokens1 = require(`${testDir}/${testCase}/source1.css`); + let tokens2 = require(`${testDir}/${testCase}/source2.css`); + let tokens = Object.assign({}, tokens1, tokens2); + + equal(JSON.stringify(tokens), JSON.stringify(expectedTokens)); + }); + } +}); From 2d965b07db48ec6aac89072d9ce799f1dc0609e7 Mon Sep 17 00:00:00 2001 From: Alexey Litvinov Date: Sun, 2 Aug 2015 23:31:27 +0300 Subject: [PATCH 05/11] extended parser --- src/parser.js | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 src/parser.js diff --git a/src/parser.js b/src/parser.js new file mode 100644 index 0000000..1b18a4b --- /dev/null +++ b/src/parser.js @@ -0,0 +1,40 @@ +'use strict'; + +import Parser from 'css-modules-loader-core/lib/parser'; + +class SyncParser extends Parser { + plugin(css, result) { + this.fetchAllImports(css); + this.linkImportedSymbols(css); + this.extractExports(css); + } + + fetchImport(importNode, relativeTo, depNr) { + let file = importNode.selector.match( importRegexp )[1]; + let depTrace = this.trace + String.fromCharCode(depNr); + let exp = this.pathFetcher(file, relativeTo, depTrace); + + importNode.each(decl => { + if (decl.type === 'decl') { + this.translations[decl.prop] = exports[decl.value] + } + }); + + importNode.removeSelf(); + } + + fetchImport( importNode, relativeTo, depNr ) { + let file = importNode.selector.match( importRegexp )[1], + depTrace = this.trace + String.fromCharCode(depNr) + return this.pathFetcher( file, relativeTo, depTrace ).then( exports => { + importNode.each( decl => { + if ( decl.type == 'decl' ) { + this.translations[decl.prop] = exports[decl.value] + } + } ) + importNode.removeSelf() + }, err => console.log( err ) ) + } +} + +export default SyncParser; From 64387e63d76d919eb4b308a190c95d896fc6f160 Mon Sep 17 00:00:00 2001 From: Alexey Litvinov Date: Tue, 4 Aug 2015 22:27:40 +0300 Subject: [PATCH 06/11] custom parser plugin --- src/parser.js | 74 ++++++++++++++++++++++++++++++++++----------------- 1 file changed, 50 insertions(+), 24 deletions(-) diff --git a/src/parser.js b/src/parser.js index 1b18a4b..ae2b55f 100644 --- a/src/parser.js +++ b/src/parser.js @@ -1,40 +1,66 @@ 'use strict'; -import Parser from 'css-modules-loader-core/lib/parser'; +import { plugin } from 'postcss'; -class SyncParser extends Parser { - plugin(css, result) { - this.fetchAllImports(css); - this.linkImportedSymbols(css); - this.extractExports(css); - } +const importRegexp = /^:import\((.+)\)$/ + +export default plugin('parser', function (opts) { + opts = opts || {}; + + let exportTokens = opts.exportTokens; + let translations = {}; - fetchImport(importNode, relativeTo, depNr) { + const fetchImport = (importNode, relativeTo, depNr) => { let file = importNode.selector.match( importRegexp )[1]; - let depTrace = this.trace + String.fromCharCode(depNr); - let exp = this.pathFetcher(file, relativeTo, depTrace); + let depTrace = opts.trace + String.fromCharCode(depNr); + let exports = opts.pathFetcher(file, relativeTo, depTrace); importNode.each(decl => { if (decl.type === 'decl') { - this.translations[decl.prop] = exports[decl.value] + translations[decl.prop] = exports[decl.value]; } }); importNode.removeSelf(); } - fetchImport( importNode, relativeTo, depNr ) { - let file = importNode.selector.match( importRegexp )[1], - depTrace = this.trace + String.fromCharCode(depNr) - return this.pathFetcher( file, relativeTo, depTrace ).then( exports => { - importNode.each( decl => { - if ( decl.type == 'decl' ) { - this.translations[decl.prop] = exports[decl.value] - } - } ) - importNode.removeSelf() - }, err => console.log( err ) ) + const fetchAllImports = css => { + let imports = 0; + + css.each(node => { + if (node.type === 'rule' && node.selector.match(importRegexp)) { + fetchImport(node, css.source.input.from, imports++); + } + }); } -} -export default SyncParser; + const linkImportedSymbols = css => css.eachDecl(decl => { + Object.keys(translations).forEach(translation => { + decl.value = decl.value.replace(translation, translations[translation]) + }); + }); + + const handleExport = exportNode => { + exportNode.each(decl => { + if (decl.type === 'decl') { + Object.keys(translations).forEach(translation => { + decl.value = decl.value.replace(translation, translations[translation]) + }); + + exportTokens[decl.prop] = decl.value; + } + }); + + exportNode.removeSelf(); + } + + const extractExports = css => css.each(node => { + if (node.type === 'rule' && node.selector === ':export') handleExport(node); + }); + + return css => { + fetchAllImports(css); + linkImportedSymbols(css); + extractExports(css); + } +}); From 161ecf020c4260a8af86aa4fa0466ff72fe5090c Mon Sep 17 00:00:00 2001 From: Alexey Litvinov Date: Tue, 4 Aug 2015 22:43:24 +0300 Subject: [PATCH 07/11] barely working version :) --- src/index.js | 44 ++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 42 insertions(+), 2 deletions(-) diff --git a/src/index.js b/src/index.js index 43c36d3..046de30 100644 --- a/src/index.js +++ b/src/index.js @@ -1,17 +1,57 @@ 'use strict'; import './guard'; -import fs from 'fs'; import hook from './hook'; import postcss from 'postcss'; +import { basename, dirname, join, resolve } from 'path'; +import { readFileSync } from 'fs'; import extractImports from 'postcss-modules-extract-imports'; import localByDefault from 'postcss-modules-local-by-default'; import scope from 'postcss-modules-scope'; +import parser from './parser'; let plugins = [localByDefault, extractImports, scope]; -hook(filename => postcss(plugins).process(fs.readFileSync(filename, 'utf8')).css); +const load = (sourceString, sourcePath, trace, pathFetcher) => { + let exportTokens = {}; + let result = postcss(plugins.concat(new parser({ exportTokens, pathFetcher, trace }))) + .process(sourceString, {from: '/' + sourcePath}) + .stringify(); + + return { injectableSource: result.css, exportTokens: exportTokens }; +} + +hook(filename => { + const root = dirname(filename); + const sources = {}; + const tokensByFile = {}; + let importNr = 0; + + const fetch = (_newPath, _relativeTo, _trace) => { + let newPath = _newPath.replace(/^["']|["']$/g, ''); + let trace = _trace || String.fromCharCode(importNr++); + + let relativeDir = dirname(_relativeTo); + let rootRelativePath = resolve(relativeDir, newPath); + let fileRelativePath = resolve(join(root, relativeDir), newPath); + + const tokens = tokensByFile[fileRelativePath]; + if (tokens) { + return tokens; + } + + let source = readFileSync(fileRelativePath, 'utf-8'); + let { injectableSource, exportTokens } = load(source, rootRelativePath, trace, fetch); + + sources[trace] = injectableSource; + tokensByFile[fileRelativePath] = exportTokens; + + return exportTokens; + } + + return fetch(basename(filename), '/'); +}); /** * @param {object} opts From d0ff7e9190efb86ee87987fc5805c5d7c80418a8 Mon Sep 17 00:00:00 2001 From: Alexey Litvinov Date: Tue, 4 Aug 2015 23:46:32 +0300 Subject: [PATCH 08/11] fixed dependency version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 7fc89b6..afe2af4 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,7 @@ "css-modules-loader-core": "0.0.11", "postcss": "^4.1.16", "postcss-modules-extract-imports": "0.0.5", - "postcss-modules-local-by-default": "0.0.11", + "postcss-modules-local-by-default": "0.0.9", "postcss-modules-scope": "0.0.8" }, "devDependencies": { From d07e74ee7e97f8365de70340cf1f229fbfe125d2 Mon Sep 17 00:00:00 2001 From: Alexey Litvinov Date: Tue, 4 Aug 2015 23:47:31 +0300 Subject: [PATCH 09/11] possibility to load custom list of plugins --- src/index.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/index.js b/src/index.js index 046de30..5f2eb40 100644 --- a/src/index.js +++ b/src/index.js @@ -58,4 +58,11 @@ hook(filename => { * @param {array} opts.u * @param {array} opts.use */ -export default function configure(opts) {} +export default function configure(opts) { + opts = opts || {}; + + let customPlugins = opts.u || opts.use; + plugins = Array.isArray(customPlugins) + ? customPlugins + : [localByDefault, extractImports, scope]; +} From 25ed8fe356ea1fb80bbc0e1a5e76e9ec38cd61bc Mon Sep 17 00:00:00 2001 From: Alexey Litvinov Date: Tue, 4 Aug 2015 23:48:01 +0300 Subject: [PATCH 10/11] test fixes --- test/test-hook.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/test/test-hook.js b/test/test-hook.js index ea9bb15..a87328c 100644 --- a/test/test-hook.js +++ b/test/test-hook.js @@ -1,10 +1,11 @@ 'use strict'; -import '../src'; import fs from 'fs' import path from 'path' import { equal } from 'assert'; +import hook from '../src'; + const pipelines = { 'test-cases': undefined, 'cssi': [] @@ -16,7 +17,8 @@ Object.keys(pipelines).forEach(dirname => { fs.readdirSync(testDir).forEach(testCase => { if (fs.existsSync(path.join(testDir, testCase, 'source.css'))) { - it('should ' + testCase.replace(/-/g, ' '), done => { + it('should ' + testCase.replace(/-/g, ' '), () => { + hook({use: pipelines[dirname]}); let expectedTokens = JSON.parse(fs.readFileSync(path.join(testDir, testCase, 'expected.json'), 'utf-8')); let tokens = require(`${testDir}/${testCase}/source.css`); @@ -34,7 +36,8 @@ describe( 'multiple sources', () => { let dirname = 'test-cases'; if (fs.existsSync(path.join(testDir, testCase, 'source1.css'))) { - it('should ' + testCase.replace(/-/g, ' '), done => { + it('should ' + testCase.replace(/-/g, ' '), () => { + hook({use: pipelines[dirname]}); let expectedTokens = JSON.parse(fs.readFileSync(path.join(testDir, testCase, 'expected.json'), 'utf-8')); let tokens1 = require(`${testDir}/${testCase}/source1.css`); let tokens2 = require(`${testDir}/${testCase}/source2.css`); From 1c2f082df6bd93fc90502276ab2d0113e7aa62b8 Mon Sep 17 00:00:00 2001 From: Alexey Litvinov Date: Tue, 4 Aug 2015 23:57:16 +0300 Subject: [PATCH 11/11] simple loader without precompiling --- index.js | 83 ++++++++-------------------------------------------- package.json | 2 +- 2 files changed, 13 insertions(+), 72 deletions(-) diff --git a/index.js b/index.js index 45bff14..88c9ed8 100644 --- a/index.js +++ b/index.js @@ -1,78 +1,19 @@ 'use strict'; -if (global._cssModulesPolyfill) { - throw new Error('only one instance of css-modules/polyfill is allowed'); -} - -global._cssModulesPolyfill = true; - -var assign = require('object-assign'); -var options = {}; +var path = require('path'); -/** - * Posibility to pass custom options to the module - * @param {object} opts - */ -module.exports = function (opts) { - assign(options, opts); +var escape = function (str) { + return str.replace(/[\[\]\/{}()*+?.\\^$|-]/g, '\\$&'); }; -var Core = require('css-modules-loader-core'); -var pluginsCache; - -/** - * Caching plugins for the future calls - * @return {array} - */ -function loadPlugins() { - // retrieving from cache if they are already loaded - if (pluginsCache) { - return pluginsCache; - } - - // PostCSS plugins passed to FileSystemLoader - var plugins = options.use || options.u; - if (!plugins) { - plugins = Core.defaultPlugins; - } else { - if (typeof plugins === 'string') { - plugins = [ plugins ]; - } - - plugins = plugins.map(function requirePlugin (name) { - // assume functions are already required plugins - if (typeof name === 'function') { - return name; - } - - var plugin = require(require.resolve(name)); +var regexp = ['src', 'test'].map(function (i) { + return '^' + escape(path.join(__dirname, i) + path.sep); +}).join('|'); - // custom scoped name generation - if (name === 'postcss-modules-scope') { - options[name] = options[name] || {}; - options[name].generateScopedName = createScopedNameFunc(plugin); - } +require('babel/register')({ + only: new RegExp('(' + regexp + ')'), + ignore: false, + loose: 'all' +}); - if (name in options) { - plugin = plugin(options[name]); - } else { - plugin = plugin.postcss || plugin(); - } - - return plugin; - }); - } - - return pluginsCache = plugins; -} - -var FileSystemLoader = require('css-modules-loader-core/lib/file-system-loader'); -var path = require('path'); - -require.extensions['.css'] = function (m, filename) { - var plugins = loadPlugins(); - var loader = new FileSystemLoader(path.dirname(filename), plugins); - var tokens = loader.fetchSync(path.basename(filename), '/'); - - return m._compile('module.exports = ' + JSON.stringify(tokens), filename); -}; +module.exports = require('./src'); diff --git a/package.json b/package.json index afe2af4..bf40324 100644 --- a/package.json +++ b/package.json @@ -4,6 +4,7 @@ "description": "A require hook to compile CSS Modules on the fly", "main": "index.js", "dependencies": { + "babel": "^5.8.20", "css-modules-loader-core": "0.0.11", "postcss": "^4.1.16", "postcss-modules-extract-imports": "0.0.5", @@ -11,7 +12,6 @@ "postcss-modules-scope": "0.0.8" }, "devDependencies": { - "babel": "^5.8.20", "mocha": "^2.2.5" }, "scripts": {