From 7accbc376592713feec92faa700d51cc3c5db1f2 Mon Sep 17 00:00:00 2001 From: Joey Baker Date: Fri, 9 Oct 2015 15:48:56 -0700 Subject: [PATCH 1/3] internal: add test case for multiple JS files * ensures that the transform is called on each file * ensures the default class names contain folder names --- tests/cases/multiple-js-files/expected.css | 3 +++ tests/cases/multiple-js-files/main.js | 1 + 2 files changed, 4 insertions(+) create mode 100644 tests/cases/multiple-js-files/expected.css create mode 100644 tests/cases/multiple-js-files/main.js diff --git a/tests/cases/multiple-js-files/expected.css b/tests/cases/multiple-js-files/expected.css new file mode 100644 index 0000000..00c95c8 --- /dev/null +++ b/tests/cases/multiple-js-files/expected.css @@ -0,0 +1,3 @@ +._simple_styles__foo { + color: #F00; +} diff --git a/tests/cases/multiple-js-files/main.js b/tests/cases/multiple-js-files/main.js new file mode 100644 index 0000000..a095317 --- /dev/null +++ b/tests/cases/multiple-js-files/main.js @@ -0,0 +1 @@ +module.exports = require('../simple/main.js'); From 71e8bbf8c7c5fa5e44bb013fdd14c084a0426edd Mon Sep 17 00:00:00 2001 From: Joey Baker Date: Fri, 9 Oct 2015 15:49:22 -0700 Subject: [PATCH 2/3] internal: fix default tests to use t.error b/c tape internals are nice :) --- tests/index.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/index.js b/tests/index.js index 556b76d..f651e55 100644 --- a/tests/index.js +++ b/tests/index.js @@ -34,8 +34,7 @@ function runTestCase (dir) { b.bundle(function (err) { if (err) { - console.error(err); - return t.fail('Unexpected error'); + t.error(err, 'should not error'); } t.end(); From 9b8827a8ca6965edf34cff1844b02bbada0a1781 Mon Sep 17 00:00:00 2001 From: Joey Baker Date: Fri, 9 Oct 2015 15:54:22 -0700 Subject: [PATCH 3/3] fix #17: add a stream output option MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The `bundle` now emits a `’css stream’` event. Users can listen to this and get a stream of the compiled CSS. I’m not a super big fan of this API, but I couldn’t think of a better way to give access to the stream. --- README.md | 21 ++++++++++++++- index.js | 44 +++++++++++++++++++++---------- tests/stream-output.js | 59 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 110 insertions(+), 14 deletions(-) create mode 100644 tests/stream-output.js diff --git a/README.md b/README.md index 8be238c..90f0c22 100644 --- a/README.md +++ b/README.md @@ -45,14 +45,33 @@ b.plugin(require('css-modulesify'), { b.bundle(); ``` +```js +// or, get the output as a stream +var b = require('browserify')(); +var fs = require('fs'); + +b.add('./main.js'); +b.plugin(require('css-modulesify'), { + rootDir: __dirname +}); + +var bundle = b.bundle() +bundle.on('css stream', function (css) { + css.pipe(fs.createWriteStream('mycss.css')); +}); +``` + ### Options: - `rootDir`: absolute path to your project's root directory. This is optional but providing it will result in better generated classnames. -- `output`: path to write the generated css. +- `output`: path to write the generated css. If not provided, you'll need to listen to the `'css stream'` event on the bundle to get the output. - `jsonOutput`: optional path to write a json manifest of classnames. - `use`: optional array of postcss plugins (by default we use the css-modules core plugins). - `generateScopedName`: (API only) a function to override the default behaviour of creating locally scoped classnames. +### Events +- `b.bundle().on('css stream', callback)` The callback is called with a readable stream containing the compiled CSS. You can write this to a file. + ## Using CSS Modules on the backend If you want to use CSS Modules in server-generated templates there are a couple of options: diff --git a/index.js b/index.js index 6b06cfa..4bbbbaa 100644 --- a/index.js +++ b/index.js @@ -5,6 +5,7 @@ var Core = require('css-modules-loader-core'); var FileSystemLoader = require('css-modules-loader-core/lib/file-system-loader'); var assign = require('object-assign'); var stringHash = require('string-hash'); +var ReadableStream = require('stream').Readable; /* @@ -96,10 +97,6 @@ module.exports = function (browserify, options) { if (!rootDir) { rootDir = process.cwd(); } var cssOutFilename = options.output || options.o; - if (!cssOutFilename) { - throw new Error('css-modulesify needs the --output / -o option (path to output css file)'); - } - var jsonOutFilename = options.json || options.jsonOutput; // PostCSS plugins passed to FileSystemLoader @@ -143,6 +140,10 @@ module.exports = function (browserify, options) { return plugin; }); + // the compiled CSS stream needs to be avalible to the transform, + // but re-created on each bundle call. + var compiledCssStream; + function transform (filename) { // only handle .css files if (!cssExt.test(filename)) { @@ -164,6 +165,8 @@ module.exports = function (browserify, options) { // store this file's source to be written out to disk later sourceByFile[filename] = loader.finalSource; + compiledCssStream.push(loader.finalSource); + self.queue(output); self.queue(null); }, function (err) { @@ -177,17 +180,32 @@ module.exports = function (browserify, options) { }); browserify.on('bundle', function (bundle) { + // on each bundle, create a new stream b/c the old one might have ended + compiledCssStream = new ReadableStream(); + compiledCssStream._read = function () {}; + + bundle.emit('css stream', compiledCssStream); + bundle.on('end', function () { // Combine the collected sources into a single CSS file - var css = Object.keys(sourceByFile).map(function (file) { - return sourceByFile[file]; - }).join('\n'); - - fs.writeFile(cssOutFilename, css, function (err) { - if (err) { - browserify.emit('error', err); - } - }); + var files = Object.keys(sourceByFile); + var css; + + // end the output stream + compiledCssStream.push(null); + + // write the css file + if (cssOutFilename) { + css = files.map(function (file) { + return sourceByFile[file]; + }).join('\n'); + + fs.writeFile(cssOutFilename, css, function (err) { + if (err) { + browserify.emit('error', err); + } + }); + } // write the classname manifest if (jsonOutFilename) { diff --git a/tests/stream-output.js b/tests/stream-output.js new file mode 100644 index 0000000..4268f7b --- /dev/null +++ b/tests/stream-output.js @@ -0,0 +1,59 @@ +var tape = require('tape'); + +var browserify = require('browserify'); +var proxyquire = require('proxyquire'); +var fs = require('fs'); +var path = require('path'); + +var casesDir = path.join(__dirname, 'cases'); +var simpleCaseDir = path.join(casesDir, 'simple'); +var cssFilesTotal = 1; +var cssOutFilename = 'out.css'; + +tape('stream output', function (t) { + var fakeFs = { + writeFile: function (filename, content, cb) { + var expected = fs.readFileSync(path.join(simpleCaseDir, 'expected.css'), 'utf8'); + + t.equal(filename, cssOutFilename, 'correct output filename'); + t.equal(content, expected, 'output matches expected'); + cb(); + } + }; + + var cssModulesify = proxyquire('../', { + fs: fakeFs + }); + + t.plan(cssFilesTotal * 2 + 1); + + var cssFilesCount = 0; + browserify(path.join(simpleCaseDir, 'main.js')) + .plugin(cssModulesify, { + rootDir: path.join(simpleCaseDir) + }) + .on('error', t.error) + .bundle(function noop () {}) + .on('css stream', function (stream) { + stream + .on('data', function onData (css) { + var cssString = css.toString(); + // just get the first class name, use that as an id + var cssId = cssString.split('\n')[0].split(' ')[0]; + + t.ok( + ++cssFilesCount <= cssFilesTotal + , 'emits data for ' + cssId + ); + + t.ok( + cssString.indexOf('._styles') === 0 + , 'emits compiled css for ' + cssId + ); + }) + .on('end', function onEnd () { + t.pass('ends the stream'); + }) + .on('error', t.error); + }); +});