From b4a5288cbfb3c146a968f534da23ee2ab2ee0403 Mon Sep 17 00:00:00 2001 From: Sherman Chen Date: Tue, 29 Apr 2014 18:00:33 +0800 Subject: [PATCH] Support import options --- import.js | 151 +++++++++++++++++++++++++++++ index.js | 41 ++++---- package.json | 4 +- test/features/imprt.css | 3 +- test/features/imprt.out.css | 6 ++ test/features/imprt_core.out.css | 3 + test/features/imprt_large.css | 2 +- test/features/imprt_large.out.css | 9 ++ test/features/imprt_xlarge.css | 5 + test/features/imprt_xlarge.out.css | 9 ++ test/index.js | 43 +++++++- 11 files changed, 250 insertions(+), 26 deletions(-) create mode 100644 import.js create mode 100644 test/features/imprt_core.out.css create mode 100644 test/features/imprt_large.out.css create mode 100644 test/features/imprt_xlarge.css create mode 100644 test/features/imprt_xlarge.out.css diff --git a/import.js b/import.js new file mode 100644 index 0000000..bc061dd --- /dev/null +++ b/import.js @@ -0,0 +1,151 @@ +// +// # Import +// +// Subsitute `@import { file: filename; }` with the contents of `filename`. +// + +/*jslint node: true */ +"use strict"; + +var fs = require('fs'); +var path = require('path'); +var whitespace = require('css-whitespace'); +var rework = require('rework'); + +// +// ## Register plugin +// +// * **opts**, options object. May contain the following: +// +// * path: base path for resolving imports. +// * whitespace: boolean, set to true if imported files use significant +// whitespace instead of curlies. +// +module.exports = function (opts) { + return function (style) { + return new Import(opts).visit(style); + }; +}; + +// +// ## Importer +// +function Import(opts) { + if(!opts.base) { + throw new Error("Must specify a file path"); + } + + opts = opts || {}; + this.opts = opts; + this.base = opts.base || process.cwd(); + this.path = opts.path; + this.visit = this.visit.bind(this); + this.importFile = this.importFile.bind(this); + this.map = opts.map || []; + this.target = opts.target; + + // is relative? + if(path.resolve(this.path) !== this.path) { + this.path = path.resolve(this.base, this.path); + } +} + +Import.prototype.visit = function (node, index, arr) { + if (!node) return; + var type = node.type || 'stylesheet'; + if (!this[type]) return; + this[type](node, index, arr); +}; + +Import.prototype.stylesheet = function (stylesheet) { + for (var i = stylesheet.rules.length; i >= 0; i-=1) { + this.visit(stylesheet.rules[i], i, stylesheet.rules); + } +}; + +Import.prototype.import = function (node, index, arr) { + var regex = /url\(['"]?(.*?)['"]?\)/; + var filename = node.import.match(regex); + if (filename && filename[1] && !isUrl(filename[1])) { + if(this.target) { + // /(\w+)_(common|target)\.\w+/ + var targetRegex = new RegExp('(\\w+)_(' + this.target.join('|') + ')\\.\\w+'); + var matchTarget = filename[1].match(targetRegex); + if(!matchTarget) { + arr.splice(index, 1); + return; + } + } + + var ast = this.parseFile(filename[1]); + var i = 0; + arr.splice(index, 1); + + ast.rules.forEach(function (rule) { + arr.splice(0 + i + index, 0, rule); + i++; + }); + } +}; + +Import.prototype.rule = function (rule, index, base) { + if (rule.selectors[0] == '@import') { + var ast = rule.declarations.map(this.importFile); + var rules = []; + ast.filter(function (item) { + return !!item; + }).forEach(function (item) { + rules = rules.concat(item.rules); + }); + + var removed = base.splice(index, 1); + // Insert rules at same index + var i = 0; // To make imports in order. + rules.forEach(function (rule) { + var removed = base.splice(index + i, 0, rule); + i++; + }); + } +}; + +Import.prototype.importFile = function (declaration) { + if (declaration.property !== 'file') return; + return this.parseFile(declaration.value); +}; + +Import.prototype.parseFile = function (file) { + var load; + //is absolute? + if(path.resolve(file) === file) { + load = path.join(this.base, file); + } else { + load = path.resolve(path.dirname(this.path), file); + } + + // Skip circular imports. + if (this.map.indexOf(load) !== -1) { + return false; + } + var data = fs.readFileSync(load, this.opts.encoding || 'utf8'); + + if (this.opts.whitespace) { + data = whitespace(data); + } + + this.map.push(load); + // Create AST and look for imports in imported code. + var opts = { + whitespace: this.opts.whitespace, + map: this.map, + base: this.base, + path: load + }; + + var ast = rework(data).use(module.exports(opts)); + return ast.obj.stylesheet; +}; + + +function isUrl(url) { + return (/^([\w]+:)?\/\/./).test(url); +} diff --git a/index.js b/index.js index b1c4fc3..0e36890 100644 --- a/index.js +++ b/index.js @@ -4,7 +4,7 @@ var rework = require('rework'); var calc = require('rework-calc'); var hex = require('rework-hex-alpha'); var vars = require('rework-vars')(); -var imprt = require('rework-importer'); +var imprt = require('./import.js'); var path = require('path'); /** @@ -22,30 +22,29 @@ module.exports = provecss; */ function provecss (string, options) { - var browsers; - var import_path, import_base; - if(options && options.browsers) { - browsers = options.browsers; - } - if(options && options.path) { - import_path = path.basename(options.path); - if(!options.base) { - import_base = path.dirname(options.path); - } else { - import_base = options.base; - } + options = options || {}; + this.browsers = options.browsers; + if(options.path) { + this.import_path = path.basename(options.path); + this.import_base = options.base || path.dirname(options.path); } + this.layout_target = options.target; + //not run autoprefixer by default - if(browsers) { - string = prefixes(browsers).process(string).css; + if(this.browsers) { + string = prefixes(this.browsers).process(string).css; } - if(import_path) { - string = rework(string) - .use(imprt({ - path: import_path, - base: import_base - })).toString(); + + //handle import inlining if any + if(this.import_path) { + var opts = { + path: this.import_path, + base: this.import_base, + target: this.layout_target + }; + string = rework(string).use(imprt(opts)).toString(); } + return rework(string, options) .use(vars) .use(hex) diff --git a/package.json b/package.json index d682dea..4b62f91 100644 --- a/package.json +++ b/package.json @@ -15,13 +15,13 @@ }, "dependencies": { "autoprefixer": "~1.1.20140429", + "css-whitespace": "~1.1.0", "rework": "~0.20.2", "rework-calc": "~0.2.1", "rework-color-function": "~1.0.0", "rework-font-variant": "~1.0.0", "rework-hex-alpha": "~1.0.0", - "rework-vars": "~3.0.0", - "rework-importer": "~0.3.1" + "rework-vars": "~3.0.0" }, "devDependencies": { "mocha": "~1.18.2" diff --git a/test/features/imprt.css b/test/features/imprt.css index ac92a86..640ceb9 100644 --- a/test/features/imprt.css +++ b/test/features/imprt.css @@ -1,2 +1,3 @@ @import url("imprt_core.css"); -@import url("imprt_large.css"); \ No newline at end of file +@import url("imprt_large.css"); +@import url("imprt_xlarge.css"); diff --git a/test/features/imprt.out.css b/test/features/imprt.out.css index 6d768cc..ea887a9 100644 --- a/test/features/imprt.out.css +++ b/test/features/imprt.out.css @@ -6,4 +6,10 @@ headers { headers { background-color: black; } +} + +@media (min-width: 1024px) { + headers { + background-color: red; + } } \ No newline at end of file diff --git a/test/features/imprt_core.out.css b/test/features/imprt_core.out.css new file mode 100644 index 0000000..d9f90b2 --- /dev/null +++ b/test/features/imprt_core.out.css @@ -0,0 +1,3 @@ +headers { + background-color: orange; +} diff --git a/test/features/imprt_large.css b/test/features/imprt_large.css index d4d67a6..48b3c92 100644 --- a/test/features/imprt_large.css +++ b/test/features/imprt_large.css @@ -2,4 +2,4 @@ headers { background-color: black; } -} \ No newline at end of file +} diff --git a/test/features/imprt_large.out.css b/test/features/imprt_large.out.css new file mode 100644 index 0000000..349b945 --- /dev/null +++ b/test/features/imprt_large.out.css @@ -0,0 +1,9 @@ +headers { + background-color: orange; +} + +@media (min-width: 768px) { + headers { + background-color: black; + } +} diff --git a/test/features/imprt_xlarge.css b/test/features/imprt_xlarge.css new file mode 100644 index 0000000..18381cb --- /dev/null +++ b/test/features/imprt_xlarge.css @@ -0,0 +1,5 @@ +@media (min-width: 1024px) { + headers { + background-color: red; + } +} diff --git a/test/features/imprt_xlarge.out.css b/test/features/imprt_xlarge.out.css new file mode 100644 index 0000000..f35b500 --- /dev/null +++ b/test/features/imprt_xlarge.out.css @@ -0,0 +1,9 @@ +headers { + background-color: orange; +} + +@media (min-width: 1024px) { + headers { + background-color: red; + } +} diff --git a/test/index.js b/test/index.js index b5b8b1b..d8af97b 100644 --- a/test/index.js +++ b/test/index.js @@ -45,7 +45,7 @@ describe('@import inlining feature', function () { {path:"test/features/imprt.css"}).trim(), output.trim()); }); - + it('should add base option support', function () { var input = read('features/imprt'); var output = read('features/imprt.out'); @@ -56,6 +56,47 @@ describe('@import inlining feature', function () { }).trim(), output.trim()); }); + + it('should generate css based on target option (core)', function () { + var input = read('features/imprt'); + var output = read('features/imprt_core.out'); + var option = { + path: 'test/features/imprt.css', + target: ['core'] + }; + assert.equal(provecss(input, option).trim(), output.trim()); + }); + + it('should generate css based on target option (core+large)', function () { + var input = read('features/imprt'); + var output = read('features/imprt_large.out'); + var option = { + path: 'test/features/imprt.css', + target: ['core', 'large'] + }; + assert.equal(provecss(input, option).trim(), output.trim()); + }); + + it('should generate css based on target option (core+xlarge)', function () { + var input = read('features/imprt'); + var output = read('features/imprt_xlarge.out'); + var option = { + path: 'test/features/imprt.css', + target: ['core', 'xlarge'] + }; + assert.equal(provecss(input, option).trim(), output.trim()); + }); + + it('should generate css based on target option (core+large+xlarge)', function () { + var input = read('features/imprt'); + var output = read('features/imprt.out'); + var option = { + path: 'test/features/imprt.css', + target: ['core', 'large' , 'xlarge'] + }; + assert.equal(provecss(input, option).trim(), output.trim()); + }); + }); /**