Skip to content

Commit bfbf50d

Browse files
fix: worker compilation should not be async
In some cases, when using the AOT of Angular applications, multiple worker compilations may happen at the same time. As they share the same Angular Compiler instance, the compilation itself may fail. To resolve this, ensure there's only one compilation of a worker in a specified moment - start the next one after the current one finishes.
1 parent d8df7ac commit bfbf50d

File tree

1 file changed

+95
-86
lines changed

1 file changed

+95
-86
lines changed

src/index.js

Lines changed: 95 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -37,103 +37,112 @@ module.exports = function workerLoader() { };
3737

3838
const requests = [];
3939

40+
let pitchPromise = Promise.resolve();
4041
module.exports.pitch = function pitch(request) {
41-
if (!this.webpack) {
42-
throw new Error("Only usable with webpack");
43-
}
44-
4542
const callback = this.async();
46-
const options = loaderUtils.getOptions(this) || {};
47-
const compilerOptions = this._compiler.options || {};
48-
const pluginOptions = compilerOptions.plugins.find(p => p[NATIVESCRIPT_WORKER_PLUGIN_SYMBOL]).options;
49-
50-
// handle calls to itself to avoid an infinite loop
51-
if (requests.indexOf(request) === -1) {
52-
requests.push(request);
53-
} else {
54-
return callback(null, "");
55-
}
56-
57-
validateSchema(optionsSchema, options, "Worker Loader");
58-
if (!this._compilation.workerChunks) {
59-
this._compilation.workerChunks = [];
60-
}
6143

62-
const filename = loaderUtils.interpolateName(this, options.name || "[hash].worker.js", {
63-
context: options.context || this.rootContext,
64-
regExp: options.regExp,
65-
});
44+
pitchPromise = pitchPromise.then(() => {
45+
return new Promise((resolve, reject) => {
46+
if (!this.webpack) {
47+
reject(new Error("Only usable with webpack"));
48+
}
6649

67-
const outputOptions = {
68-
filename,
69-
chunkFilename: `[id].${filename}`,
70-
namedChunkFilename: null,
71-
};
72-
73-
const plugins = (pluginOptions.plugins || []).map(plugin => {
74-
if (typeof plugin !== "string") {
75-
return plugin;
76-
}
77-
const found = compilerOptions.plugins.find(p => p.constructor.name === plugin);
78-
if (!found) {
79-
console.warn(`Warning (worker-plugin): Plugin "${plugin}" is not found.`);
80-
}
81-
return found;
82-
});
50+
const options = loaderUtils.getOptions(this) || {};
51+
const compilerOptions = this._compiler.options || {};
52+
const pluginOptions = compilerOptions.plugins.find(p => p[NATIVESCRIPT_WORKER_PLUGIN_SYMBOL]).options;
8353

84-
const workerCompiler = this._compilation.createChildCompiler("worker", outputOptions, plugins);
85-
new WebWorkerTemplatePlugin(outputOptions).apply(workerCompiler);
86-
if (this.target !== "webworker" && this.target !== "web") {
87-
new NodeTargetPlugin().apply(workerCompiler);
88-
}
54+
// handle calls to itself to avoid an infinite loop
55+
if (requests.indexOf(request) === -1) {
56+
requests.push(request);
57+
} else {
58+
resolve();
59+
return callback(null, "");
60+
}
8961

90-
new SingleEntryPlugin(this.context, `!!${request}`, "main").apply(workerCompiler);
91-
const plugin = { name: "WorkerLoader" };
92-
93-
workerCompiler.hooks.thisCompilation.tap(plugin, compilation => {
94-
/**
95-
* A dirty hack to disable HMR plugin in childCompilation:
96-
* https://github.com/webpack/webpack/blob/4056506488c1e071dfc9a0127daa61bf531170bf/lib/HotModuleReplacementPlugin.js#L154
97-
*
98-
* Once we update to webpack@4.40.3 and above this can be removed:
99-
* https://github.com/webpack/webpack/commit/1c4138d6ac04b7b47daa5ec4475c0ae1b4f596a2
100-
*/
101-
compilation.hotUpdateChunkTemplate = null;
102-
});
62+
validateSchema(optionsSchema, options, "Worker Loader");
63+
if (!this._compilation.workerChunks) {
64+
this._compilation.workerChunks = [];
65+
}
10366

104-
workerCompiler.runAsChild((err, entries, childCompilation) => {
105-
if (err) {
106-
return callback(err);
107-
}
67+
const filename = loaderUtils.interpolateName(this, options.name || "[hash].worker.js", {
68+
context: options.context || this.rootContext,
69+
regExp: options.regExp,
70+
});
10871

109-
if (entries[0]) {
110-
const fileDeps = Array.from(childCompilation.fileDependencies);
111-
this.clearDependencies();
112-
fileDeps.forEach(fileName => {
113-
this.addDependency(fileName);
72+
const outputOptions = {
73+
filename,
74+
chunkFilename: `[id].${filename}`,
75+
namedChunkFilename: null,
76+
};
77+
78+
const plugins = (pluginOptions.plugins || []).map(plugin => {
79+
if (typeof plugin !== "string") {
80+
return plugin;
81+
}
82+
const found = compilerOptions.plugins.find(p => p.constructor.name === plugin);
83+
if (!found) {
84+
console.warn(`Warning (worker-plugin): Plugin "${plugin}" is not found.`);
85+
}
86+
return found;
11487
});
115-
/**
116-
* Clears the hash of the child compilation as it affects the hash of the parent compilation:
117-
* https://github.com/webpack/webpack/blob/4056506488c1e071dfc9a0127daa61bf531170bf/lib/Compilation.js#L2281
118-
*
119-
* If we don't clear the hash an emit of runtime.js and an empty [somehash].hot-update.json will happen on save without changes.
120-
* This will restart the NS application.
121-
*/
122-
childCompilation.hash = "";
123-
const workerFile = entries[0].files[0];
124-
this._compilation.workerChunks.push(workerFile);
125-
const workerFactory = getWorker(workerFile);
126-
127-
// invalidate cache
128-
const processedIndex = requests.indexOf(request);
129-
if (processedIndex > -1) {
130-
requests.splice(processedIndex, 1);
88+
89+
const workerCompiler = this._compilation.createChildCompiler("worker", outputOptions, plugins);
90+
new WebWorkerTemplatePlugin(outputOptions).apply(workerCompiler);
91+
if (this.target !== "webworker" && this.target !== "web") {
92+
new NodeTargetPlugin().apply(workerCompiler);
13193
}
13294

133-
return callback(null, `module.exports = function() {\n\treturn ${workerFactory};\n};`);
134-
}
95+
new SingleEntryPlugin(this.context, `!!${request}`, "main").apply(workerCompiler);
96+
const plugin = { name: "WorkerLoader" };
97+
98+
workerCompiler.hooks.thisCompilation.tap(plugin, compilation => {
99+
/**
100+
* A dirty hack to disable HMR plugin in childCompilation:
101+
* https://github.com/webpack/webpack/blob/4056506488c1e071dfc9a0127daa61bf531170bf/lib/HotModuleReplacementPlugin.js#L154
102+
*
103+
* Once we update to webpack@4.40.3 and above this can be removed:
104+
* https://github.com/webpack/webpack/commit/1c4138d6ac04b7b47daa5ec4475c0ae1b4f596a2
105+
*/
106+
compilation.hotUpdateChunkTemplate = null;
107+
});
135108

136-
return callback(null, "");
109+
workerCompiler.runAsChild((err, entries, childCompilation) => {
110+
if (err) {
111+
reject(err);
112+
return callback(err);
113+
}
114+
115+
if (entries[0]) {
116+
const fileDeps = Array.from(childCompilation.fileDependencies);
117+
this.clearDependencies();
118+
fileDeps.forEach(fileName => {
119+
this.addDependency(fileName);
120+
});
121+
/**
122+
* Clears the hash of the child compilation as it affects the hash of the parent compilation:
123+
* https://github.com/webpack/webpack/blob/4056506488c1e071dfc9a0127daa61bf531170bf/lib/Compilation.js#L2281
124+
*
125+
* If we don't clear the hash an emit of runtime.js and an empty [somehash].hot-update.json will happen on save without changes.
126+
* This will restart the NS application.
127+
*/
128+
childCompilation.hash = "";
129+
const workerFile = entries[0].files[0];
130+
this._compilation.workerChunks.push(workerFile);
131+
const workerFactory = getWorker(workerFile);
132+
133+
// invalidate cache
134+
const processedIndex = requests.indexOf(request);
135+
if (processedIndex > -1) {
136+
requests.splice(processedIndex, 1);
137+
}
138+
139+
resolve();
140+
return callback(null, `module.exports = function() {\n\treturn ${workerFactory};\n};`);
141+
}
142+
143+
resolve();
144+
return callback(null, "");
145+
});
146+
});
137147
});
138148
};
139-

0 commit comments

Comments
 (0)