From 9978de970058417fef2ba3bfe21520e610295622 Mon Sep 17 00:00:00 2001 From: Alexey Litvinov Date: Mon, 23 Nov 2015 09:35:48 +0300 Subject: [PATCH 1/6] the new API --- src/file-system-loader.js | 61 +++++++------- src/index.js | 83 +++++++++++++------ src/parser.js | 63 -------------- test/test-cases.js | 62 +++++++------- .../compose-node-module/expected.css | 2 +- .../compose-node-module/expected.json | 2 +- .../multiple-dependencies/expected.css | 12 +-- test/test-cases/multiple-sources/expected.css | 6 +- 8 files changed, 132 insertions(+), 159 deletions(-) delete mode 100644 src/parser.js diff --git a/src/file-system-loader.js b/src/file-system-loader.js index 2a3f47d..abf54f8 100644 --- a/src/file-system-loader.js +++ b/src/file-system-loader.js @@ -1,6 +1,6 @@ -import Core from './index.js' -import fs from 'fs' -import path from 'path' +import core from './index' +import { readFile } from 'fs' +import { dirname, resolve } from 'path' // Sorts dependencies in the following way: // AAA comes before AA and A @@ -20,47 +20,42 @@ const traceKeySorter = ( a, b ) => { }; export default class FileSystemLoader { - constructor( root, plugins ) { - this.root = root - this.sources = {} + constructor( options, processorOptions = {} ) { + this.processorOptions = processorOptions + this.core = core( options, this.fetch.bind(this) ) this.importNr = 0 - this.core = new Core(plugins) - this.tokensByFile = {}; + this.sources = {} + this.tokensByFile = {} } - fetch( _newPath, relativeTo, _trace ) { - let newPath = _newPath.replace( /^["']|["']$/g, "" ), - trace = _trace || String.fromCharCode( this.importNr++ ) - return new Promise( ( resolve, reject ) => { - let relativeDir = path.dirname( relativeTo ), - rootRelativePath = path.resolve( relativeDir, newPath ), - fileRelativePath = path.resolve( path.join( this.root, relativeDir ), newPath ) + fetch( _to, from ) { + let to = _to.replace( /^["']|["']$/g, '' ), + trace = String.fromCharCode( this.importNr++ ) + + const filename = /\w/i.test(to[0]) + ? require.resolve(to) + : resolve(dirname(from), to) - // if the path is not relative or absolute, try to resolve it in node_modules - if (newPath[0] !== '.' && newPath[0] !== '/') { - try { - fileRelativePath = require.resolve(newPath); + return new Promise(( resolve, reject ) => { + readFile( filename, 'utf8', (err, source) => { + if (err) { + return void reject(err); } - catch (e) {} - } - const tokens = this.tokensByFile[fileRelativePath] - if (tokens) { return resolve(tokens) } + this.core.process( source, Object.assign( this.processorOptions, { from: filename } ) ) + .then( result => { + this.sources[filename] = result.css + this.tokensByFile[filename] = result.root.tokens - fs.readFile( fileRelativePath, "utf-8", ( err, source ) => { - if ( err ) reject( err ) - this.core.load( source, rootRelativePath, trace, this.fetch.bind( this ) ) - .then( ( { injectableSource, exportTokens } ) => { - this.sources[trace] = injectableSource - this.tokensByFile[fileRelativePath] = exportTokens - resolve( exportTokens ) - }, reject ) + resolve( this.tokensByFile[filename] ) + } ) + .catch( reject ) } ) - } ) + }) } get finalSource() { return Object.keys( this.sources ).sort( traceKeySorter ).map( s => this.sources[s] ) - .join( "" ) + .join( '' ) } } diff --git a/src/index.js b/src/index.js index 92795bd..0f46eac 100644 --- a/src/index.js +++ b/src/index.js @@ -1,31 +1,66 @@ -import postcss from 'postcss' -import localByDefault from 'postcss-modules-local-by-default' -import extractImports from 'postcss-modules-extract-imports' -import scope from 'postcss-modules-scope' -import values from 'postcss-modules-values' +import postcss from 'postcss'; +import genericNames from 'generic-names'; +import partialRight from 'lodash.partialright'; +import { relative } from 'path'; -import Parser from './parser' +import LocalByDefault from 'postcss-modules-local-by-default' +import ExtractImports from 'postcss-modules-extract-imports' +import Scope from 'postcss-modules-scope' +import Parser from 'postcss-modules-parser' +import Values from 'postcss-modules-values' -export default class Core { - constructor( plugins ) { - this.plugins = plugins || Core.defaultPlugins - } +/** + * @param {array} options.append + * @param {array} options.prepend + * @param {array} options.use + * @param {function} options.createImportedName + * @param {function|string} options.generateScopedName + * @param {string} options.mode + * @param {string} options.rootDir + * @param {function} fetch + * @return {object} + */ +export default function core({ + append = [], + prepend = [], + createImportedName, + generateScopedName: scopedName, + rootDir: context = process.cwd(), + mode, + use, +} = {}, _fetch) { + let instance + let generateScopedName - load( sourceString, sourcePath, trace, pathFetcher ) { - let parser = new Parser( pathFetcher, trace ) + const fetch = function () { + return _fetch.apply(null, Array.prototype.slice.call(arguments).concat(instance)); + } - return postcss( this.plugins.concat( [parser.plugin] ) ) - .process( sourceString, { from: "/" + sourcePath } ) - .then( result => { - return { injectableSource: result.css, exportTokens: parser.exportTokens } - } ) + if (scopedName) { + // https://github.com/css-modules/postcss-modules-scope/blob/master/src/index.js#L38 + generateScopedName = typeof scopedName !== 'function' + ? genericNames(scopedName || '[name]__[local]___[hash:base64:5]', {context}) + : (local, filepath, css) => scopedName(local, filepath, css, context) + } else { + generateScopedName = (localName, filepath) => { + return Scope.generateScopedName(localName, relative(context, filepath)); + } } -} -// These four plugins are aliased under this package for simplicity. -Core.values = values -Core.localByDefault = localByDefault -Core.extractImports = extractImports -Core.scope = scope + const plugins = (use || [ + ...prepend, + Values, + mode + ? new LocalByDefault({mode}) + : LocalByDefault, + createImportedName + ? new ExtractImports({createImportedName}) + : ExtractImports, + new Scope({generateScopedName}), + ...append, + ]) + .concat(new Parser({fetch})) // no pushing in order to avoid the possible mutations -Core.defaultPlugins = [values, localByDefault, extractImports, scope] + instance = postcss(plugins) + return instance; +} diff --git a/src/parser.js b/src/parser.js deleted file mode 100644 index 8dfb1dc..0000000 --- a/src/parser.js +++ /dev/null @@ -1,63 +0,0 @@ -const importRegexp = /^:import\((.+)\)$/ -import replaceSymbols from 'icss-replace-symbols' - -export default class Parser { - constructor( pathFetcher, trace ) { - this.pathFetcher = pathFetcher - this.plugin = this.plugin.bind( this ) - this.exportTokens = {} - this.translations = {} - this.trace = trace - } - - plugin( css, result ) { - return Promise.all( this.fetchAllImports( css ) ) - .then( _ => this.linkImportedSymbols( css ) ) - .then( _ => this.extractExports( css ) ) - } - - fetchAllImports( css ) { - let imports = [] - css.each( node => { - if ( node.type == "rule" && node.selector.match( importRegexp ) ) { - imports.push( this.fetchImport( node, css.source.input.from, imports.length ) ) - } - } ) - return imports - } - - linkImportedSymbols( css ) { - replaceSymbols(css, this.translations) - } - - extractExports( css ) { - css.each( node => { - if ( node.type == "rule" && node.selector == ":export" ) this.handleExport( node ) - } ) - } - - handleExport( exportNode ) { - exportNode.each( decl => { - if ( decl.type == 'decl' ) { - Object.keys(this.translations).forEach( translation => { - decl.value = decl.value.replace(translation, this.translations[translation]) - } ) - this.exportTokens[decl.prop] = decl.value - } - } ) - exportNode.remove() - } - - 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.remove() - }, err => console.log( err ) ) - } -} diff --git a/test/test-cases.js b/test/test-cases.js index c6adcff..b015fa8 100644 --- a/test/test-cases.js +++ b/test/test-cases.js @@ -1,32 +1,34 @@ -"use strict"; - -import assert from "assert" -import fs from "fs" -import path from "path" -import FileSystemLoader from "../src/file-system-loader" +import assert from 'assert' +import { existsSync, readdirSync, readFileSync } from 'fs' +import { join } from 'path' +import FileSystemLoader from '../src/file-system-loader' let normalize = ( str ) => { - return str.replace( /\r\n?/g, "\n" ); + return str.replace( /\r\n?/g, '\n' ); } const pipelines = { - "test-cases": undefined, - "cssi": [] + '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 ) + let testDir = join( __dirname, dirname ) + readdirSync( testDir ).forEach( testCase => { + if ( existsSync( join( testDir, testCase, 'source.css' ) ) ) { + it( 'should ' + testCase.replace( /-/g, ' ' ), done => { + const loader = new FileSystemLoader({rootDir: testDir, use: pipelines[dirname]}) + + let expected = normalize( readFileSync( join( testDir, testCase, 'expected.css' ), 'utf-8' ) ) + let expectedTokens = JSON.parse( readFileSync( join( testDir, testCase, 'expected.json' ), 'utf-8' ) ) + let filepath = join(testDir, testCase, 'source.css') + + loader.fetch(filepath, filepath, null) + .then( tokens => { + assert.equal( loader.finalSource, expected ) + assert.equal( JSON.stringify( tokens ), JSON.stringify( expectedTokens ) ) + } ).then( done, done ) } ); } } ); @@ -35,16 +37,20 @@ Object.keys( pipelines ).forEach( dirname => { // special case for testing multiple sources describe( 'multiple sources', () => { - let testDir = path.join( __dirname, 'test-cases' ) + let testDir = 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 => { + if ( existsSync( join( testDir, testCase, 'source1.css' ) ) ) { + it( 'should ' + testCase.replace( /-/g, ' ' ), done => { + const loader = new FileSystemLoader({rootDir: testDir, use: pipelines[dirname]}) + + let expected = normalize( readFileSync( join( testDir, testCase, 'expected.css' ), 'utf-8' ) ) + let expectedTokens = JSON.parse( readFileSync( join( testDir, testCase, 'expected.json' ), 'utf-8' ) ) + let filepath1 = join(testDir, testCase, 'source1.css') + let filepath2 = join(testDir, testCase, 'source2.css') + + loader.fetch( filepath1, filepath1, null ).then( tokens1 => { + loader.fetch( filepath2, filepath2, null ).then( tokens2 => { assert.equal( loader.finalSource, expected ) const tokens = Object.assign({}, tokens1, tokens2); assert.equal( JSON.stringify( tokens ), JSON.stringify( expectedTokens ) ) diff --git a/test/test-cases/compose-node-module/expected.css b/test/test-cases/compose-node-module/expected.css index 0667b94..f304ae0 100644 --- a/test/test-cases/compose-node-module/expected.css +++ b/test/test-cases/compose-node-module/expected.css @@ -1,4 +1,4 @@ -._compose_node_module_cool_styles_foo__example { +._node_modules_cool_styles_foo__example { color: #F00; } ._compose_node_module_source__foo { diff --git a/test/test-cases/compose-node-module/expected.json b/test/test-cases/compose-node-module/expected.json index a57448c..ce71efb 100644 --- a/test/test-cases/compose-node-module/expected.json +++ b/test/test-cases/compose-node-module/expected.json @@ -1,3 +1,3 @@ { - "foo": "_compose_node_module_source__foo _compose_node_module_cool_styles_foo__example" + "foo": "_compose_node_module_source__foo _node_modules_cool_styles_foo__example" } diff --git a/test/test-cases/multiple-dependencies/expected.css b/test/test-cases/multiple-dependencies/expected.css index 01b0d9f..3393a94 100644 --- a/test/test-cases/multiple-dependencies/expected.css +++ b/test/test-cases/multiple-dependencies/expected.css @@ -1,9 +1,3 @@ -._multiple_dependencies_d__d1 { - color: #d1d1d1; -} -._multiple_dependencies_d__d2 { - color: #d2d2d2; -} ._multiple_dependencies_b__b1 { color: #b1b1b1; } @@ -14,6 +8,12 @@ ._multiple_dependencies_c__c { color: #ccc; } +._multiple_dependencies_d__d1 { + color: #d1d1d1; +} +._multiple_dependencies_d__d2 { + color: #d2d2d2; +} ._multiple_dependencies_source__a { color: #aaa; } diff --git a/test/test-cases/multiple-sources/expected.css b/test/test-cases/multiple-sources/expected.css index da64401..938a13c 100644 --- a/test/test-cases/multiple-sources/expected.css +++ b/test/test-cases/multiple-sources/expected.css @@ -1,12 +1,12 @@ -._multiple_sources_d__d { - color: #ddd; -} ._multiple_sources_b__b { color: #bbb; } ._multiple_sources_c__c { color: #ccc; } +._multiple_sources_d__d { + color: #ddd; +} ._multiple_sources_source1__a { color: #aaa; } From 347716045c7815ebc11305563162db0b59afcdb3 Mon Sep 17 00:00:00 2001 From: Alexey Litvinov Date: Mon, 23 Nov 2015 09:51:06 +0300 Subject: [PATCH 2/6] output warnings --- src/file-system-loader.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/file-system-loader.js b/src/file-system-loader.js index abf54f8..6343f88 100644 --- a/src/file-system-loader.js +++ b/src/file-system-loader.js @@ -47,6 +47,9 @@ export default class FileSystemLoader { this.sources[filename] = result.css this.tokensByFile[filename] = result.root.tokens + // https://github.com/postcss/postcss/blob/master/docs/api.md#lazywarnings + result.warnings().forEach(message => console.warn(message.text)); + resolve( this.tokensByFile[filename] ) } ) .catch( reject ) From 7499554fc76e918bf04c0777407017fd245e4704 Mon Sep 17 00:00:00 2001 From: Alexey Litvinov Date: Mon, 23 Nov 2015 10:05:11 +0300 Subject: [PATCH 3/6] fixing missing dependencies --- package.json | 6 ++++-- src/index.js | 1 - 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 2443f9a..a47d262 100644 --- a/package.json +++ b/package.json @@ -7,12 +7,14 @@ "test": "test" }, "dependencies": { + "generic-names": "^1.0.1", "icss-replace-symbols": "1.0.2", "postcss": "5.0.10", - "postcss-modules-values": "1.1.1", "postcss-modules-extract-imports": "1.0.0", "postcss-modules-local-by-default": "1.0.0", - "postcss-modules-scope": "1.0.0" + "postcss-modules-parser": "^1.0.1", + "postcss-modules-scope": "1.0.0", + "postcss-modules-values": "1.1.1" }, "devDependencies": { "babel": "5.8.29", diff --git a/src/index.js b/src/index.js index 0f46eac..40d1f0a 100644 --- a/src/index.js +++ b/src/index.js @@ -1,6 +1,5 @@ import postcss from 'postcss'; import genericNames from 'generic-names'; -import partialRight from 'lodash.partialright'; import { relative } from 'path'; import LocalByDefault from 'postcss-modules-local-by-default' From da9603e25da2d841ec1cf0c7019e55e4a3bb5b8b Mon Sep 17 00:00:00 2001 From: Alexey Litvinov Date: Tue, 24 Nov 2015 01:39:46 +0300 Subject: [PATCH 4/6] resolving filename inside promise in order to handle errors propely --- src/file-system-loader.js | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/src/file-system-loader.js b/src/file-system-loader.js index 6343f88..a881139 100644 --- a/src/file-system-loader.js +++ b/src/file-system-loader.js @@ -29,17 +29,18 @@ export default class FileSystemLoader { } fetch( _to, from ) { - let to = _to.replace( /^["']|["']$/g, '' ), - trace = String.fromCharCode( this.importNr++ ) + let to = _to.replace( /^["']|["']$/g, '' ) - const filename = /\w/i.test(to[0]) - ? require.resolve(to) - : resolve(dirname(from), to) + return new Promise(( _resolve, _reject ) => { + const filename = /\w/i.test(to[0]) + ? require.resolve(to) + : resolve(dirname(from), to) + + const trace = String.fromCharCode( this.importNr++ ) - return new Promise(( resolve, reject ) => { readFile( filename, 'utf8', (err, source) => { if (err) { - return void reject(err); + return void _reject(err); } this.core.process( source, Object.assign( this.processorOptions, { from: filename } ) ) @@ -50,9 +51,9 @@ export default class FileSystemLoader { // https://github.com/postcss/postcss/blob/master/docs/api.md#lazywarnings result.warnings().forEach(message => console.warn(message.text)); - resolve( this.tokensByFile[filename] ) + _resolve( this.tokensByFile[filename] ) } ) - .catch( reject ) + .catch( _reject ) } ) }) } From 94e60713c4c34e234b67f2a43ff713e45cea7b76 Mon Sep 17 00:00:00 2001 From: Alexey Litvinov Date: Wed, 25 Nov 2015 01:27:09 +0300 Subject: [PATCH 5/6] parser update --- package.json | 2 +- src/file-system-loader.js | 4 +--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index a47d262..2360a33 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "postcss": "5.0.10", "postcss-modules-extract-imports": "1.0.0", "postcss-modules-local-by-default": "1.0.0", - "postcss-modules-parser": "^1.0.1", + "postcss-modules-parser": "^1.1.0", "postcss-modules-scope": "1.0.0", "postcss-modules-values": "1.1.1" }, diff --git a/src/file-system-loader.js b/src/file-system-loader.js index a881139..f17792e 100644 --- a/src/file-system-loader.js +++ b/src/file-system-loader.js @@ -28,9 +28,7 @@ export default class FileSystemLoader { this.tokensByFile = {} } - fetch( _to, from ) { - let to = _to.replace( /^["']|["']$/g, '' ) - + fetch( to, from ) { return new Promise(( _resolve, _reject ) => { const filename = /\w/i.test(to[0]) ? require.resolve(to) From b626256652eb1d658c5a4d039d5136eb23a26a6d Mon Sep 17 00:00:00 2001 From: Alexey Litvinov Date: Wed, 25 Nov 2015 01:35:41 +0300 Subject: [PATCH 6/6] trace fix --- src/file-system-loader.js | 22 ++++++++++++++----- .../multiple-dependencies/expected.css | 12 +++++----- test/test-cases/multiple-sources/expected.css | 6 ++--- 3 files changed, 25 insertions(+), 15 deletions(-) diff --git a/src/file-system-loader.js b/src/file-system-loader.js index f17792e..5100128 100644 --- a/src/file-system-loader.js +++ b/src/file-system-loader.js @@ -26,15 +26,25 @@ export default class FileSystemLoader { this.importNr = 0 this.sources = {} this.tokensByFile = {} + this.trace = {} } - fetch( to, from ) { + fetch( to, from, depTrace ) { return new Promise(( _resolve, _reject ) => { - const filename = /\w/i.test(to[0]) - ? require.resolve(to) - : resolve(dirname(from), to) + const filename = /\w/i.test( to[0] ) + ? require.resolve( to ) + : resolve( dirname( from ), to ) - const trace = String.fromCharCode( this.importNr++ ) + if ( this.tokensByFile[filename] ) { + return void _resolve( this.tokensByFile[filename] ) + } + + let trace = this.trace[from] || String.fromCharCode( this.importNr++ ) + if (typeof depTrace === 'number') { + trace += String.fromCharCode( depTrace ) + } + + this.trace[filename] = trace readFile( filename, 'utf8', (err, source) => { if (err) { @@ -43,7 +53,7 @@ export default class FileSystemLoader { this.core.process( source, Object.assign( this.processorOptions, { from: filename } ) ) .then( result => { - this.sources[filename] = result.css + this.sources[trace] = result.css this.tokensByFile[filename] = result.root.tokens // https://github.com/postcss/postcss/blob/master/docs/api.md#lazywarnings diff --git a/test/test-cases/multiple-dependencies/expected.css b/test/test-cases/multiple-dependencies/expected.css index 3393a94..01b0d9f 100644 --- a/test/test-cases/multiple-dependencies/expected.css +++ b/test/test-cases/multiple-dependencies/expected.css @@ -1,3 +1,9 @@ +._multiple_dependencies_d__d1 { + color: #d1d1d1; +} +._multiple_dependencies_d__d2 { + color: #d2d2d2; +} ._multiple_dependencies_b__b1 { color: #b1b1b1; } @@ -8,12 +14,6 @@ ._multiple_dependencies_c__c { color: #ccc; } -._multiple_dependencies_d__d1 { - color: #d1d1d1; -} -._multiple_dependencies_d__d2 { - color: #d2d2d2; -} ._multiple_dependencies_source__a { color: #aaa; } diff --git a/test/test-cases/multiple-sources/expected.css b/test/test-cases/multiple-sources/expected.css index 938a13c..da64401 100644 --- a/test/test-cases/multiple-sources/expected.css +++ b/test/test-cases/multiple-sources/expected.css @@ -1,12 +1,12 @@ +._multiple_sources_d__d { + color: #ddd; +} ._multiple_sources_b__b { color: #bbb; } ._multiple_sources_c__c { color: #ccc; } -._multiple_sources_d__d { - color: #ddd; -} ._multiple_sources_source1__a { color: #aaa; }