Skip to content
Closed
16 changes: 16 additions & 0 deletions declarations/WebpackOptions.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1027,6 +1027,10 @@ export interface EntryDescription {
* The method of loading chunks (methods included by default are 'jsonp' (web), 'import' (ESM), 'importScripts' (WebWorker), 'require' (sync node.js), 'async-node' (async node.js), but others might be added by plugins).
*/
chunkLoading?: ChunkLoading;
/**
* Use cross-origin loading technique for this entry.
*/
crossOriginLoading?: boolean;
/**
* The entrypoints that the current entrypoint depend on. They must be loaded when this entrypoint is loaded.
*/
Expand Down Expand Up @@ -2124,6 +2128,10 @@ export interface Output {
* The method of loading chunks (methods included by default are 'jsonp' (web), 'import' (ESM), 'importScripts' (WebWorker), 'require' (sync node.js), 'async-node' (async node.js), but others might be added by plugins).
*/
workerChunkLoading?: ChunkLoading;
/**
* This option enables cross-origin loading of workers.
*/
workerCrossOriginLoading?: boolean;
/**
* The method of loading WebAssembly Modules (methods included by default are 'fetch' (web/WebWorker), 'async-node' (node.js), but others might be added by plugins).
*/
Expand Down Expand Up @@ -2704,6 +2712,10 @@ export interface EntryDescriptionNormalized {
* The method of loading chunks (methods included by default are 'jsonp' (web), 'import' (ESM), 'importScripts' (WebWorker), 'require' (sync node.js), 'async-node' (async node.js), but others might be added by plugins).
*/
chunkLoading?: ChunkLoading;
/**
* Use cross-origin loading technique for this entry.
*/
crossOriginLoading?: boolean;
/**
* The entrypoints that the current entrypoint depend on. They must be loaded when this entrypoint is loaded.
*/
Expand Down Expand Up @@ -3240,6 +3252,10 @@ export interface OutputNormalized {
* The method of loading chunks (methods included by default are 'jsonp' (web), 'import' (ESM), 'importScripts' (WebWorker), 'require' (sync node.js), 'async-node' (async node.js), but others might be added by plugins).
*/
workerChunkLoading?: ChunkLoading;
/**
* This option enables cross-origin loading of workers.
*/
workerCrossOriginLoading?: boolean;
/**
* The method of loading WebAssembly Modules (methods included by default are 'fetch' (web/WebWorker), 'async-node' (node.js), but others might be added by plugins).
*/
Expand Down
11 changes: 11 additions & 0 deletions lib/RuntimeGlobals.js
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,12 @@ exports.loadScript = "__webpack_require__.l";
*/
exports.createScriptUrl = "__webpack_require__.tu";

/**
* function to create blob url for cross-origin loading
* Arguments: (code: string) => URL
*/
exports.createScriptBlob = "__webpack_require__.sb";

/**
* the chunk name of the chunk with the runtime
*/
Expand Down Expand Up @@ -333,6 +339,11 @@ exports.systemContext = "__webpack_require__.y";
*/
exports.baseURI = "__webpack_require__.b";

/**
* the url of current script
*/
exports.scriptUrl = "__webpack_require__.su";

/**
* a RelativeURL class when relative URLs are used
*/
Expand Down
23 changes: 23 additions & 0 deletions lib/RuntimePlugin.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,10 @@ const AutoPublicPathRuntimeModule = require("./runtime/AutoPublicPathRuntimeModu
const CompatGetDefaultExportRuntimeModule = require("./runtime/CompatGetDefaultExportRuntimeModule");
const CompatRuntimeModule = require("./runtime/CompatRuntimeModule");
const CreateFakeNamespaceObjectRuntimeModule = require("./runtime/CreateFakeNamespaceObjectRuntimeModule");
const CreateScriptBlobRuntimeModule = require("./runtime/CreateScriptBlobRuntimeModule");
const CreateScriptUrlRuntimeModule = require("./runtime/CreateScriptUrlRuntimeModule");
const DefinePropertyGettersRuntimeModule = require("./runtime/DefinePropertyGettersRuntimeModule");
const DetectScriptUrlRuntimeModule = require("./runtime/DetectScriptUrlRuntimeModule");
const EnsureChunkRuntimeModule = require("./runtime/EnsureChunkRuntimeModule");
const GetChunkFilenameRuntimeModule = require("./runtime/GetChunkFilenameRuntimeModule");
const GetMainFilenameRuntimeModule = require("./runtime/GetMainFilenameRuntimeModule");
Expand Down Expand Up @@ -53,6 +55,7 @@ const GLOBALS_ON_REQUIRE = [
RuntimeGlobals.publicPath,
RuntimeGlobals.baseURI,
RuntimeGlobals.relativeUrl,
RuntimeGlobals.scriptUrl,
RuntimeGlobals.scriptNonce,
RuntimeGlobals.uncaughtErrorHandler,
RuntimeGlobals.asyncModule,
Expand Down Expand Up @@ -186,6 +189,7 @@ class RuntimePlugin {
: globalPublicPath;

if (publicPath === "auto") {
set.add(RuntimeGlobals.scriptUrl);
const module = new AutoPublicPathRuntimeModule();
if (scriptType !== "module") set.add(RuntimeGlobals.global);
compilation.addRuntimeModule(chunk, module);
Expand Down Expand Up @@ -348,12 +352,31 @@ class RuntimePlugin {
);
return true;
});
compilation.hooks.runtimeRequirementInTree
.for(RuntimeGlobals.createScriptBlob)
.tap("RuntimePlugin", (chunk, set) => {
compilation.addRuntimeModule(
chunk,
new CreateScriptBlobRuntimeModule()
);
return true;
});
compilation.hooks.runtimeRequirementInTree
.for(RuntimeGlobals.relativeUrl)
.tap("RuntimePlugin", (chunk, set) => {
compilation.addRuntimeModule(chunk, new RelativeUrlRuntimeModule());
return true;
});
compilation.hooks.runtimeRequirementInTree
.for(RuntimeGlobals.scriptUrl)
.tap("RuntimePlugin", (chunk, set) => {
set.add(RuntimeGlobals.global);
compilation.addRuntimeModule(
chunk,
new DetectScriptUrlRuntimeModule()
);
return true;
});
compilation.hooks.runtimeRequirementInTree
.for(RuntimeGlobals.onChunksLoaded)
.tap("RuntimePlugin", (chunk, set) => {
Expand Down
1 change: 1 addition & 0 deletions lib/WebpackOptionsApply.js
Original file line number Diff line number Diff line change
Expand Up @@ -325,6 +325,7 @@ class WebpackOptionsApply extends OptionsApply {
new URLPlugin().apply(compiler);
new WorkerPlugin(
options.output.workerChunkLoading,
options.output.workerCrossOriginLoading,
options.output.workerWasmLoading,
options.output.module
).apply(compiler);
Expand Down
1 change: 1 addition & 0 deletions lib/config/defaults.js
Original file line number Diff line number Diff line change
Expand Up @@ -806,6 +806,7 @@ const applyOutputDefaults = (
}
return false;
});
D(output, "workerCrossOriginLoading", false);
F(output, "workerWasmLoading", () => output.wasmLoading);
F(output, "devtoolNamespace", () => output.uniqueName);
if (output.library) {
Expand Down
1 change: 1 addition & 0 deletions lib/config/normalization.js
Original file line number Diff line number Diff line change
Expand Up @@ -366,6 +366,7 @@ const getNormalizedWebpackOptions = config => {
wasmLoading: output.wasmLoading,
webassemblyModuleFilename: output.webassemblyModuleFilename,
workerChunkLoading: output.workerChunkLoading,
workerCrossOriginLoading: output.workerCrossOriginLoading,
workerWasmLoading: output.workerWasmLoading
};
return result;
Expand Down
46 changes: 36 additions & 10 deletions lib/dependencies/WorkerDependency.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,12 @@ class WorkerDependency extends ModuleDependency {
/**
* @param {string} request request
* @param {[number, number]} range range
* @param {boolean} crossOriginLoading Use cross-origin loading
*/
constructor(request, range) {
constructor(request, range, crossOriginLoading) {
super(request);
this.range = range;
this.crossOriginLoading = crossOriginLoading;
}

/**
Expand Down Expand Up @@ -60,7 +62,8 @@ WorkerDependency.Template = class WorkerDependencyTemplate extends (
* @returns {void}
*/
apply(dependency, source, templateContext) {
const { chunkGraph, moduleGraph, runtimeRequirements } = templateContext;
const { chunkGraph, moduleGraph, runtimeRequirements, runtimeTemplate } =
templateContext;
const dep = /** @type {WorkerDependency} */ (dependency);
const block = /** @type {AsyncDependenciesBlock} */ (
moduleGraph.getParentBlock(dependency)
Expand All @@ -71,16 +74,39 @@ WorkerDependency.Template = class WorkerDependencyTemplate extends (
const chunk = entrypoint.getEntrypointChunk();

runtimeRequirements.add(RuntimeGlobals.publicPath);
runtimeRequirements.add(RuntimeGlobals.baseURI);
runtimeRequirements.add(RuntimeGlobals.getChunkScriptFilename);

source.replace(
dep.range[0],
dep.range[1] - 1,
`/* worker import */ ${RuntimeGlobals.publicPath} + ${
RuntimeGlobals.getChunkScriptFilename
}(${JSON.stringify(chunk.id)}), ${RuntimeGlobals.baseURI}`
);
const workerUrlTemplate = `${RuntimeGlobals.publicPath} + ${
RuntimeGlobals.getChunkScriptFilename
}(${JSON.stringify(chunk.id)})`;

if (dep.crossOriginLoading) {
runtimeRequirements.add(RuntimeGlobals.scriptUrl);
runtimeRequirements.add(RuntimeGlobals.createScriptBlob);

const baseUriTemplate = `JSON.stringify(${RuntimeGlobals.scriptUrl})`;
const scriptUrlTemplate = `JSON.stringify(new URL(${workerUrlTemplate}, ${RuntimeGlobals.scriptUrl}))`;
const crossOriginLoaderTemplate = [
`"self.__webpack_base_uri__=" + ${baseUriTemplate} + ";`,
runtimeTemplate.isModule()
? `import " + ${scriptUrlTemplate} + ";"`
: `importScripts(" + ${scriptUrlTemplate} + ");"`
].join("");

source.replace(
dep.range[0],
dep.range[1] - 1,
`/* worker import */ ${RuntimeGlobals.createScriptBlob}(${crossOriginLoaderTemplate})`
);
} else {
runtimeRequirements.add(RuntimeGlobals.baseURI);

source.replace(
dep.range[0],
dep.range[1] - 1,
`/* worker import */ ${workerUrlTemplate}, ${RuntimeGlobals.baseURI}`
);
}
}
};

Expand Down
19 changes: 17 additions & 2 deletions lib/dependencies/WorkerPlugin.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ const WorkerDependency = require("./WorkerDependency");
/** @typedef {import("estree").Pattern} Pattern */
/** @typedef {import("estree").Property} Property */
/** @typedef {import("estree").SpreadElement} SpreadElement */
/** @typedef {import("../../declarations/WebpackOptions").ChunkLoading} ChunkLoading */
/** @typedef {import("../../declarations/WebpackOptions").OutputModule} OutputModule */
/** @typedef {import("../../declarations/WebpackOptions").WasmLoading} WasmLoading */
/** @typedef {import("../Compiler")} Compiler */
/** @typedef {import("../Entrypoint").EntryOptions} EntryOptions */
/** @typedef {import("../Parser").ParserState} ParserState */
Expand All @@ -48,8 +51,15 @@ const DEFAULT_SYNTAX = [
const workerIndexMap = new WeakMap();

class WorkerPlugin {
constructor(chunkLoading, wasmLoading, module) {
/**
* @param {ChunkLoading} chunkLoading The method of loading chunks.
* @param {boolean} crossOriginLoading This option enables cross-origin loading of workers.
* @param {WasmLoading} wasmLoading The method of loading WebAssembly Modules.
* @param {OutputModule} module Output javascript files as module source type.
*/
constructor(chunkLoading, crossOriginLoading, wasmLoading, module) {
this._chunkLoading = chunkLoading;
this._crossOriginLoading = crossOriginLoading;
this._wasmLoading = wasmLoading;
this._module = module;
}
Expand Down Expand Up @@ -292,11 +302,16 @@ class WorkerPlugin {
entryOptions: {
chunkLoading: this._chunkLoading,
wasmLoading: this._wasmLoading,
crossOriginLoading: this._crossOriginLoading,
...entryOptions
}
});
block.loc = expr.loc;
const dep = new WorkerDependency(url.string, range);
const dep = new WorkerDependency(
url.string,
range,
this._crossOriginLoading
);
dep.loc = expr.loc;
block.addDependency(dep);
parser.state.module.addBlock(block);
Expand Down
43 changes: 2 additions & 41 deletions lib/runtime/AutoPublicPathRuntimeModule.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,6 @@
const RuntimeGlobals = require("../RuntimeGlobals");
const RuntimeModule = require("../RuntimeModule");
const Template = require("../Template");
const JavascriptModulesPlugin = require("../javascript/JavascriptModulesPlugin");
const { getUndoPath } = require("../util/identifier");

class AutoPublicPathRuntimeModule extends RuntimeModule {
constructor() {
Expand All @@ -19,49 +17,12 @@ class AutoPublicPathRuntimeModule extends RuntimeModule {
* @returns {string} runtime code
*/
generate() {
const { compilation } = this;
const { scriptType, importMetaName, path } = compilation.outputOptions;
const chunkName = compilation.getPath(
JavascriptModulesPlugin.getChunkFilenameTemplate(
this.chunk,
compilation.outputOptions
),
{
chunk: this.chunk,
contentHashType: "javascript"
}
);
const undoPath = getUndoPath(chunkName, path, false);

return Template.asString([
"var scriptUrl;",
scriptType === "module"
? `if (typeof ${importMetaName}.url === "string") scriptUrl = ${importMetaName}.url`
: Template.asString([
`if (${RuntimeGlobals.global}.importScripts) scriptUrl = ${RuntimeGlobals.global}.location + "";`,
`var document = ${RuntimeGlobals.global}.document;`,
"if (!scriptUrl && document) {",
Template.indent([
`if (document.currentScript)`,
Template.indent(`scriptUrl = document.currentScript.src`),
"if (!scriptUrl) {",
Template.indent([
'var scripts = document.getElementsByTagName("script");',
"if(scripts.length) scriptUrl = scripts[scripts.length - 1].src"
]),
"}"
]),
"}"
]),
`var scriptUrl = ${RuntimeGlobals.scriptUrl};`,
"// When supporting browsers where an automatic publicPath is not supported you must specify an output.publicPath manually via configuration",
'// or pass an empty string ("") and set the __webpack_public_path__ variable from your code to use your own logic.',
'if (!scriptUrl) throw new Error("Automatic publicPath is not supported in this browser");',
'scriptUrl = scriptUrl.replace(/#.*$/, "").replace(/\\?.*$/, "").replace(/\\/[^\\/]+$/, "/");',
!undoPath
? `${RuntimeGlobals.publicPath} = scriptUrl;`
: `${RuntimeGlobals.publicPath} = scriptUrl + ${JSON.stringify(
undoPath
)};`
`${RuntimeGlobals.publicPath} = scriptUrl;`
]);
}
}
Expand Down
40 changes: 40 additions & 0 deletions lib/runtime/CreateScriptBlobRuntimeModule.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/*
MIT License http://www.opensource.org/licenses/mit-license.php
*/

"use strict";

const RuntimeGlobals = require("../RuntimeGlobals");
const Template = require("../Template");
const HelperRuntimeModule = require("./HelperRuntimeModule");

class CreateScriptBlobRuntimeModule extends HelperRuntimeModule {
constructor() {
super("create script blob");
}

/**
* @returns {string} runtime code
*/
generate() {
const { compilation } = this;
const { runtimeTemplate } = compilation;
const fn = RuntimeGlobals.createScriptBlob;

return Template.asString([
`${fn} = ${runtimeTemplate.basicFunction("code", [
"try {",
Template.indent(
`return URL.createObjectURL(new Blob([code], { type: "application/javascript" }));`
),
`} catch (e) {`,
Template.indent(
`return new URL("data:application/javascript," + encodeURIComponent(code));`
),
"}"
])}`
]);
}
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure it is good solution

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you elaborate?
Is it bad because of the runtime implementation?
Are you concerned about browser support?
Or is it bad because of how it interact with webpack architecture?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Firstly, we need to add blob support to https://github.com/webpack/webpack/blob/main/lib/config/target.js#L81, we don't need to generate extra runtime if developer provide versions.

I think we need to look at this solution a little differently, blob loading can be added not only for workers, any file can be loaded using blob, yes it is most often applicable to workers, but if we need to add blob support we need to add them to any types - js/web wokrers/asset modules and provide option to this.

crossOriginLoading is some misleading, you apply several techniques that can be applied to other things to solve the cross origin problem. For example you can apply base64 here too.

I see what you want to solve and what problem you are experiencing, but I think we should think it over in more detail

@alexander-akait alexander-akait Dec 7, 2021

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We already support asset/raw, so we can have asset/blob and refactor runtime to allow using it on javascript files, so you can loaded bundled file as raw text or as blob (just idea)

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm... That's interesting, I agree that we should try to generalise the use-case.
On the other hand, we don't load the file using Blob - we only use it to create importScripts(filePath) code (and the file itself is loaded later by the runtime).
So this wouldn't work with other file types. Do you suggest adding asset/blob and then emitting the "importScripts(filePath)" file and importing it using asset/blob?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In some sense it's already possible - I do this here (it could be extracted to a plugin).

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@sokra What do you think about this?

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@sokra bump.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My team is desperate for this to be merged. We need cross-origin workers, and worker-loader is causing all kinds of problems. Is there any way that generalizing custom public paths can be deferred until after this feature is merged? If I can assist in development or testing in any way, I am happy to offer my attention and energy.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@AprilArcus
You can use a shim in the meantime that @piotr-oles created here

Works great for me. Although, I'm in the same boat because I want to have 2 separate builds for varying purposes.
Would be happy to add support where I can. @alexander-akait - is there anyone else that could take a look?


module.exports = CreateScriptBlobRuntimeModule;
Loading