1 |
|
2 |
|
3 |
|
4 |
|
5 | "use strict";
|
6 |
|
7 | const parseJson = require("json-parse-better-errors");
|
8 | const asyncLib = require("neo-async");
|
9 | const path = require("path");
|
10 | const util = require("util");
|
11 | const {
|
12 | Tapable,
|
13 | SyncHook,
|
14 | SyncBailHook,
|
15 | AsyncParallelHook,
|
16 | AsyncSeriesHook
|
17 | } = require("tapable");
|
18 |
|
19 | const Compilation = require("./Compilation");
|
20 | const Stats = require("./Stats");
|
21 | const Watching = require("./Watching");
|
22 | const NormalModuleFactory = require("./NormalModuleFactory");
|
23 | const ContextModuleFactory = require("./ContextModuleFactory");
|
24 | const ResolverFactory = require("./ResolverFactory");
|
25 |
|
26 | const RequestShortener = require("./RequestShortener");
|
27 | const { makePathsRelative } = require("./util/identifier");
|
28 | const ConcurrentCompilationError = require("./ConcurrentCompilationError");
|
29 |
|
30 |
|
31 |
|
32 |
|
33 |
|
34 |
|
35 |
|
36 |
|
37 |
|
38 |
|
39 |
|
40 | class Compiler extends Tapable {
|
41 | constructor(context) {
|
42 | super();
|
43 | this.hooks = {
|
44 |
|
45 | shouldEmit: new SyncBailHook(["compilation"]),
|
46 |
|
47 | done: new AsyncSeriesHook(["stats"]),
|
48 |
|
49 | additionalPass: new AsyncSeriesHook([]),
|
50 |
|
51 | beforeRun: new AsyncSeriesHook(["compiler"]),
|
52 |
|
53 | run: new AsyncSeriesHook(["compiler"]),
|
54 |
|
55 | emit: new AsyncSeriesHook(["compilation"]),
|
56 |
|
57 | afterEmit: new AsyncSeriesHook(["compilation"]),
|
58 |
|
59 |
|
60 | thisCompilation: new SyncHook(["compilation", "params"]),
|
61 |
|
62 | compilation: new SyncHook(["compilation", "params"]),
|
63 |
|
64 | normalModuleFactory: new SyncHook(["normalModuleFactory"]),
|
65 |
|
66 | contextModuleFactory: new SyncHook(["contextModulefactory"]),
|
67 |
|
68 |
|
69 | beforeCompile: new AsyncSeriesHook(["params"]),
|
70 |
|
71 | compile: new SyncHook(["params"]),
|
72 |
|
73 | make: new AsyncParallelHook(["compilation"]),
|
74 |
|
75 | afterCompile: new AsyncSeriesHook(["compilation"]),
|
76 |
|
77 |
|
78 | watchRun: new AsyncSeriesHook(["compiler"]),
|
79 |
|
80 | failed: new SyncHook(["error"]),
|
81 |
|
82 | invalid: new SyncHook(["filename", "changeTime"]),
|
83 |
|
84 | watchClose: new SyncHook([]),
|
85 |
|
86 |
|
87 |
|
88 |
|
89 | environment: new SyncHook([]),
|
90 |
|
91 | afterEnvironment: new SyncHook([]),
|
92 |
|
93 | afterPlugins: new SyncHook(["compiler"]),
|
94 |
|
95 | afterResolvers: new SyncHook(["compiler"]),
|
96 |
|
97 | entryOption: new SyncBailHook(["context", "entry"])
|
98 | };
|
99 |
|
100 | this._pluginCompat.tap("Compiler", options => {
|
101 | switch (options.name) {
|
102 | case "additional-pass":
|
103 | case "before-run":
|
104 | case "run":
|
105 | case "emit":
|
106 | case "after-emit":
|
107 | case "before-compile":
|
108 | case "make":
|
109 | case "after-compile":
|
110 | case "watch-run":
|
111 | options.async = true;
|
112 | break;
|
113 | }
|
114 | });
|
115 |
|
116 |
|
117 | this.name = undefined;
|
118 |
|
119 | this.parentCompilation = undefined;
|
120 |
|
121 | this.outputPath = "";
|
122 |
|
123 | this.outputFileSystem = null;
|
124 | this.inputFileSystem = null;
|
125 |
|
126 |
|
127 | this.recordsInputPath = null;
|
128 |
|
129 | this.recordsOutputPath = null;
|
130 | this.records = {};
|
131 | this.removedFiles = new Set();
|
132 |
|
133 | this.fileTimestamps = new Map();
|
134 |
|
135 | this.contextTimestamps = new Map();
|
136 |
|
137 | this.resolverFactory = new ResolverFactory();
|
138 |
|
139 |
|
140 | this.resolvers = {
|
141 | normal: {
|
142 | plugins: util.deprecate((hook, fn) => {
|
143 | this.resolverFactory.plugin("resolver normal", resolver => {
|
144 | resolver.plugin(hook, fn);
|
145 | });
|
146 | }, "webpack: Using compiler.resolvers.normal is deprecated.\n" + 'Use compiler.resolverFactory.plugin("resolver normal", resolver => {\n resolver.plugin(/* … */);\n}); instead.'),
|
147 | apply: util.deprecate((...args) => {
|
148 | this.resolverFactory.plugin("resolver normal", resolver => {
|
149 | resolver.apply(...args);
|
150 | });
|
151 | }, "webpack: Using compiler.resolvers.normal is deprecated.\n" + 'Use compiler.resolverFactory.plugin("resolver normal", resolver => {\n resolver.apply(/* … */);\n}); instead.')
|
152 | },
|
153 | loader: {
|
154 | plugins: util.deprecate((hook, fn) => {
|
155 | this.resolverFactory.plugin("resolver loader", resolver => {
|
156 | resolver.plugin(hook, fn);
|
157 | });
|
158 | }, "webpack: Using compiler.resolvers.loader is deprecated.\n" + 'Use compiler.resolverFactory.plugin("resolver loader", resolver => {\n resolver.plugin(/* … */);\n}); instead.'),
|
159 | apply: util.deprecate((...args) => {
|
160 | this.resolverFactory.plugin("resolver loader", resolver => {
|
161 | resolver.apply(...args);
|
162 | });
|
163 | }, "webpack: Using compiler.resolvers.loader is deprecated.\n" + 'Use compiler.resolverFactory.plugin("resolver loader", resolver => {\n resolver.apply(/* … */);\n}); instead.')
|
164 | },
|
165 | context: {
|
166 | plugins: util.deprecate((hook, fn) => {
|
167 | this.resolverFactory.plugin("resolver context", resolver => {
|
168 | resolver.plugin(hook, fn);
|
169 | });
|
170 | }, "webpack: Using compiler.resolvers.context is deprecated.\n" + 'Use compiler.resolverFactory.plugin("resolver context", resolver => {\n resolver.plugin(/* … */);\n}); instead.'),
|
171 | apply: util.deprecate((...args) => {
|
172 | this.resolverFactory.plugin("resolver context", resolver => {
|
173 | resolver.apply(...args);
|
174 | });
|
175 | }, "webpack: Using compiler.resolvers.context is deprecated.\n" + 'Use compiler.resolverFactory.plugin("resolver context", resolver => {\n resolver.apply(/* … */);\n}); instead.')
|
176 | }
|
177 | };
|
178 |
|
179 |
|
180 | this.options = ({});
|
181 |
|
182 | this.context = context;
|
183 |
|
184 | this.requestShortener = new RequestShortener(context);
|
185 |
|
186 |
|
187 | this.running = false;
|
188 |
|
189 |
|
190 | this.watchMode = false;
|
191 | }
|
192 |
|
193 | watch(watchOptions, handler) {
|
194 | if (this.running) return handler(new ConcurrentCompilationError());
|
195 |
|
196 | this.running = true;
|
197 | this.watchMode = true;
|
198 | this.fileTimestamps = new Map();
|
199 | this.contextTimestamps = new Map();
|
200 | this.removedFiles = new Set();
|
201 | return new Watching(this, watchOptions, handler);
|
202 | }
|
203 |
|
204 | run(callback) {
|
205 | if (this.running) return callback(new ConcurrentCompilationError());
|
206 |
|
207 | const finalCallback = (err, stats) => {
|
208 | this.running = false;
|
209 |
|
210 | if (callback !== undefined) return callback(err, stats);
|
211 | };
|
212 |
|
213 | const startTime = Date.now();
|
214 |
|
215 | this.running = true;
|
216 |
|
217 | const onCompiled = (err, compilation) => {
|
218 | if (err) return finalCallback(err);
|
219 |
|
220 | if (this.hooks.shouldEmit.call(compilation) === false) {
|
221 | const stats = new Stats(compilation);
|
222 | stats.startTime = startTime;
|
223 | stats.endTime = Date.now();
|
224 | this.hooks.done.callAsync(stats, err => {
|
225 | if (err) return finalCallback(err);
|
226 | return finalCallback(null, stats);
|
227 | });
|
228 | return;
|
229 | }
|
230 |
|
231 | this.emitAssets(compilation, err => {
|
232 | if (err) return finalCallback(err);
|
233 |
|
234 | if (compilation.hooks.needAdditionalPass.call()) {
|
235 | compilation.needAdditionalPass = true;
|
236 |
|
237 | const stats = new Stats(compilation);
|
238 | stats.startTime = startTime;
|
239 | stats.endTime = Date.now();
|
240 | this.hooks.done.callAsync(stats, err => {
|
241 | if (err) return finalCallback(err);
|
242 |
|
243 | this.hooks.additionalPass.callAsync(err => {
|
244 | if (err) return finalCallback(err);
|
245 | this.compile(onCompiled);
|
246 | });
|
247 | });
|
248 | return;
|
249 | }
|
250 |
|
251 | this.emitRecords(err => {
|
252 | if (err) return finalCallback(err);
|
253 |
|
254 | const stats = new Stats(compilation);
|
255 | stats.startTime = startTime;
|
256 | stats.endTime = Date.now();
|
257 | this.hooks.done.callAsync(stats, err => {
|
258 | if (err) return finalCallback(err);
|
259 | return finalCallback(null, stats);
|
260 | });
|
261 | });
|
262 | });
|
263 | };
|
264 |
|
265 | this.hooks.beforeRun.callAsync(this, err => {
|
266 | if (err) return finalCallback(err);
|
267 |
|
268 | this.hooks.run.callAsync(this, err => {
|
269 | if (err) return finalCallback(err);
|
270 |
|
271 | this.readRecords(err => {
|
272 | if (err) return finalCallback(err);
|
273 |
|
274 | this.compile(onCompiled);
|
275 | });
|
276 | });
|
277 | });
|
278 | }
|
279 |
|
280 | runAsChild(callback) {
|
281 | this.compile((err, compilation) => {
|
282 | if (err) return callback(err);
|
283 |
|
284 | this.parentCompilation.children.push(compilation);
|
285 | for (const name of Object.keys(compilation.assets)) {
|
286 | this.parentCompilation.assets[name] = compilation.assets[name];
|
287 | }
|
288 |
|
289 | const entries = Array.from(
|
290 | compilation.entrypoints.values(),
|
291 | ep => ep.chunks
|
292 | ).reduce((array, chunks) => {
|
293 | return array.concat(chunks);
|
294 | }, []);
|
295 |
|
296 | return callback(null, entries, compilation);
|
297 | });
|
298 | }
|
299 |
|
300 | purgeInputFileSystem() {
|
301 | if (this.inputFileSystem && this.inputFileSystem.purge) {
|
302 | this.inputFileSystem.purge();
|
303 | }
|
304 | }
|
305 |
|
306 | emitAssets(compilation, callback) {
|
307 | let outputPath;
|
308 | const emitFiles = err => {
|
309 | if (err) return callback(err);
|
310 |
|
311 | asyncLib.forEach(
|
312 | compilation.assets,
|
313 | (source, file, callback) => {
|
314 | let targetFile = file;
|
315 | const queryStringIdx = targetFile.indexOf("?");
|
316 | if (queryStringIdx >= 0) {
|
317 | targetFile = targetFile.substr(0, queryStringIdx);
|
318 | }
|
319 |
|
320 | const writeOut = err => {
|
321 | if (err) return callback(err);
|
322 | const targetPath = this.outputFileSystem.join(
|
323 | outputPath,
|
324 | targetFile
|
325 | );
|
326 | if (source.existsAt === targetPath) {
|
327 | source.emitted = false;
|
328 | return callback();
|
329 | }
|
330 | let content = source.source();
|
331 |
|
332 | if (!Buffer.isBuffer(content)) {
|
333 | content = Buffer.from(content, "utf8");
|
334 | }
|
335 |
|
336 | source.existsAt = targetPath;
|
337 | source.emitted = true;
|
338 | this.outputFileSystem.writeFile(targetPath, content, callback);
|
339 | };
|
340 |
|
341 | if (targetFile.match(/\/|\\/)) {
|
342 | const dir = path.dirname(targetFile);
|
343 | this.outputFileSystem.mkdirp(
|
344 | this.outputFileSystem.join(outputPath, dir),
|
345 | writeOut
|
346 | );
|
347 | } else {
|
348 | writeOut();
|
349 | }
|
350 | },
|
351 | err => {
|
352 | if (err) return callback(err);
|
353 |
|
354 | this.hooks.afterEmit.callAsync(compilation, err => {
|
355 | if (err) return callback(err);
|
356 |
|
357 | return callback();
|
358 | });
|
359 | }
|
360 | );
|
361 | };
|
362 |
|
363 | this.hooks.emit.callAsync(compilation, err => {
|
364 | if (err) return callback(err);
|
365 | outputPath = compilation.getPath(this.outputPath);
|
366 | this.outputFileSystem.mkdirp(outputPath, emitFiles);
|
367 | });
|
368 | }
|
369 |
|
370 | emitRecords(callback) {
|
371 | if (!this.recordsOutputPath) return callback();
|
372 | const idx1 = this.recordsOutputPath.lastIndexOf("/");
|
373 | const idx2 = this.recordsOutputPath.lastIndexOf("\\");
|
374 | let recordsOutputPathDirectory = null;
|
375 | if (idx1 > idx2) {
|
376 | recordsOutputPathDirectory = this.recordsOutputPath.substr(0, idx1);
|
377 | } else if (idx1 < idx2) {
|
378 | recordsOutputPathDirectory = this.recordsOutputPath.substr(0, idx2);
|
379 | }
|
380 |
|
381 | const writeFile = () => {
|
382 | this.outputFileSystem.writeFile(
|
383 | this.recordsOutputPath,
|
384 | JSON.stringify(this.records, undefined, 2),
|
385 | callback
|
386 | );
|
387 | };
|
388 |
|
389 | if (!recordsOutputPathDirectory) {
|
390 | return writeFile();
|
391 | }
|
392 | this.outputFileSystem.mkdirp(recordsOutputPathDirectory, err => {
|
393 | if (err) return callback(err);
|
394 | writeFile();
|
395 | });
|
396 | }
|
397 |
|
398 | readRecords(callback) {
|
399 | if (!this.recordsInputPath) {
|
400 | this.records = {};
|
401 | return callback();
|
402 | }
|
403 | this.inputFileSystem.stat(this.recordsInputPath, err => {
|
404 |
|
405 |
|
406 | if (err) return callback();
|
407 |
|
408 | this.inputFileSystem.readFile(this.recordsInputPath, (err, content) => {
|
409 | if (err) return callback(err);
|
410 |
|
411 | try {
|
412 | this.records = parseJson(content.toString("utf-8"));
|
413 | } catch (e) {
|
414 | e.message = "Cannot parse records: " + e.message;
|
415 | return callback(e);
|
416 | }
|
417 |
|
418 | return callback();
|
419 | });
|
420 | });
|
421 | }
|
422 |
|
423 | createChildCompiler(
|
424 | compilation,
|
425 | compilerName,
|
426 | compilerIndex,
|
427 | outputOptions,
|
428 | plugins
|
429 | ) {
|
430 | const childCompiler = new Compiler(this.context);
|
431 | if (Array.isArray(plugins)) {
|
432 | for (const plugin of plugins) {
|
433 | plugin.apply(childCompiler);
|
434 | }
|
435 | }
|
436 | for (const name in this.hooks) {
|
437 | if (
|
438 | ![
|
439 | "make",
|
440 | "compile",
|
441 | "emit",
|
442 | "afterEmit",
|
443 | "invalid",
|
444 | "done",
|
445 | "thisCompilation"
|
446 | ].includes(name)
|
447 | ) {
|
448 | if (childCompiler.hooks[name]) {
|
449 | childCompiler.hooks[name].taps = this.hooks[name].taps.slice();
|
450 | }
|
451 | }
|
452 | }
|
453 | childCompiler.name = compilerName;
|
454 | childCompiler.outputPath = this.outputPath;
|
455 | childCompiler.inputFileSystem = this.inputFileSystem;
|
456 | childCompiler.outputFileSystem = null;
|
457 | childCompiler.resolverFactory = this.resolverFactory;
|
458 | childCompiler.fileTimestamps = this.fileTimestamps;
|
459 | childCompiler.contextTimestamps = this.contextTimestamps;
|
460 |
|
461 | const relativeCompilerName = makePathsRelative(this.context, compilerName);
|
462 | if (!this.records[relativeCompilerName]) {
|
463 | this.records[relativeCompilerName] = [];
|
464 | }
|
465 | if (this.records[relativeCompilerName][compilerIndex]) {
|
466 | childCompiler.records = this.records[relativeCompilerName][compilerIndex];
|
467 | } else {
|
468 | this.records[relativeCompilerName].push((childCompiler.records = {}));
|
469 | }
|
470 |
|
471 | childCompiler.options = Object.create(this.options);
|
472 | childCompiler.options.output = Object.create(childCompiler.options.output);
|
473 | for (const name in outputOptions) {
|
474 | childCompiler.options.output[name] = outputOptions[name];
|
475 | }
|
476 | childCompiler.parentCompilation = compilation;
|
477 |
|
478 | compilation.hooks.childCompiler.call(
|
479 | childCompiler,
|
480 | compilerName,
|
481 | compilerIndex
|
482 | );
|
483 |
|
484 | return childCompiler;
|
485 | }
|
486 |
|
487 | isChild() {
|
488 | return !!this.parentCompilation;
|
489 | }
|
490 |
|
491 | createCompilation() {
|
492 | return new Compilation(this);
|
493 | }
|
494 |
|
495 | newCompilation(params) {
|
496 | const compilation = this.createCompilation();
|
497 | compilation.fileTimestamps = this.fileTimestamps;
|
498 | compilation.contextTimestamps = this.contextTimestamps;
|
499 | compilation.name = this.name;
|
500 | compilation.records = this.records;
|
501 | compilation.compilationDependencies = params.compilationDependencies;
|
502 | this.hooks.thisCompilation.call(compilation, params);
|
503 | this.hooks.compilation.call(compilation, params);
|
504 | return compilation;
|
505 | }
|
506 |
|
507 | createNormalModuleFactory() {
|
508 | const normalModuleFactory = new NormalModuleFactory(
|
509 | this.options.context,
|
510 | this.resolverFactory,
|
511 | this.options.module || {}
|
512 | );
|
513 | this.hooks.normalModuleFactory.call(normalModuleFactory);
|
514 | return normalModuleFactory;
|
515 | }
|
516 |
|
517 | createContextModuleFactory() {
|
518 | const contextModuleFactory = new ContextModuleFactory(this.resolverFactory);
|
519 | this.hooks.contextModuleFactory.call(contextModuleFactory);
|
520 | return contextModuleFactory;
|
521 | }
|
522 |
|
523 | newCompilationParams() {
|
524 | const params = {
|
525 | normalModuleFactory: this.createNormalModuleFactory(),
|
526 | contextModuleFactory: this.createContextModuleFactory(),
|
527 | compilationDependencies: new Set()
|
528 | };
|
529 | return params;
|
530 | }
|
531 |
|
532 | compile(callback) {
|
533 | const params = this.newCompilationParams();
|
534 | this.hooks.beforeCompile.callAsync(params, err => {
|
535 | if (err) return callback(err);
|
536 |
|
537 | this.hooks.compile.call(params);
|
538 |
|
539 | const compilation = this.newCompilation(params);
|
540 |
|
541 | this.hooks.make.callAsync(compilation, err => {
|
542 | if (err) return callback(err);
|
543 |
|
544 | compilation.finish();
|
545 |
|
546 | compilation.seal(err => {
|
547 | if (err) return callback(err);
|
548 |
|
549 | this.hooks.afterCompile.callAsync(compilation, err => {
|
550 | if (err) return callback(err);
|
551 |
|
552 | return callback(null, compilation);
|
553 | });
|
554 | });
|
555 | });
|
556 | });
|
557 | }
|
558 | }
|
559 |
|
560 | module.exports = Compiler;
|