diff --git a/index.js b/index.js index d6a67fa..595807e 100644 --- a/index.js +++ b/index.js @@ -1,7 +1,6 @@ const ConcatStream = require('concat-stream') -const isRequire = require('is-require')() +const staticModule = require('static-module') const through = require('through2') -const falafel = require('falafel') const assert = require('assert') const fs = require('fs') @@ -22,16 +21,28 @@ function cssExtract (bundle, opts) { addHooks() function addHooks () { - // run before the "debug" step in browserify pipeline - bundle.pipeline.get('debug').unshift(through.obj(write, flush)) const writeStream = (typeof outFile === 'function') ? outFile() - : ConcatStream(writeOutFile) + : ConcatStream(fs.writeFileSync.bind(fs, outFile)) + + // run before the "label" step in browserify pipeline + // this makes sure insert-css requires are found before plugins like bundle-collapser run + bundle.pipeline.get('label').unshift(through.obj(write, flush)) function write (chunk, enc, cb) { - const css = extract(chunk) - writeStream.write(css) - cb(null, chunk) + // A small performance boost: don't do ast parsing unless we know it's needed + if (String(chunk.source).indexOf('insert-css') === -1) { + return cb(null, chunk) + } + + var sm = createStaticModule(writeStream) + sm.write(chunk.source) + sm.pipe(ConcatStream(function (source) { + // chunk.source is expected to be a string + chunk.source = String(source) + cb(null, chunk) + })) + sm.end() } // close stream and signal end @@ -40,29 +51,10 @@ function cssExtract (bundle, opts) { cb() } } - - function writeOutFile (buffer) { - fs.writeFileSync(outFile, buffer) - } } -// extract css from chunks -// obj -> str -function extract (chunk) { - // Do a performant check before building the ast - if (String(chunk.source).indexOf('insert-css') === -1) return '' - - const css = [] - const ast = falafel(chunk.source, { ecmaVersion: 6 }, walk) - chunk.source = ast.toString() - return css.join('\n') - - function walk (node) { - if (!isRequire(node)) return - if (!node.arguments) return - if (!node.arguments[0]) return - if (node.arguments[0].value !== 'insert-css') return - css.push(node.parent.arguments[0].value) - node.parent.update('0') - } +function createStaticModule (writeStream) { + return staticModule({ + 'insert-css': writeStream.write.bind(writeStream) + }) } diff --git a/package.json b/package.json index a518c3e..edc6285 100644 --- a/package.json +++ b/package.json @@ -21,8 +21,7 @@ "license": "MIT", "dependencies": { "concat-stream": "^1.5.1", - "falafel": "^1.2.0", - "is-require": "0.0.1", + "static-module": "^1.3.0", "through2": "^2.0.1" }, "devDependencies": { diff --git a/test/expected-static.css b/test/expected-static.css new file mode 100644 index 0000000..990a9a5 --- /dev/null +++ b/test/expected-static.css @@ -0,0 +1 @@ +.foo {background: green} \ No newline at end of file diff --git a/test/index.js b/test/index.js index c614c9a..ff24730 100644 --- a/test/index.js +++ b/test/index.js @@ -14,7 +14,7 @@ test('css-extract', function (t) { t.throws(cssExtract.bind(null, {}), 123, /object/) }) - t.test('should extract css', function (t) { + t.test('should extract sheetify css to given stream', function (t) { t.plan(2) browserify(path.join(__dirname, 'source.js')) .transform('sheetify/transform') @@ -31,7 +31,7 @@ test('css-extract', function (t) { } }) - t.test('should write file', function (t) { + t.test('should extract sheetify css to file', function (t) { t.plan(3) tmpDir({unsafeCleanup: true}, onDir) @@ -54,4 +54,42 @@ test('css-extract', function (t) { }) } }) + + t.test('should extract static insert-css statements', function (t) { + t.plan(2) + browserify(path.join(__dirname, 'source-static.js')) + .plugin(cssExtract, { out: createWs }) + .bundle() + + function createWs () { + return bl(function (err, data) { + t.ifError(err, 'no error') + const exPath = path.join(__dirname, './expected-static.css') + const expected = fs.readFileSync(exPath, 'utf8').trim() + t.equal(String(data), expected, 'extracted all the CSS') + }) + } + }) + + t.test('should not extract dynamic insert-css statements', function (t) { + t.plan(4) + const sourcePath = path.join(__dirname, 'source-dynamic.js') + + browserify(sourcePath) + .plugin(cssExtract, { out: readCss }) + .bundle(readJs) + + function readCss () { + return bl(function (err, data) { + t.ifError(err, 'no error') + t.equal(String(data), '', 'no css extracted') + }) + } + + function readJs (err, data) { + t.ifError(err, 'no error') + const source = fs.readFileSync(sourcePath, 'utf8') + t.ok(String(data).indexOf(String(source)) !== -1, 'source is still in built bundle') + } + }) }) diff --git a/test/source-dynamic.js b/test/source-dynamic.js new file mode 100644 index 0000000..844b9b3 --- /dev/null +++ b/test/source-dynamic.js @@ -0,0 +1,5 @@ +insert('.foo {}') + +function insert (foo) { + require('insert-css')(foo) +} diff --git a/test/source-static.js b/test/source-static.js new file mode 100644 index 0000000..ca66f1d --- /dev/null +++ b/test/source-static.js @@ -0,0 +1,3 @@ +var insertCss = require('insert-css') + +insertCss('.foo {background: green}')