diff --git a/package.json b/package.json index c3393d8..551db24 100644 --- a/package.json +++ b/package.json @@ -41,7 +41,7 @@ "eslint-config-developit": "^1.2.0", "karmatic": "^1.4.0", "microbundle": "^0.12.1", - "webpack": "^4.43.0" + "webpack": "^5.30.0" }, "dependencies": { "loader-utils": "^2.0.0" diff --git a/src/index.js b/src/index.js index db75312..c40f6b2 100644 --- a/src/index.js +++ b/src/index.js @@ -38,25 +38,24 @@ loader.pitch = function(request) { const cb = this.async(); - const filename = loaderUtils.interpolateName(this, `${options.name || '[hash]'}.worker.js`, { - context: options.context || this.rootContext || this.options.context, - regExp: options.regExp - }); + const compilerOptions = this._compiler.options || {}; + + const filename = (options.name || '[fullhash]') + '.worker.js'; const worker = {}; worker.options = { filename, - chunkFilename: `[id].${filename}`, - namedChunkFilename: null + chunkFilename: filename, + publicPath: options.publicPath || compilerOptions.output.publicPath, + globalObject: 'self' }; - const compilerOptions = this._compiler.options || {}; if (compilerOptions.output && compilerOptions.output.globalObject==='window') { console.warn('Warning (workerize-loader): output.globalObject is set to "window". It should be set to "self" or "this" to support HMR in Workers.'); } - worker.compiler = this._compilation.createChildCompiler('worker', worker.options); + worker.compiler = this._compilation.createChildCompiler(`worker ${request}`, worker.options); (new WebWorkerTemplatePlugin(worker.options)).apply(worker.compiler); @@ -83,28 +82,28 @@ loader.pitch = function(request) { }).apply(worker.compiler); } - (new SingleEntryPlugin(this.context, `!!${path.resolve(__dirname, 'rpc-worker-loader.js')}!${request}`, 'main')).apply(worker.compiler); + const bundleName = path.parse(this.resourcePath).name; - const subCache = `subcache ${__dirname} ${request}`; + (new SingleEntryPlugin(this.context, `!!${path.resolve(__dirname, 'rpc-worker-loader.js')}!${request}`, bundleName)).apply(worker.compiler); compilationHook(worker.compiler, (compilation, data) => { - if (compilation.cache) { - if (!compilation.cache[subCache]) compilation.cache[subCache] = {}; - - compilation.cache = compilation.cache[subCache]; - } parseHook(data, (parser, options) => { exportDeclarationHook(parser, expr => { - let decl = expr.declaration || expr, - { compilation, current } = parser.state, - entry = compilation.entries[0].resource; + let decl = expr.declaration || expr; + let { compilation, current } = parser.state; + + let entryModule = + compilation.entries instanceof Map + ? compilation.moduleGraph.getModule( + compilation.entries.get(bundleName).dependencies[0] + ) + : compilation.entries[0]; // only process entry exports - if (current.resource!==entry) return; + if (current.resource!==entryModule.resource) return; let key = current.nameForCondition(); let exports = CACHE[key] || (CACHE[key] = {}); - if (decl.id) { exports[decl.id.name] = true; } @@ -116,6 +115,20 @@ loader.pitch = function(request) { else { console.warn('[workerize] unknown export declaration: ', expr); } + + // This is for Webpack 5: mark the exports as used so it does not get tree-shaken away on production build + if (compilation.moduleGraph) { + const { getEntryRuntime } = require('webpack/lib/util/runtime'); + const { UsageState } = require('webpack'); + const runtime = getEntryRuntime(compilation, bundleName); + for (const exportName of Object.keys(exports)) { + const exportInfo = compilation.moduleGraph.getExportInfo(entryModule, exportName); + exportInfo.setUsed(UsageState.Used, runtime); + exportInfo.canMangleUse = false; + exportInfo.canMangleProvide = false; + } + compilation.moduleGraph.addExtraReason(entryModule, 'used by workerize-loader'); + } }); }); }); @@ -124,9 +137,20 @@ loader.pitch = function(request) { if (err) return cb(err); if (entries[0]) { - worker.file = entries[0].files[0]; - - let key = entries[0].entryModule.nameForCondition(); + worker.file = Array.from(entries[0].files)[0]; + const entryModules = + compilation.chunkGraph && + compilation.chunkGraph.getChunkEntryModulesIterable + ? Array.from( + compilation.chunkGraph.getChunkEntryModulesIterable(entries[0]) + ) + : null; + const entryModule = + entryModules && entryModules.length > 0 + ? entryModules[0] + : entries[0].entryModule; + + let key = entryModule.nameForCondition(); let contents = compilation.assets[worker.file].source(); let exports = Object.keys(CACHE[key] || {}); @@ -151,11 +175,15 @@ loader.pitch = function(request) { workerUrl = `"data:,importScripts('"+location.origin+${workerUrl}+"')"`; } + // workerUrl will be URL.revokeObjectURL() to avoid memory leaks on browsers + // https://github.com/webpack-contrib/worker-loader/issues/208 + return cb(null, ` var addMethods = require(${loaderUtils.stringifyRequest(this, path.resolve(__dirname, 'rpc-wrapper.js'))}) var methods = ${JSON.stringify(exports)} module.exports = function() { var w = new Worker(${workerUrl}, { name: ${JSON.stringify(filename)} }) + URL.revokeObjectURL(${workerUrl}); addMethods(w, methods) ${ options.ready ? 'w.ready = new Promise(function(r) { w.addEventListener("ready", function(){ r(w) }) })' : '' } return w