diff --git a/eslint.config.js b/eslint.config.js index 3b3f0978589..2001584733d 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -401,6 +401,7 @@ module.exports = [ "n/no-unsupported-features/node-builtins": [ "error", { + ignores: ["Blob"], allowExperimental: true } ], diff --git a/lib/runtime/AutoPublicPathRuntimeModule.js b/lib/runtime/AutoPublicPathRuntimeModule.js index 74b40a1e883..0433194fb09 100644 --- a/lib/runtime/AutoPublicPathRuntimeModule.js +++ b/lib/runtime/AutoPublicPathRuntimeModule.js @@ -72,7 +72,7 @@ class AutoPublicPathRuntimeModule extends RuntimeModule { "// 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(/\\/[^\\/]+$/, "/");', + 'scriptUrl = scriptUrl.replace(/^blob:/, "").replace(/#.*$/, "").replace(/\\?.*$/, "").replace(/\\/[^\\/]+$/, "/");', !undoPath ? `${RuntimeGlobals.publicPath} = scriptUrl;` : `${RuntimeGlobals.publicPath} = scriptUrl + ${JSON.stringify( diff --git a/test/ConfigTestCases.template.js b/test/ConfigTestCases.template.js index 475902b09bc..62bd0816cd7 100644 --- a/test/ConfigTestCases.template.js +++ b/test/ConfigTestCases.template.js @@ -470,6 +470,9 @@ const describeCases = config => { baseModuleScope.getComputedStyle = globalContext.getComputedStyle; baseModuleScope.URL = URL; + if (typeof Blob !== "undefined") { + baseModuleScope.Blob = Blob; + } baseModuleScope.Worker = require("./helpers/createFakeWorker")({ outputDirectory diff --git a/test/Stats.test.js b/test/Stats.test.js index a1965e4123c..7ae75f303bb 100644 --- a/test/Stats.test.js +++ b/test/Stats.test.js @@ -190,10 +190,10 @@ describe("Stats", () => { "assets": Array [ Object { "name": "entryB.js", - "size": 3060, + "size": 3081, }, ], - "assetsSize": 3060, + "assetsSize": 3081, "auxiliaryAssets": undefined, "auxiliaryAssetsSize": 0, "childAssets": undefined, @@ -238,10 +238,10 @@ describe("Stats", () => { "info": Object { "javascriptModule": false, "minimized": true, - "size": 3060, + "size": 3081, }, "name": "entryB.js", - "size": 3060, + "size": 3081, "type": "asset", }, Object { diff --git a/test/__snapshots__/StatsTestCases.basictest.js.snap b/test/__snapshots__/StatsTestCases.basictest.js.snap index a2971b70183..8a0ad992bcd 100644 --- a/test/__snapshots__/StatsTestCases.basictest.js.snap +++ b/test/__snapshots__/StatsTestCases.basictest.js.snap @@ -2298,7 +2298,7 @@ runtime modules X KiB webpack/runtime/load script X KiB {792} [code generated] [no exports] [used exports unknown] - webpack/runtime/publicPath X bytes {792} [code generated] + webpack/runtime/publicPath X KiB {792} [code generated] [no exports] [used exports unknown] cacheable modules X bytes @@ -2588,7 +2588,7 @@ chunk {792} (runtime: main) main.js (main) X bytes (javascript) X KiB (runtime) webpack/runtime/load script X KiB {792} [code generated] [no exports] [used exports unknown] - webpack/runtime/publicPath X bytes {792} [code generated] + webpack/runtime/publicPath X KiB {792} [code generated] [no exports] [used exports unknown] cacheable modules X bytes diff --git a/test/configCases/worker/blob/index.js b/test/configCases/worker/blob/index.js new file mode 100644 index 00000000000..c004e7b6561 --- /dev/null +++ b/test/configCases/worker/blob/index.js @@ -0,0 +1,14 @@ +import { default as Worker } from './worker-wrapper'; + +it("should allow to load chunk in blob", async () => { + const worker = new Worker(new URL('./worker.js', import.meta.url)).getWorker(); + worker.postMessage("ok"); + const result = await new Promise(resolve => { + worker.worker.on("message", data => { + resolve(data); + }); + }); + expect(result).toBe("data: 3, protocol: blob:, thanks"); + await worker.terminate(); +}); + diff --git a/test/configCases/worker/blob/module.js b/test/configCases/worker/blob/module.js new file mode 100644 index 00000000000..c17afd446da --- /dev/null +++ b/test/configCases/worker/blob/module.js @@ -0,0 +1,3 @@ +export function sum(a, b) { + return a + b; +} diff --git a/test/configCases/worker/blob/test.filter.js b/test/configCases/worker/blob/test.filter.js new file mode 100644 index 00000000000..77b40315781 --- /dev/null +++ b/test/configCases/worker/blob/test.filter.js @@ -0,0 +1,6 @@ +const supportsWorker = require("../../../helpers/supportsWorker"); +const supportsBlob = require("../../../helpers/supportsBlob"); + +module.exports = function (config) { + return supportsWorker() && supportsBlob(); +}; diff --git a/test/configCases/worker/blob/webpack.config.js b/test/configCases/worker/blob/webpack.config.js new file mode 100644 index 00000000000..03c779ee0af --- /dev/null +++ b/test/configCases/worker/blob/webpack.config.js @@ -0,0 +1,4 @@ +/** @type {import("../../../../").Configuration} */ +module.exports = { + target: "web" +}; diff --git a/test/configCases/worker/blob/worker-wrapper.js b/test/configCases/worker/blob/worker-wrapper.js new file mode 100644 index 00000000000..cb16fed0544 --- /dev/null +++ b/test/configCases/worker/blob/worker-wrapper.js @@ -0,0 +1,17 @@ +export default class MyWorker { + _worker; + + constructor(url) { + const objectURL = URL.createObjectURL( + new Blob([`importScripts(${JSON.stringify(url.toString())});`], { + type: 'application/javascript' + }) + ); + this._worker = new Worker(objectURL, { originalURL: url }); + URL.revokeObjectURL(objectURL); + } + + getWorker() { + return this._worker; + } +} diff --git a/test/configCases/worker/blob/worker.js b/test/configCases/worker/blob/worker.js new file mode 100644 index 00000000000..4657c5748ac --- /dev/null +++ b/test/configCases/worker/blob/worker.js @@ -0,0 +1,6 @@ +onmessage = async event => { + const { sum } = await import("./module"); + const protocol = self.location.protocol; + parentPort.postMessage(`data: ${sum(1, 2)}, protocol: ${protocol}, thanks`); +}; + diff --git a/test/helpers/createFakeWorker.js b/test/helpers/createFakeWorker.js index a0ebc24c928..bb7f81cf346 100644 --- a/test/helpers/createFakeWorker.js +++ b/test/helpers/createFakeWorker.js @@ -3,11 +3,10 @@ const path = require("path"); module.exports = ({ outputDirectory }) => class Worker { constructor(resource, options = {}) { - expect(resource).toBeInstanceOf(URL); - const isFileURL = /^file:/i.test(resource); + const isBlobURL = /^blob:/i.test(resource); - if (!isFileURL) { + if (!isFileURL && !isBlobURL) { expect(resource.origin).toBe("https://test.cases"); expect(resource.pathname.startsWith("/path/")).toBe(true); } @@ -15,7 +14,12 @@ module.exports = ({ outputDirectory }) => this.url = resource; const file = isFileURL ? resource - : path.resolve(outputDirectory, resource.pathname.slice(6)); + : path.resolve( + outputDirectory, + isBlobURL + ? options.originalURL.pathname.slice(6) + : resource.pathname.slice(6) + ); const workerBootstrap = ` const { parentPort } = require("worker_threads"); @@ -24,7 +28,11 @@ const path = require("path"); const fs = require("fs"); global.self = global; self.URL = URL; -self.location = new URL(${JSON.stringify(resource.toString())}); +self.location = new URL(${JSON.stringify( + isBlobURL + ? resource.toString().replace("nodedata:", "https://test.cases/path/") + : resource.toString() + )}); const urlToPath = url => { if (/^file:/i.test(url)) return fileURLToPath(url); if (url.startsWith("https://test.cases/path/")) url = url.slice(24); diff --git a/test/helpers/supportsBlob.js b/test/helpers/supportsBlob.js new file mode 100644 index 00000000000..4131935515a --- /dev/null +++ b/test/helpers/supportsBlob.js @@ -0,0 +1,7 @@ +module.exports = function supportsWebAssembly() { + try { + return typeof Blob !== "undefined"; + } catch (_err) { + return false; + } +};