Skip to content

Commit 8f2c842

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 8f2c842

File tree

1 file changed

+97
-86
lines changed

1 file changed

+97
-86
lines changed

src/index.js

Lines changed: 97 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -37,103 +37,114 @@ 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+
const error = new Error("Only usable with webpack");
48+
reject(error);
49+
return callback(error)
50+
}
6651

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-
});
52+
const options = loaderUtils.getOptions(this) || {};
53+
const compilerOptions = this._compiler.options || {};
54+
const pluginOptions = compilerOptions.plugins.find(p => p[NATIVESCRIPT_WORKER_PLUGIN_SYMBOL]).options;
8355

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-
}
56+
// handle calls to itself to avoid an infinite loop
57+
if (requests.indexOf(request) === -1) {
58+
requests.push(request);
59+
} else {
60+
resolve();
61+
return callback(null, "");
62+
}
8963

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-
});
64+
validateSchema(optionsSchema, options, "Worker Loader");
65+
if (!this._compilation.workerChunks) {
66+
this._compilation.workerChunks = [];
67+
}
10368

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

109-
if (entries[0]) {
110-
const fileDeps = Array.from(childCompilation.fileDependencies);
111-
this.clearDependencies();
112-
fileDeps.forEach(fileName => {
113-
this.addDependency(fileName);
74+
const outputOptions = {
75+
filename,
76+
chunkFilename: `[id].${filename}`,
77+
namedChunkFilename: null,
78+
};
79+
80+
const plugins = (pluginOptions.plugins || []).map(plugin => {
81+
if (typeof plugin !== "string") {
82+
return plugin;
83+
}
84+
const found = compilerOptions.plugins.find(p => p.constructor.name === plugin);
85+
if (!found) {
86+
console.warn(`Warning (worker-plugin): Plugin "${plugin}" is not found.`);
87+
}
88+
return found;
11489
});
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);
90+
91+
const workerCompiler = this._compilation.createChildCompiler("worker", outputOptions, plugins);
92+
new WebWorkerTemplatePlugin(outputOptions).apply(workerCompiler);
93+
if (this.target !== "webworker" && this.target !== "web") {
94+
new NodeTargetPlugin().apply(workerCompiler);
13195
}
13296

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

136-
return callback(null, "");
111+
workerCompiler.runAsChild((err, entries, childCompilation) => {
112+
if (err) {
113+
reject(err);
114+
return callback(err);
115+
}
116+
117+
if (entries[0]) {
118+
const fileDeps = Array.from(childCompilation.fileDependencies);
119+
this.clearDependencies();
120+
fileDeps.forEach(fileName => {
121+
this.addDependency(fileName);
122+
});
123+
/**
124+
* Clears the hash of the child compilation as it affects the hash of the parent compilation:
125+
* https://github.com/webpack/webpack/blob/4056506488c1e071dfc9a0127daa61bf531170bf/lib/Compilation.js#L2281
126+
*
127+
* 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.
128+
* This will restart the NS application.
129+
*/
130+
childCompilation.hash = "";
131+
const workerFile = entries[0].files[0];
132+
this._compilation.workerChunks.push(workerFile);
133+
const workerFactory = getWorker(workerFile);
134+
135+
// invalidate cache
136+
const processedIndex = requests.indexOf(request);
137+
if (processedIndex > -1) {
138+
requests.splice(processedIndex, 1);
139+
}
140+
141+
resolve();
142+
return callback(null, `module.exports = function() {\n\treturn ${workerFactory};\n};`);
143+
}
144+
145+
resolve();
146+
return callback(null, "");
147+
});
148+
});
137149
});
138150
};
139-

0 commit comments

Comments
 (0)