UNPKG

10.2 kBJavaScriptView Raw
1/*
2 MIT License http://www.opensource.org/licenses/mit-license.php
3 Author Tobias Koppers @sokra
4*/
5var ConcatSource = require("webpack-sources").ConcatSource;
6var async = require("async");
7var ExtractedModule = require("./ExtractedModule");
8var Chunk = require("webpack/lib/Chunk");
9var OrderUndefinedError = require("./OrderUndefinedError");
10var loaderUtils = require("loader-utils");
11
12var nextId = 0;
13
14function ExtractTextPluginCompilation() {
15 this.modulesByIdentifier = {};
16}
17
18ExtractTextPlugin.prototype.mergeNonInitialChunks = function(chunk, intoChunk, checkedChunks) {
19 if(!intoChunk) {
20 checkedChunks = [];
21 chunk.chunks.forEach(function(c) {
22 if(c.initial) return;
23 this.mergeNonInitialChunks(c, chunk, checkedChunks);
24 }, this);
25 } else if(checkedChunks.indexOf(chunk) < 0) {
26 checkedChunks.push(chunk);
27 chunk.modules.slice().forEach(function(module) {
28 intoChunk.addModule(module);
29 module.addChunk(intoChunk);
30 });
31 chunk.chunks.forEach(function(c) {
32 if(c.initial) return;
33 this.mergeNonInitialChunks(c, intoChunk, checkedChunks);
34 }, this);
35 }
36};
37
38ExtractTextPluginCompilation.prototype.addModule = function(identifier, originalModule, source, additionalInformation, sourceMap, prevModules) {
39 var m;
40 if(!this.modulesByIdentifier[identifier]) {
41 m = this.modulesByIdentifier[identifier] = new ExtractedModule(identifier, originalModule, source, sourceMap, additionalInformation, prevModules);
42 } else {
43 m = this.modulesByIdentifier[identifier];
44 m.addPrevModules(prevModules);
45 if(originalModule.index2 < m.getOriginalModule().index2) {
46 m.setOriginalModule(originalModule);
47 }
48 }
49 return m;
50};
51
52ExtractTextPluginCompilation.prototype.addResultToChunk = function(identifier, result, originalModule, extractedChunk) {
53 if(!Array.isArray(result)) {
54 result = [[identifier, result]];
55 }
56 var counterMap = {};
57 var prevModules = [];
58 result.forEach(function(item) {
59 var c = counterMap[item[0]];
60 var module = this.addModule.call(this, item[0] + (c || ""), originalModule, item[1], item[2], item[3], prevModules.slice());
61 extractedChunk.addModule(module);
62 module.addChunk(extractedChunk);
63 counterMap[item[0]] = (c || 0) + 1;
64 prevModules.push(module);
65 }, this);
66};
67
68ExtractTextPlugin.prototype.renderExtractedChunk = function(chunk) {
69 var source = new ConcatSource();
70 chunk.modules.forEach(function(module) {
71 var moduleSource = module.source();
72 source.add(this.applyAdditionalInformation(moduleSource, module.additionalInformation));
73 }, this);
74 return source;
75};
76
77function isInvalidOrder(a, b) {
78 var bBeforeA = a.getPrevModules().indexOf(b) >= 0;
79 var aBeforeB = b.getPrevModules().indexOf(a) >= 0;
80 return aBeforeB && bBeforeA;
81}
82
83function getOrder(a, b) {
84 var aOrder = a.getOrder();
85 var bOrder = b.getOrder();
86 if(aOrder < bOrder) return -1;
87 if(aOrder > bOrder) return 1;
88 var aIndex = a.getOriginalModule().index2;
89 var bIndex = b.getOriginalModule().index2;
90 if(aIndex < bIndex) return -1;
91 if(aIndex > bIndex) return 1;
92 var bBeforeA = a.getPrevModules().indexOf(b) >= 0;
93 var aBeforeB = b.getPrevModules().indexOf(a) >= 0;
94 if(aBeforeB && !bBeforeA) return -1;
95 if(!aBeforeB && bBeforeA) return 1;
96 var ai = a.identifier();
97 var bi = b.identifier();
98 if(ai < bi) return -1;
99 if(ai > bi) return 1;
100 return 0;
101}
102
103function ExtractTextPlugin(id, filename, options) {
104 if(typeof filename !== "string") {
105 options = filename;
106 filename = id;
107 id = ++nextId;
108 }
109 if(!options) options = {};
110 this.filename = filename;
111 this.options = options;
112 this.id = id;
113}
114module.exports = ExtractTextPlugin;
115
116function mergeOptions(a, b) {
117 if(!b) return a;
118 Object.keys(b).forEach(function(key) {
119 a[key] = b[key];
120 });
121 return a;
122}
123
124ExtractTextPlugin.loader = function(options) {
125 return require.resolve("./loader") + (options ? "?" + JSON.stringify(options) : "");
126};
127
128ExtractTextPlugin.extract = function(before, loader, options) {
129 if(typeof loader === "string" || Array.isArray(loader)) {
130 if(typeof before === "string") {
131 before = before.split("!");
132 }
133 return [
134 ExtractTextPlugin.loader(mergeOptions({omit: before.length, extract: true, remove: true}, options))
135 ].concat(before, loader).join("!");
136 } else {
137 options = loader;
138 loader = before;
139 return [
140 ExtractTextPlugin.loader(mergeOptions({remove: true}, options))
141 ].concat(loader).join("!");
142 }
143};
144
145ExtractTextPlugin.prototype.applyAdditionalInformation = function(source, info) {
146 if(info) {
147 return new ConcatSource(
148 "@media " + info[0] + " {",
149 source,
150 "}"
151 );
152 }
153 return source;
154};
155
156ExtractTextPlugin.prototype.loader = function(options) {
157 options = JSON.parse(JSON.stringify(options || {}));
158 options.id = this.id;
159 return ExtractTextPlugin.loader(options);
160};
161
162ExtractTextPlugin.prototype.extract = function(before, loader, options) {
163 if(typeof loader === "string" || Array.isArray(loader)) {
164 if(typeof before === "string") {
165 before = before.split("!");
166 }
167 return [
168 this.loader(mergeOptions({omit: before.length, extract: true, remove: true}, options))
169 ].concat(before, loader).join("!");
170 } else {
171 options = loader;
172 loader = before;
173 return [
174 this.loader(mergeOptions({remove: true}, options))
175 ].concat(loader).join("!");
176 }
177};
178
179ExtractTextPlugin.prototype.apply = function(compiler) {
180 var options = this.options;
181 compiler.plugin("this-compilation", function(compilation) {
182 var extractCompilation = new ExtractTextPluginCompilation();
183 compilation.plugin("normal-module-loader", function(loaderContext, module) {
184 loaderContext[__dirname] = function(content, opt) {
185 if(options.disable)
186 return false;
187 if(!Array.isArray(content) && content !== null)
188 throw new Error("Exported value is not a string.");
189 module.meta[__dirname] = {
190 content: content,
191 options: opt || {}
192 };
193 return options.allChunks || module.meta[__dirname + "/extract"]; // eslint-disable-line no-path-concat
194 };
195 });
196 var filename = this.filename;
197 var id = this.id;
198 var extractedChunks, entryChunks, initialChunks;
199 compilation.plugin("optimize", function() {
200 entryChunks = compilation.chunks.filter(function(c) {
201 return c.entry;
202 });
203 initialChunks = compilation.chunks.filter(function(c) {
204 return c.initial;
205 });
206 });
207 compilation.plugin("optimize-tree", function(chunks, modules, callback) {
208 extractedChunks = chunks.map(function() {
209 return new Chunk();
210 });
211 chunks.forEach(function(chunk, i) {
212 var extractedChunk = extractedChunks[i];
213 extractedChunk.index = i;
214 extractedChunk.originalChunk = chunk;
215 extractedChunk.name = chunk.name;
216 extractedChunk.entry = chunk.entry;
217 extractedChunk.initial = chunk.initial;
218 chunk.chunks.forEach(function(c) {
219 extractedChunk.addChunk(extractedChunks[chunks.indexOf(c)]);
220 });
221 chunk.parents.forEach(function(c) {
222 extractedChunk.addParent(extractedChunks[chunks.indexOf(c)]);
223 });
224 });
225 entryChunks.forEach(function(chunk) {
226 var idx = chunks.indexOf(chunk);
227 if(idx < 0) return;
228 var extractedChunk = extractedChunks[idx];
229 extractedChunk.entry = true;
230 });
231 initialChunks.forEach(function(chunk) {
232 var idx = chunks.indexOf(chunk);
233 if(idx < 0) return;
234 var extractedChunk = extractedChunks[idx];
235 extractedChunk.initial = true;
236 });
237 async.forEach(chunks, function(chunk, callback) {
238 var extractedChunk = extractedChunks[chunks.indexOf(chunk)];
239 var shouldExtract = !!(options.allChunks || chunk.initial);
240 async.forEach(chunk.modules.slice(), function(module, callback) {
241 var meta = module.meta && module.meta[__dirname];
242 if(meta && (!meta.options.id || meta.options.id === id)) {
243 var wasExtracted = Array.isArray(meta.content);
244 if(shouldExtract !== wasExtracted) {
245 module.meta[__dirname + "/extract"] = shouldExtract; // eslint-disable-line no-path-concat
246 compilation.rebuildModule(module, function(err) {
247 if(err) {
248 compilation.errors.push(err);
249 return callback();
250 }
251 meta = module.meta[__dirname];
252 if(!Array.isArray(meta.content)) {
253 err = new Error(module.identifier() + " doesn't export content");
254 compilation.errors.push(err);
255 return callback();
256 }
257 if(meta.content)
258 extractCompilation.addResultToChunk(module.identifier(), meta.content, module, extractedChunk);
259 callback();
260 });
261 } else {
262 if(meta.content)
263 extractCompilation.addResultToChunk(module.identifier(), meta.content, module, extractedChunk);
264 callback();
265 }
266 } else callback();
267 }, function(err) {
268 if(err) return callback(err);
269 callback();
270 });
271 }, function(err) {
272 if(err) return callback(err);
273 extractedChunks.forEach(function(extractedChunk) {
274 if(extractedChunk.initial)
275 this.mergeNonInitialChunks(extractedChunk);
276 }, this);
277 extractedChunks.forEach(function(extractedChunk) {
278 if(!extractedChunk.initial) {
279 extractedChunk.modules.forEach(function(module) {
280 extractedChunk.removeModule(module);
281 });
282 }
283 });
284 compilation.applyPlugins("optimize-extracted-chunks", extractedChunks);
285 callback();
286 }.bind(this));
287 }.bind(this));
288 compilation.plugin("additional-assets", function(callback) {
289 extractedChunks.forEach(function(extractedChunk) {
290 if(extractedChunk.modules.length) {
291 extractedChunk.modules.sort(function(a, b) {
292 if(!options.ignoreOrder && isInvalidOrder(a, b)) {
293 compilation.errors.push(new OrderUndefinedError(a.getOriginalModule()));
294 compilation.errors.push(new OrderUndefinedError(b.getOriginalModule()));
295 }
296 return getOrder(a, b);
297 });
298 var chunk = extractedChunk.originalChunk;
299 var source = this.renderExtractedChunk(extractedChunk);
300 var file = compilation.getPath(filename, {
301 chunk: chunk
302 }).replace(/\[(?:(\w+):)?contenthash(?::([a-z]+\d*))?(?::(\d+))?\]/ig, function() {
303 return loaderUtils.getHashDigest(source.source(), arguments[1], arguments[2], parseInt(arguments[3], 10));
304 });
305 compilation.assets[file] = source;
306 chunk.files.push(file);
307 }
308 }, this);
309 callback();
310 }.bind(this));
311 }.bind(this));
312};