From 4550e6cfdb3fd0bd23afce293a21c8984c65e25d Mon Sep 17 00:00:00 2001 From: Ryan Tsao Date: Fri, 21 Aug 2015 22:34:57 -0700 Subject: [PATCH 1/2] Initial dead code elimination --- package.json | 1 + src/file-system-loader.js | 41 ++++++++++++++++--- src/parser.js | 9 ++-- test/test-cases/multiple-composes/c.css | 14 +++++++ test/test-cases/multiple-composes/d.css | 15 +++++++ .../test-cases/multiple-composes/expected.css | 25 +++++++++++ .../multiple-composes/expected.json | 4 ++ test/test-cases/multiple-composes/source.css | 9 ++++ test/test-cases/multiple-sources/b.css | 8 ++++ test/test-cases/multiple-sources/expected.css | 3 ++ .../test-cases/multiple-sources/expected.json | 2 +- test/test-cases/multiple-sources/source2.css | 2 +- 12 files changed, 123 insertions(+), 10 deletions(-) create mode 100644 test/test-cases/multiple-composes/c.css create mode 100644 test/test-cases/multiple-composes/d.css create mode 100644 test/test-cases/multiple-composes/expected.css create mode 100644 test/test-cases/multiple-composes/expected.json create mode 100644 test/test-cases/multiple-composes/source.css diff --git a/package.json b/package.json index ecec1f9..f4a77c2 100644 --- a/package.json +++ b/package.json @@ -7,6 +7,7 @@ "test": "test" }, "dependencies": { + "lodash.partialright": "^3.1.1", "postcss": "^4.1.11", "postcss-modules-extract-imports": "^0.0.5", "postcss-modules-local-by-default": "^0.0.9", diff --git a/src/file-system-loader.js b/src/file-system-loader.js index 2a3f47d..2096af0 100644 --- a/src/file-system-loader.js +++ b/src/file-system-loader.js @@ -1,6 +1,8 @@ import Core from './index.js' import fs from 'fs' import path from 'path' +import partialRight from 'lodash.partialright' +import {parse} from 'postcss'; // Sorts dependencies in the following way: // AAA comes before AA and A @@ -28,7 +30,7 @@ export default class FileSystemLoader { this.tokensByFile = {}; } - fetch( _newPath, relativeTo, _trace ) { + fetch( _newPath, relativeTo, _trace, composedClasses ) { let newPath = _newPath.replace( /^["']|["']$/g, "" ), trace = _trace || String.fromCharCode( this.importNr++ ) return new Promise( ( resolve, reject ) => { @@ -44,12 +46,36 @@ export default class FileSystemLoader { catch (e) {} } - const tokens = this.tokensByFile[fileRelativePath] - if (tokens) { return resolve(tokens) } + fs.readFile( fileRelativePath, 'utf-8', ( err, source ) => { + + if (composedClasses) { + let root = parse(source); + + let exportedNames = {}; + + root.each( node => { + if ( node.type === 'rule' && node.selector === ':export' ) { + node.each( decl => { + exportedNames[decl.prop] = decl.value; + } ) + } + } ) + + root.each(decl => { + if (!composedClasses.some(name => { + if (exportedNames[name]) { + name = exportedNames[name] + } + return decl.selector === ':export' || containsClass(decl.selector, name) + })) { + decl.removeSelf() + } + }) + source = root.toString() + } - fs.readFile( fileRelativePath, "utf-8", ( err, source ) => { if ( err ) reject( err ) - this.core.load( source, rootRelativePath, trace, this.fetch.bind( this ) ) + this.core.load( source, rootRelativePath, trace, partialRight(this.fetch.bind( this ), composedClasses) ) .then( ( { injectableSource, exportTokens } ) => { this.sources[trace] = injectableSource this.tokensByFile[fileRelativePath] = exportTokens @@ -64,3 +90,8 @@ export default class FileSystemLoader { .join( "" ) } } + +function containsClass(selector, className) { + let test = new RegExp('\\.' + className + '($|[\\s\.\[,>+~#:])'); + return selector.match(test); +} diff --git a/src/parser.js b/src/parser.js index 536e9cd..cc51206 100644 --- a/src/parser.js +++ b/src/parser.js @@ -16,10 +16,12 @@ export default class Parser { } fetchAllImports( css ) { + let importedClasses = [] let imports = [] css.each( node => { if ( node.type == "rule" && node.selector.match( importRegexp ) ) { - imports.push( this.fetchImport( node, css.source.input.from, imports.length ) ) + importedClasses = node.nodes.map(decl => decl.value); + imports.push( this.fetchImport( node, css.source.input.from, imports.length, importedClasses ) ) } } ) return imports @@ -51,10 +53,11 @@ export default class Parser { exportNode.removeSelf() } - fetchImport( importNode, relativeTo, depNr ) { + fetchImport( importNode, relativeTo, depNr, stuff) { let file = importNode.selector.match( importRegexp )[1], depTrace = this.trace + String.fromCharCode(depNr) - return this.pathFetcher( file, relativeTo, depTrace ).then( exports => { + + return this.pathFetcher( file, relativeTo, depTrace, stuff ).then( exports => { importNode.each( decl => { if ( decl.type == 'decl' ) { this.translations[decl.prop] = exports[decl.value] diff --git a/test/test-cases/multiple-composes/c.css b/test/test-cases/multiple-composes/c.css new file mode 100644 index 0000000..3a8d1e8 --- /dev/null +++ b/test/test-cases/multiple-composes/c.css @@ -0,0 +1,14 @@ +.c1 { + composes: d1 from "./d.css"; + color: #c1c1c1; +} + +.c2 { + composes: d2 from "./d.css"; + color: #c2c2c2; +} + +.c3 { + composes: d3 from "./d.css"; + color: #c3c3c3; +} diff --git a/test/test-cases/multiple-composes/d.css b/test/test-cases/multiple-composes/d.css new file mode 100644 index 0000000..fb4affa --- /dev/null +++ b/test/test-cases/multiple-composes/d.css @@ -0,0 +1,15 @@ +.d1 { + color: #d1d1d1; +} + +.d2 { + color: #d2d2d2; +} + +.d3 { + color: #d3d3d3; +} + +.d1 .another { + color: #555; +} diff --git a/test/test-cases/multiple-composes/expected.css b/test/test-cases/multiple-composes/expected.css new file mode 100644 index 0000000..b7a3267 --- /dev/null +++ b/test/test-cases/multiple-composes/expected.css @@ -0,0 +1,25 @@ +._multiple_composes_d__d1 { + color: #d1d1d1; +} + +._multiple_composes_d__d2 { + color: #d2d2d2; +} + +._multiple_composes_d__d1 ._multiple_composes_d__another { + color: #555; +} +._multiple_composes_c__c1 { + color: #c1c1c1; +} + +._multiple_composes_c__c2 { + color: #c2c2c2; +} +._multiple_composes_source__a { + color: #aaa; +} + +._multiple_composes_source__b { + color: #bbb; +} diff --git a/test/test-cases/multiple-composes/expected.json b/test/test-cases/multiple-composes/expected.json new file mode 100644 index 0000000..6e79e6b --- /dev/null +++ b/test/test-cases/multiple-composes/expected.json @@ -0,0 +1,4 @@ +{ + "a": "_multiple_composes_source__a _multiple_composes_c__c1 _multiple_composes_d__d1", + "b": "_multiple_composes_source__b _multiple_composes_c__c2 _multiple_composes_d__d2" +} diff --git a/test/test-cases/multiple-composes/source.css b/test/test-cases/multiple-composes/source.css new file mode 100644 index 0000000..58d35c9 --- /dev/null +++ b/test/test-cases/multiple-composes/source.css @@ -0,0 +1,9 @@ +.a { + composes: c1 from "./c.css"; + color: #aaa; +} + +.b { + composes: c2 from "./c.css"; + color: #bbb; +} diff --git a/test/test-cases/multiple-sources/b.css b/test/test-cases/multiple-sources/b.css index c4dcd92..9cdefac 100644 --- a/test/test-cases/multiple-sources/b.css +++ b/test/test-cases/multiple-sources/b.css @@ -2,3 +2,11 @@ composes: d from "./d.css"; color: #bbb; } + +.b2 { + color: #b2b2b2; +} + +.ab { + color: #ababab; +} diff --git a/test/test-cases/multiple-sources/expected.css b/test/test-cases/multiple-sources/expected.css index da64401..f36c685 100644 --- a/test/test-cases/multiple-sources/expected.css +++ b/test/test-cases/multiple-sources/expected.css @@ -10,6 +10,9 @@ ._multiple_sources_source1__a { color: #aaa; } +._multiple_sources_b__b2 { + color: #b2b2b2; +} ._multiple_sources_source2__foo { color: #f00; } diff --git a/test/test-cases/multiple-sources/expected.json b/test/test-cases/multiple-sources/expected.json index 074abf9..831d7ba 100644 --- a/test/test-cases/multiple-sources/expected.json +++ b/test/test-cases/multiple-sources/expected.json @@ -1,4 +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" + "foo": "_multiple_sources_source2__foo _multiple_sources_b__b2" } diff --git a/test/test-cases/multiple-sources/source2.css b/test/test-cases/multiple-sources/source2.css index 151a720..1267398 100644 --- a/test/test-cases/multiple-sources/source2.css +++ b/test/test-cases/multiple-sources/source2.css @@ -1,4 +1,4 @@ .foo { - composes: b from "./b.css"; + composes: b2 from "./b.css"; color: #f00; } From 849c206f2ed711c066dafb05c8526d390058e82b Mon Sep 17 00:00:00 2001 From: Ryan Tsao Date: Thu, 27 Aug 2015 13:14:04 -0700 Subject: [PATCH 2/2] Use selector-has module --- package.json | 3 ++- src/file-system-loader.js | 10 +++------- src/parser.js | 4 ++-- 3 files changed, 7 insertions(+), 10 deletions(-) diff --git a/package.json b/package.json index f4a77c2..d0b6453 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,8 @@ "postcss": "^4.1.11", "postcss-modules-extract-imports": "^0.0.5", "postcss-modules-local-by-default": "^0.0.9", - "postcss-modules-scope": "^0.0.8" + "postcss-modules-scope": "^0.0.8", + "selector-has": "^1.0.0" }, "devDependencies": { "babel": "^5.5.4", diff --git a/src/file-system-loader.js b/src/file-system-loader.js index 2096af0..65e09fc 100644 --- a/src/file-system-loader.js +++ b/src/file-system-loader.js @@ -1,8 +1,9 @@ import Core from './index.js' import fs from 'fs' import path from 'path' -import partialRight from 'lodash.partialright' import {parse} from 'postcss'; +import selectorHas from 'selector-has' +import partialRight from 'lodash.partialright' // Sorts dependencies in the following way: // AAA comes before AA and A @@ -66,7 +67,7 @@ export default class FileSystemLoader { if (exportedNames[name]) { name = exportedNames[name] } - return decl.selector === ':export' || containsClass(decl.selector, name) + return decl.selector === ':export' || selectorHas(decl.selector, name) })) { decl.removeSelf() } @@ -90,8 +91,3 @@ export default class FileSystemLoader { .join( "" ) } } - -function containsClass(selector, className) { - let test = new RegExp('\\.' + className + '($|[\\s\.\[,>+~#:])'); - return selector.match(test); -} diff --git a/src/parser.js b/src/parser.js index cc51206..297473c 100644 --- a/src/parser.js +++ b/src/parser.js @@ -53,11 +53,11 @@ export default class Parser { exportNode.removeSelf() } - fetchImport( importNode, relativeTo, depNr, stuff) { + fetchImport( importNode, relativeTo, depNr, importedClasses ) { let file = importNode.selector.match( importRegexp )[1], depTrace = this.trace + String.fromCharCode(depNr) - return this.pathFetcher( file, relativeTo, depTrace, stuff ).then( exports => { + return this.pathFetcher( file, relativeTo, depTrace, importedClasses ).then( exports => { importNode.each( decl => { if ( decl.type == 'decl' ) { this.translations[decl.prop] = exports[decl.value]