Skip to content

Commit 95bee89

Browse files
committed
Merge pull request #64 from css-modules/fix-watchify
Updates to make watchify work
2 parents 01ccedc + 7a9f650 commit 95bee89

File tree

6 files changed

+228
-45
lines changed

6 files changed

+228
-45
lines changed

cmify.js

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
var stream = require("stream");
2+
var path = require("path");
3+
var util = require("util");
4+
5+
util.inherits(Cmify, stream.Transform);
6+
function Cmify(filename, opts) {
7+
if (!(this instanceof Cmify)) {
8+
return new Cmify(filename, opts);
9+
}
10+
11+
stream.Transform.call(this);
12+
13+
this.cssExt = /\.css$/;
14+
this._data = "";
15+
this._filename = filename;
16+
}
17+
18+
Cmify.prototype.isCssFile = function (filename) {
19+
return this.cssExt.test(filename)
20+
}
21+
22+
Cmify.prototype._transform = function (buf, enc, callback) {
23+
// only handle .css files
24+
if (!this.isCssFile(this._filename)) {
25+
this.push(buf)
26+
return callback()
27+
}
28+
29+
this._data += buf
30+
callback()
31+
};
32+
33+
module.exports = Cmify

file-system-loader.js

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
'use strict';
2+
3+
var DepGraph = require('dependency-graph').DepGraph;
4+
5+
Object.defineProperty(exports, '__esModule', {
6+
value: true
7+
});
8+
9+
var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })();
10+
11+
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
12+
13+
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } }
14+
15+
var _indexJs = require('css-modules-loader-core/lib/index.js');
16+
17+
var _indexJs2 = _interopRequireDefault(_indexJs);
18+
19+
var _fs = require('fs');
20+
21+
var _fs2 = _interopRequireDefault(_fs);
22+
23+
var _path = require('path');
24+
25+
var _path2 = _interopRequireDefault(_path);
26+
27+
// Sorts dependencies in the following way:
28+
// AAA comes before AA and A
29+
// AB comes after AA and before A
30+
// All Bs come after all As
31+
// This ensures that the files are always returned in the following order:
32+
// - In the order they were required, except
33+
// - After all their dependencies
34+
var traceKeySorter = function traceKeySorter(a, b) {
35+
if (a.length < b.length) {
36+
return a < b.substring(0, a.length) ? -1 : 1;
37+
} else if (a.length > b.length) {
38+
return a.substring(0, b.length) <= b ? -1 : 1;
39+
} else {
40+
return a < b ? -1 : 1;
41+
}
42+
};
43+
44+
var FileSystemLoader = (function () {
45+
function FileSystemLoader(root, plugins) {
46+
_classCallCheck(this, FileSystemLoader);
47+
48+
this.root = root;
49+
this.sources = {};
50+
this.traces = {};
51+
this.importNr = 0;
52+
this.core = new _indexJs2['default'](plugins);
53+
this.tokensByFile = {};
54+
this.deps = new DepGraph();
55+
}
56+
57+
_createClass(FileSystemLoader, [{
58+
key: 'fetch',
59+
value: function fetch(_newPath, relativeTo, _trace) {
60+
var _this = this;
61+
62+
var newPath = _newPath.replace(/^["']|["']$/g, ''),
63+
trace = _trace || String.fromCharCode(this.importNr++);
64+
return new Promise(function (resolve, reject) {
65+
var relativeDir = _path2['default'].dirname(relativeTo),
66+
rootRelativePath = _path2['default'].resolve(relativeDir, newPath),
67+
fileRelativePath = _path2['default'].resolve(_path2['default'].join(_this.root, relativeDir), newPath);
68+
69+
// if the path is not relative or absolute, try to resolve it in node_modules
70+
if (newPath[0] !== '.' && newPath[0] !== '/') {
71+
try {
72+
fileRelativePath = require.resolve(newPath);
73+
} catch (e) {}
74+
}
75+
76+
// first time? add a node
77+
if (_trace === undefined) {
78+
if (!_this.deps.hasNode(fileRelativePath)) {
79+
_this.deps.addNode(fileRelativePath);
80+
}
81+
}
82+
// otherwise add a dependency
83+
else {
84+
var parentFilePath = _path2['default'].join(_this.root, relativeTo);
85+
if (!_this.deps.hasNode(parentFilePath)) {
86+
console.error('NO NODE', parentFilePath, fileRelativePath)
87+
}
88+
if (!_this.deps.hasNode(fileRelativePath)) {
89+
_this.deps.addNode(fileRelativePath);
90+
}
91+
_this.deps.addDependency(parentFilePath, fileRelativePath);
92+
}
93+
94+
var tokens = _this.tokensByFile[fileRelativePath];
95+
if (tokens) {
96+
return resolve(tokens);
97+
}
98+
99+
_fs2['default'].readFile(fileRelativePath, 'utf-8', function (err, source) {
100+
if (err) reject(err);
101+
_this.core.load(source, rootRelativePath, trace, _this.fetch.bind(_this)).then(function (_ref) {
102+
var injectableSource = _ref.injectableSource;
103+
var exportTokens = _ref.exportTokens;
104+
105+
_this.sources[fileRelativePath] = injectableSource;
106+
_this.traces[trace] = fileRelativePath;
107+
_this.tokensByFile[fileRelativePath] = exportTokens;
108+
resolve(exportTokens);
109+
}, reject);
110+
});
111+
});
112+
}
113+
}, {
114+
key: 'finalSource',
115+
get: function () {
116+
var traces = this.traces;
117+
var sources = this.sources;
118+
var written = {};
119+
120+
return Object.keys(traces).sort(traceKeySorter).map(function (key) {
121+
var filename = traces[key];
122+
if (written[filename] === true) {
123+
return null;
124+
}
125+
written[filename] = true;
126+
127+
return sources[filename];
128+
}).join('');
129+
}
130+
}]);
131+
132+
return FileSystemLoader;
133+
})();
134+
135+
exports['default'] = FileSystemLoader;
136+
module.exports = exports['default'];

index.js

Lines changed: 52 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,9 @@ if (!global.Promise) { global.Promise = require('promise-polyfill') }
33

44
var fs = require('fs');
55
var path = require('path');
6-
var through = require('through');
6+
var Cmify = require('./cmify');
77
var Core = require('css-modules-loader-core');
8-
var FileSystemLoader = require('css-modules-loader-core/lib/file-system-loader');
8+
var FileSystemLoader = require('./file-system-loader');
99
var assign = require('object-assign');
1010
var stringHash = require('string-hash');
1111
var ReadableStream = require('stream').Readable;
@@ -16,7 +16,7 @@ var ReadableStream = require('stream').Readable;
1616
*/
1717
function generateShortName (name, filename, css) {
1818
// first occurrence of the name
19-
// TOOD: better match with regex
19+
// TODO: better match with regex
2020
var i = css.indexOf('.' + name);
2121
var numLines = css.substr(0, i).split(/[\r\n]/).length;
2222

@@ -74,21 +74,6 @@ function normalizeManifestPaths (tokensByFile, rootDir) {
7474
return output;
7575
}
7676

77-
function dedupeSources (sources) {
78-
var foundHashes = {}
79-
Object.keys(sources).forEach(function (key) {
80-
var hash = stringHash(sources[key]);
81-
if (foundHashes[hash]) {
82-
delete sources[key];
83-
}
84-
else {
85-
foundHashes[hash] = true;
86-
}
87-
})
88-
}
89-
90-
var cssExt = /\.css$/;
91-
9277
// caches
9378
//
9479
// persist these for as long as the process is running. #32
@@ -107,6 +92,11 @@ module.exports = function (browserify, options) {
10792
if (rootDir) { rootDir = path.resolve(rootDir); }
10893
if (!rootDir) { rootDir = process.cwd(); }
10994

95+
var transformOpts = {};
96+
if (options.global) {
97+
transformOpts.global = true;
98+
}
99+
110100
var cssOutFilename = options.output || options.o;
111101
var jsonOutFilename = options.json || options.jsonOutput;
112102
var sourceKey = cssOutFilename;
@@ -161,31 +151,56 @@ module.exports = function (browserify, options) {
161151
// but re-created on each bundle call.
162152
var compiledCssStream;
163153

164-
function transform (filename) {
165-
// only handle .css files
166-
if (!cssExt.test(filename)) {
167-
return through();
168-
}
154+
// TODO: clean this up so there's less scope crossing
155+
Cmify.prototype._flush = function (callback) {
156+
var self = this;
157+
var filename = this._filename;
169158

170-
return through(function noop () {}, function end () {
171-
var self = this;
159+
// only handle .css files
160+
if (!this.isCssFile(filename)) { return callback(); }
161+
162+
// convert css to js before pushing
163+
// reset the `tokensByFile` cache
164+
var relFilename = path.relative(rootDir, filename)
165+
tokensByFile[filename] = loader.tokensByFile[filename] = null;
166+
167+
loader.fetch(relFilename, '/').then(function (tokens) {
168+
var deps = loader.deps.dependenciesOf(filename);
169+
var output = [
170+
deps.map(function (f) {
171+
return "require('" + f + "')"
172+
}).join('\n'),
173+
'module.exports = ' + JSON.stringify(tokens)
174+
].join('\n');
175+
176+
var isValid = true;
177+
var isUndefined = /\bundefined\b/;
178+
Object.keys(tokens).forEach(function (k) {
179+
if (isUndefined.test(tokens[k])) {
180+
isValid = false;
181+
}
182+
});
172183

173-
loader.fetch(path.relative(rootDir, filename), '/').then(function (tokens) {
174-
var output = 'module.exports = ' + JSON.stringify(tokens);
184+
if (!isValid) {
185+
var err = 'Composition in ' + filename + ' contains an undefined reference';
186+
console.error(err)
187+
output += '\nconsole.error("' + err + '");';
188+
}
175189

176-
assign(tokensByFile, loader.tokensByFile);
190+
assign(tokensByFile, loader.tokensByFile);
177191

178-
self.queue(output);
179-
self.queue(null);
180-
}, function (err) {
181-
self.emit('error', err);
182-
});
192+
self.push(output);
193+
return callback()
194+
}).catch(function (err) {
195+
self.push('console.error("' + err + '");');
196+
browserify.emit('error', err);
197+
return callback()
183198
});
184-
}
199+
};
185200

186-
browserify.transform(transform, {
187-
global: true
188-
});
201+
browserify.transform(Cmify, transformOpts);
202+
203+
// ----
189204

190205
browserify.on('bundle', function (bundle) {
191206
// on each bundle, create a new stream b/c the old one might have ended
@@ -195,10 +210,6 @@ module.exports = function (browserify, options) {
195210
bundle.emit('css stream', compiledCssStream);
196211

197212
bundle.on('end', function () {
198-
// under certain conditions (eg. with shared libraries) we can end up with
199-
// multiple occurrences of the same rule, so we need to remove duplicates
200-
dedupeSources(loader.sources)
201-
202213
// Combine the collected sources for a single bundle into a single CSS file
203214
var css = loader.finalSource;
204215

@@ -223,9 +234,6 @@ module.exports = function (browserify, options) {
223234
}
224235
});
225236
}
226-
227-
// reset the `tokensByFile` cache
228-
tokensByFile = {};
229237
});
230238
});
231239

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
"main": "index.js",
66
"dependencies": {
77
"css-modules-loader-core": "^1.0.0",
8+
"dependency-graph": "^0.4.1",
89
"object-assign": "^3.0.0",
910
"promise-polyfill": "^2.1.0",
1011
"string-hash": "^1.1.0",

tests/cases/compose-node-module/expected.css

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
._cool_styles_styles__foo {
1+
._node_modules_cool_styles_styles__foo {
22
color: #F00;
33
}
44
._styles__foo {

tests/index.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ var path = require('path');
88
var casesDir = path.join(__dirname, 'cases');
99
var cssOutFilename = 'out.css';
1010

11+
var globalCases = ['compose-node-module', 'import-node-module'];
12+
1113
function runTestCase (dir) {
1214
tape('case: ' + dir, function (t) {
1315
var fakeFs = {
@@ -30,6 +32,9 @@ function runTestCase (dir) {
3032
rootDir: path.join(casesDir, dir)
3133
, output: cssOutFilename
3234
, generateScopedName: cssModulesify.generateLongName
35+
36+
// only certain cases will use a global transform
37+
, global: globalCases.indexOf(dir) !== -1
3338
});
3439

3540
b.bundle(function (err) {

0 commit comments

Comments
 (0)