1 |
|
2 | if (!global.Promise) { global.Promise = require('promise-polyfill'); }
|
3 |
|
4 | var fs = require('fs');
|
5 | var path = require('path');
|
6 | var Cmify = require('./cmify');
|
7 | var Core = require('css-modules-loader-core');
|
8 | var FileSystemLoader = require('./file-system-loader');
|
9 | var assign = require('object-assign');
|
10 | var stringHash = require('string-hash');
|
11 | var ReadableStream = require('stream').Readable;
|
12 | var through = require('through2');
|
13 |
|
14 |
|
15 |
|
16 |
|
17 |
|
18 | function generateShortName (name, filename, css) {
|
19 |
|
20 |
|
21 | var i = css.indexOf('.' + name);
|
22 | var numLines = css.substr(0, i).split(/[\r\n]/).length;
|
23 |
|
24 | var hash = stringHash(css).toString(36).substr(0, 5);
|
25 | return '_' + name + '_' + hash + '_' + numLines;
|
26 | }
|
27 |
|
28 |
|
29 |
|
30 |
|
31 |
|
32 | function generateLongName (name, filename) {
|
33 | var sanitisedPath = filename.replace(/\.[^\.\/\\]+$/, '')
|
34 | .replace(/[\W_]+/g, '_')
|
35 | .replace(/^_|_$/g, '');
|
36 |
|
37 | return '_' + sanitisedPath + '__' + name;
|
38 | }
|
39 |
|
40 |
|
41 |
|
42 |
|
43 | function getDefaultPlugins (options) {
|
44 | var scope = Core.scope;
|
45 | var customNameFunc = options.generateScopedName;
|
46 | var defaultNameFunc = process.env.NODE_ENV === 'production' ?
|
47 | generateShortName :
|
48 | generateLongName;
|
49 |
|
50 | scope.generateScopedName = customNameFunc || defaultNameFunc;
|
51 |
|
52 | return [
|
53 | Core.values
|
54 | , Core.localByDefault
|
55 | , Core.extractImports
|
56 | , scope
|
57 | ];
|
58 | }
|
59 |
|
60 |
|
61 |
|
62 |
|
63 |
|
64 |
|
65 |
|
66 | function normalizeManifestPaths (tokensByFile, rootDir) {
|
67 | var output = {};
|
68 | var rootDirLength = rootDir.length + 1;
|
69 |
|
70 | Object.keys(tokensByFile).forEach(function (filename) {
|
71 | var normalizedFilename = filename.substr(rootDirLength);
|
72 | output[normalizedFilename] = tokensByFile[filename];
|
73 | });
|
74 |
|
75 | return output;
|
76 | }
|
77 |
|
78 |
|
79 |
|
80 |
|
81 |
|
82 |
|
83 | var tokensByFile = {};
|
84 |
|
85 |
|
86 | var loadersByFile = {};
|
87 |
|
88 | module.exports = function (browserify, options) {
|
89 | options = options || {};
|
90 |
|
91 |
|
92 | var rootDir = options.rootDir || options.d || browserify._options.basedir;
|
93 | if (rootDir) { rootDir = path.resolve(rootDir); }
|
94 | if (!rootDir) { rootDir = process.cwd(); }
|
95 |
|
96 | var transformOpts = {};
|
97 | if (options.global) {
|
98 | transformOpts.global = true;
|
99 | }
|
100 |
|
101 | var cssOutFilename = options.output || options.o;
|
102 | var jsonOutFilename = options.json || options.jsonOutput;
|
103 | transformOpts.cssOutFilename = cssOutFilename;
|
104 |
|
105 |
|
106 | var plugins = options.use || options.u;
|
107 | if (!plugins) {
|
108 | plugins = getDefaultPlugins(options);
|
109 | }
|
110 | else {
|
111 | if (typeof plugins === 'string') {
|
112 | plugins = [plugins];
|
113 | }
|
114 | }
|
115 |
|
116 | var postcssBefore = options.postcssBefore || options.before || [];
|
117 | var postcssAfter = options.postcssAfter || options.after || [];
|
118 | plugins = (Array.isArray(postcssBefore) ? postcssBefore : [postcssBefore]).concat(plugins).concat(postcssAfter);
|
119 |
|
120 |
|
121 | plugins = plugins.map(function requirePlugin (name) {
|
122 |
|
123 | if (typeof name !== 'string') {
|
124 | return name;
|
125 | }
|
126 |
|
127 | var plugin = module.parent.require(name);
|
128 |
|
129 |
|
130 | if (name === 'postcss-modules-scope') {
|
131 | options[name] = options[name] || {};
|
132 | if (!options[name].generateScopedName) {
|
133 | options[name].generateScopedName = generateLongName;
|
134 | }
|
135 | }
|
136 |
|
137 | if (name in options) {
|
138 | plugin = plugin(options[name]);
|
139 | }
|
140 | else {
|
141 | plugin = plugin.postcss || plugin();
|
142 | }
|
143 |
|
144 | return plugin;
|
145 | });
|
146 |
|
147 |
|
148 | if (!loadersByFile[cssOutFilename]) {
|
149 | loadersByFile[cssOutFilename] = new FileSystemLoader(rootDir, plugins);
|
150 | }
|
151 |
|
152 |
|
153 | Cmify.prototype._flush = function (callback) {
|
154 | var self = this;
|
155 | var filename = this._filename;
|
156 |
|
157 |
|
158 | if (!this.isCssFile(filename)) { return callback(); }
|
159 |
|
160 |
|
161 | var loader = loadersByFile[this._cssOutFilename];
|
162 |
|
163 |
|
164 |
|
165 | var relFilename = path.relative(rootDir, filename);
|
166 | tokensByFile[filename] = loader.tokensByFile[filename] = null;
|
167 |
|
168 | loader.fetch(relFilename, '/').then(function (tokens) {
|
169 | var deps = loader.deps.dependenciesOf(filename);
|
170 | var output = deps.map(function (f) {
|
171 | return 'require("' + f + '")';
|
172 | });
|
173 | output.push('module.exports = ' + JSON.stringify(tokens));
|
174 |
|
175 | var isValid = true;
|
176 | var isUndefined = /\bundefined\b/;
|
177 | Object.keys(tokens).forEach(function (k) {
|
178 | if (isUndefined.test(tokens[k])) {
|
179 | isValid = false;
|
180 | }
|
181 | });
|
182 |
|
183 | if (!isValid) {
|
184 | var err = 'Composition in ' + filename + ' contains an undefined reference';
|
185 | console.error(err);
|
186 | output.push('console.error("' + err + '");');
|
187 | }
|
188 |
|
189 | assign(tokensByFile, loader.tokensByFile);
|
190 |
|
191 | self.push(output.join('\n'));
|
192 | return callback();
|
193 | }).catch(function (err) {
|
194 | self.push('console.error("' + err + '");');
|
195 | self.emit('error', err);
|
196 | return callback();
|
197 | });
|
198 | };
|
199 |
|
200 | browserify.transform(Cmify, transformOpts);
|
201 |
|
202 |
|
203 |
|
204 | function addHooks () {
|
205 | browserify.pipeline.get('pack').push(through(function write (row, enc, next) {
|
206 | next(null, row);
|
207 | }, function end (cb) {
|
208 |
|
209 | var compiledCssStream = new ReadableStream();
|
210 | compiledCssStream._read = function () {};
|
211 |
|
212 | browserify.emit('css stream', compiledCssStream);
|
213 |
|
214 |
|
215 | var self = this;
|
216 | var loader = loadersByFile[cssOutFilename];
|
217 | var css = loader.finalSource;
|
218 |
|
219 |
|
220 | compiledCssStream.push(css);
|
221 | compiledCssStream.push(null);
|
222 |
|
223 | var writes = [];
|
224 |
|
225 |
|
226 | if (cssOutFilename) {
|
227 | writes.push(writeFile(cssOutFilename, css));
|
228 | }
|
229 |
|
230 |
|
231 | if (jsonOutFilename) {
|
232 | writes.push(writeFile(jsonOutFilename, JSON.stringify(normalizeManifestPaths(tokensByFile, rootDir))));
|
233 | }
|
234 | Promise.all(writes)
|
235 | .then(function () { cb(); })
|
236 | .catch(function (err) { self.emit('error', err); cb(); });
|
237 | }));
|
238 | }
|
239 |
|
240 | browserify.on('reset', addHooks);
|
241 | addHooks();
|
242 |
|
243 | return browserify;
|
244 | };
|
245 |
|
246 | function writeFile (filename, content) {
|
247 | return new Promise(function (resolve, reject) {
|
248 | fs.writeFile(filename, content, function (err) {
|
249 | if (err) reject(err);
|
250 | else resolve();
|
251 | });
|
252 | });
|
253 | }
|
254 |
|
255 | module.exports.generateShortName = generateShortName;
|
256 | module.exports.generateLongName = generateLongName;
|