Skip to content

Commit 1a77505

Browse files
committed
Merge pull request #51 from joeybaker/add-streaming
fix #17: add a stream output option
2 parents 870cc33 + 9b8827a commit 1a77505

File tree

6 files changed

+115
-16
lines changed

6 files changed

+115
-16
lines changed

README.md

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,14 +45,33 @@ b.plugin(require('css-modulesify'), {
4545
b.bundle();
4646
```
4747

48+
```js
49+
// or, get the output as a stream
50+
var b = require('browserify')();
51+
var fs = require('fs');
52+
53+
b.add('./main.js');
54+
b.plugin(require('css-modulesify'), {
55+
rootDir: __dirname
56+
});
57+
58+
var bundle = b.bundle()
59+
bundle.on('css stream', function (css) {
60+
css.pipe(fs.createWriteStream('mycss.css'));
61+
});
62+
```
63+
4864
### Options:
4965

5066
- `rootDir`: absolute path to your project's root directory. This is optional but providing it will result in better generated classnames.
51-
- `output`: path to write the generated css.
67+
- `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.
5268
- `jsonOutput`: optional path to write a json manifest of classnames.
5369
- `use`: optional array of postcss plugins (by default we use the css-modules core plugins).
5470
- `generateScopedName`: (API only) a function to override the default behaviour of creating locally scoped classnames.
5571

72+
### Events
73+
- `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.
74+
5675
## Using CSS Modules on the backend
5776

5877
If you want to use CSS Modules in server-generated templates there are a couple of options:

index.js

Lines changed: 31 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ var Core = require('css-modules-loader-core');
55
var FileSystemLoader = require('css-modules-loader-core/lib/file-system-loader');
66
var assign = require('object-assign');
77
var stringHash = require('string-hash');
8+
var ReadableStream = require('stream').Readable;
89

910

1011
/*
@@ -96,10 +97,6 @@ module.exports = function (browserify, options) {
9697
if (!rootDir) { rootDir = process.cwd(); }
9798

9899
var cssOutFilename = options.output || options.o;
99-
if (!cssOutFilename) {
100-
throw new Error('css-modulesify needs the --output / -o option (path to output css file)');
101-
}
102-
103100
var jsonOutFilename = options.json || options.jsonOutput;
104101

105102
// PostCSS plugins passed to FileSystemLoader
@@ -143,6 +140,10 @@ module.exports = function (browserify, options) {
143140
return plugin;
144141
});
145142

143+
// the compiled CSS stream needs to be avalible to the transform,
144+
// but re-created on each bundle call.
145+
var compiledCssStream;
146+
146147
function transform (filename) {
147148
// only handle .css files
148149
if (!cssExt.test(filename)) {
@@ -164,6 +165,8 @@ module.exports = function (browserify, options) {
164165
// store this file's source to be written out to disk later
165166
sourceByFile[filename] = loader.finalSource;
166167

168+
compiledCssStream.push(loader.finalSource);
169+
167170
self.queue(output);
168171
self.queue(null);
169172
}, function (err) {
@@ -177,17 +180,32 @@ module.exports = function (browserify, options) {
177180
});
178181

179182
browserify.on('bundle', function (bundle) {
183+
// on each bundle, create a new stream b/c the old one might have ended
184+
compiledCssStream = new ReadableStream();
185+
compiledCssStream._read = function () {};
186+
187+
bundle.emit('css stream', compiledCssStream);
188+
180189
bundle.on('end', function () {
181190
// Combine the collected sources into a single CSS file
182-
var css = Object.keys(sourceByFile).map(function (file) {
183-
return sourceByFile[file];
184-
}).join('\n');
185-
186-
fs.writeFile(cssOutFilename, css, function (err) {
187-
if (err) {
188-
browserify.emit('error', err);
189-
}
190-
});
191+
var files = Object.keys(sourceByFile);
192+
var css;
193+
194+
// end the output stream
195+
compiledCssStream.push(null);
196+
197+
// write the css file
198+
if (cssOutFilename) {
199+
css = files.map(function (file) {
200+
return sourceByFile[file];
201+
}).join('\n');
202+
203+
fs.writeFile(cssOutFilename, css, function (err) {
204+
if (err) {
205+
browserify.emit('error', err);
206+
}
207+
});
208+
}
191209

192210
// write the classname manifest
193211
if (jsonOutFilename) {
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
._simple_styles__foo {
2+
color: #F00;
3+
}

tests/cases/multiple-js-files/main.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
module.exports = require('../simple/main.js');

tests/index.js

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,7 @@ function runTestCase (dir) {
3434

3535
b.bundle(function (err) {
3636
if (err) {
37-
console.error(err);
38-
return t.fail('Unexpected error');
37+
t.error(err, 'should not error');
3938
}
4039

4140
t.end();

tests/stream-output.js

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
var tape = require('tape');
2+
3+
var browserify = require('browserify');
4+
var proxyquire = require('proxyquire');
5+
var fs = require('fs');
6+
var path = require('path');
7+
8+
var casesDir = path.join(__dirname, 'cases');
9+
var simpleCaseDir = path.join(casesDir, 'simple');
10+
var cssFilesTotal = 1;
11+
var cssOutFilename = 'out.css';
12+
13+
tape('stream output', function (t) {
14+
var fakeFs = {
15+
writeFile: function (filename, content, cb) {
16+
var expected = fs.readFileSync(path.join(simpleCaseDir, 'expected.css'), 'utf8');
17+
18+
t.equal(filename, cssOutFilename, 'correct output filename');
19+
t.equal(content, expected, 'output matches expected');
20+
cb();
21+
}
22+
};
23+
24+
var cssModulesify = proxyquire('../', {
25+
fs: fakeFs
26+
});
27+
28+
t.plan(cssFilesTotal * 2 + 1);
29+
30+
var cssFilesCount = 0;
31+
browserify(path.join(simpleCaseDir, 'main.js'))
32+
.plugin(cssModulesify, {
33+
rootDir: path.join(simpleCaseDir)
34+
})
35+
.on('error', t.error)
36+
.bundle(function noop () {})
37+
.on('css stream', function (stream) {
38+
stream
39+
.on('data', function onData (css) {
40+
var cssString = css.toString();
41+
// just get the first class name, use that as an id
42+
var cssId = cssString.split('\n')[0].split(' ')[0];
43+
44+
t.ok(
45+
++cssFilesCount <= cssFilesTotal
46+
, 'emits data for ' + cssId
47+
);
48+
49+
t.ok(
50+
cssString.indexOf('._styles') === 0
51+
, 'emits compiled css for ' + cssId
52+
);
53+
})
54+
.on('end', function onEnd () {
55+
t.pass('ends the stream');
56+
})
57+
.on('error', t.error);
58+
});
59+
});

0 commit comments

Comments
 (0)