workerize-loader
Advanced tools
Comparing version 1.3.0 to 2.0.0
@@ -43,13 +43,11 @@ function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; } | ||
var cb = this.async(); | ||
var filename = loaderUtils.interpolateName(this, (options.name || '[hash]') + ".worker.js", { | ||
context: options.context || this.rootContext || this.options.context, | ||
regExp: options.regExp | ||
}); | ||
var compilerOptions = this._compiler.options || {}; | ||
var filename = (options.name || '[fullhash]') + '.worker.js'; | ||
var worker = {}; | ||
worker.options = { | ||
filename: filename, | ||
chunkFilename: "[id]." + filename, | ||
namedChunkFilename: null | ||
chunkFilename: filename, | ||
publicPath: options.publicPath || compilerOptions.output.publicPath, | ||
globalObject: 'self' | ||
}; | ||
var compilerOptions = this._compiler.options || {}; | ||
@@ -60,3 +58,3 @@ if (compilerOptions.output && compilerOptions.output.globalObject === 'window') { | ||
worker.compiler = this._compilation.createChildCompiler('worker', worker.options); | ||
worker.compiler = this._compilation.createChildCompiler("worker " + request, worker.options); | ||
new WebWorkerTemplatePlugin(worker.options).apply(worker.compiler); | ||
@@ -85,19 +83,14 @@ | ||
new SingleEntryPlugin(this.context, "!!" + path.resolve(__dirname, 'rpc-worker-loader.js') + "!" + request, 'main').apply(worker.compiler); | ||
var subCache = "subcache " + __dirname + " " + request; | ||
var bundleName = path.parse(this.resourcePath).name; | ||
new SingleEntryPlugin(this.context, "!!" + path.resolve(__dirname, 'rpc-worker-loader.js') + "!" + request, bundleName).apply(worker.compiler); | ||
compilationHook(worker.compiler, function (compilation, data) { | ||
if (compilation.cache) { | ||
if (!compilation.cache[subCache]) compilation.cache[subCache] = {}; | ||
compilation.cache = compilation.cache[subCache]; | ||
} | ||
parseHook(data, function (parser, options) { | ||
exportDeclarationHook(parser, function (expr) { | ||
var decl = expr.declaration || expr, | ||
_parser$state = parser.state, | ||
var decl = expr.declaration || expr; | ||
var _parser$state = parser.state, | ||
compilation = _parser$state.compilation, | ||
current = _parser$state.current, | ||
entry = compilation.entries[0].resource; // only process entry exports | ||
current = _parser$state.current; | ||
var 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; | ||
var key = current.nameForCondition(); | ||
@@ -114,2 +107,23 @@ var exports = CACHE[key] || (CACHE[key] = {}); | ||
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) { | ||
var _require = require('webpack/lib/util/runtime'), | ||
getEntryRuntime = _require.getEntryRuntime; | ||
var _require2 = require('webpack'), | ||
UsageState = _require2.UsageState; | ||
var runtime = getEntryRuntime(compilation, bundleName); | ||
for (var _i = 0, _Object$keys = Object.keys(exports); _i < _Object$keys.length; _i++) { | ||
var exportName = _Object$keys[_i]; | ||
var 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'); | ||
} | ||
@@ -123,4 +137,6 @@ }); | ||
if (entries[0]) { | ||
worker.file = entries[0].files[0]; | ||
var key = entries[0].entryModule.nameForCondition(); | ||
worker.file = Array.from(entries[0].files)[0]; | ||
var entryModules = compilation.chunkGraph && compilation.chunkGraph.getChunkEntryModulesIterable ? Array.from(compilation.chunkGraph.getChunkEntryModulesIterable(entries[0])) : null; | ||
var entryModule = entryModules && entryModules.length > 0 ? entryModules[0] : entries[0].entryModule; | ||
var key = entryModule.nameForCondition(); | ||
var contents = compilation.assets[worker.file].source(); | ||
@@ -144,6 +160,8 @@ var exports = Object.keys(CACHE[key] || {}); // console.log('Workerized exports: ', exports.join(', ')); | ||
if (options.import) { | ||
workerUrl = "\"data:,importScripts('\"+location.origin+" + workerUrl + "+\"')\""; | ||
} | ||
workerUrl = "\"data:,importScripts('\"+new URL(" + workerUrl + ",location.origin)+\"')\""; | ||
} // workerUrl will be URL.revokeObjectURL() to avoid memory leaks on browsers | ||
// https://github.com/webpack-contrib/worker-loader/issues/208 | ||
return cb(null, "\n\t\t\t\tvar addMethods = require(" + loaderUtils.stringifyRequest(_this, path.resolve(__dirname, 'rpc-wrapper.js')) + ")\n\t\t\t\tvar methods = " + JSON.stringify(exports) + "\n\t\t\t\tmodule.exports = function() {\n\t\t\t\t\tvar w = new Worker(" + workerUrl + ", { name: " + JSON.stringify(filename) + " })\n\t\t\t\t\taddMethods(w, methods)\n\t\t\t\t\t" + (options.ready ? 'w.ready = new Promise(function(r) { w.addEventListener("ready", function(){ r(w) }) })' : '') + "\n\t\t\t\t\treturn w\n\t\t\t\t}\n\t\t\t"); | ||
return cb(null, "\n\t\t\t\tvar addMethods = require(" + loaderUtils.stringifyRequest(_this, path.resolve(__dirname, 'rpc-wrapper.js')) + ")\n\t\t\t\tvar methods = " + JSON.stringify(exports) + "\n\t\t\t\tmodule.exports = function() {\n\t\t\t\t\tvar w = new Worker(" + workerUrl + ", { name: " + JSON.stringify(filename) + " })\n\t\t\t\t\tURL.revokeObjectURL(" + workerUrl + ");\n\t\t\t\t\taddMethods(w, methods)\n\t\t\t\t\t" + (options.ready ? 'w.ready = new Promise(function(r) { w.addEventListener("ready", function(){ r(w) }) })' : '') + "\n\t\t\t\t\treturn w\n\t\t\t\t}\n\t\t\t"); | ||
} | ||
@@ -150,0 +168,0 @@ |
{ | ||
"name": "workerize-loader", | ||
"version": "1.3.0", | ||
"version": "2.0.0", | ||
"description": "Automatically move a module into a Web Worker (Webpack loader)", | ||
@@ -8,2 +8,3 @@ "main": "dist/index.js", | ||
"scripts": { | ||
"postinstall": "patch-package", | ||
"build": "microbundle --format cjs --no-compress --inline none src/*.js", | ||
@@ -41,7 +42,10 @@ "prepublishOnly": "npm run build", | ||
"devDependencies": { | ||
"@types/jest": "^27.5.1", | ||
"eslint": "^7.2.0", | ||
"eslint-config-developit": "^1.2.0", | ||
"karmatic": "^1.4.0", | ||
"karmatic": "^3.0.0", | ||
"microbundle": "^0.12.1", | ||
"webpack": "^4.43.0" | ||
"patch-package": "^6.4.7", | ||
"puppeteer": "^14.1.1", | ||
"webpack": "^5.30.0" | ||
}, | ||
@@ -48,0 +52,0 @@ "dependencies": { |
148
README.md
@@ -49,2 +49,4 @@ <img src="https://i.imgur.com/HZZG8wr.jpg" width="1358" alt="workerize-loader"> | ||
Workerize options can either be defined in your Webpack configuration, or using Webpack's [syntax for inline loader options](https://webpack.js.org/concepts/loaders/#inline). | ||
#### `inline` | ||
@@ -64,3 +66,5 @@ | ||
``` | ||
or | ||
or | ||
```js | ||
@@ -70,2 +74,83 @@ import worker from 'workerize-loader?inline!./worker' | ||
#### `name` | ||
Type: `String` | ||
Default: `[hash]` | ||
Customize filename generation for worker bundles. Note that a `.worker` suffix will be injected automatically (`{name}.worker.js`). | ||
```js | ||
// webpack.config.js | ||
{ | ||
loader: 'workerize-loader', | ||
options: { name: '[name].[contenthash:8]' } | ||
} | ||
``` | ||
or | ||
```js | ||
import worker from 'workerize-loader?name=[name].[contenthash:8]!./worker' | ||
``` | ||
#### `publicPath` | ||
Type: `String` | ||
Default: based on `output.publicPath` | ||
Workerize uses the configured value of `output.publicPath` from Webpack unless specified here. The value of `publicPath` gets prepended to bundle filenames to get their full URL. It can be a path, or a full URL with host. | ||
```js | ||
// webpack.config.js | ||
{ | ||
loader: 'workerize-loader', | ||
options: { publicPath: '/static/' } | ||
} | ||
``` | ||
#### `ready` | ||
Type: `Boolean` | ||
Default: `false` | ||
If `true`, the imported "workerized" module will include a `ready` property, which is a Promise that resolves once the Worker has been loaded. Note: this is unnecessary in most cases, since worker methods can be called prior to the worker being loaded. | ||
```js | ||
// webpack.config.js | ||
{ | ||
loader: 'workerize-loader', | ||
options: { ready: true } | ||
} | ||
``` | ||
or | ||
```js | ||
import worker from 'workerize-loader?ready!./worker' | ||
let instance = worker() // `new` is optional | ||
await instance.ready | ||
``` | ||
#### `import` | ||
Type: `Boolean` | ||
Default: `false` | ||
When enabled, generated output will create your Workers using a Data URL that loads your code via `importScripts` (eg: `new Worker('data:,importScripts("url")')`). This workaround enables cross-origin script preloading, but Workers are created on an "opaque origin" and cannot access resources on the origin of their host page without CORS enabled. Only enable it if you understand this and specifically need the workaround. | ||
```js | ||
// webpack.config.js | ||
{ | ||
loader: 'workerize-loader', | ||
options: { import: true } | ||
} | ||
``` | ||
or | ||
```js | ||
import worker from 'workerize-loader?import!./worker' | ||
``` | ||
### About [Babel](https://babeljs.io/) | ||
@@ -91,4 +176,23 @@ | ||
### Polyfill Required for IE11 | ||
Workerize-loader supports browsers that support Web Workers - that's IE10+. | ||
However, these browsers require a polyfill in order to use Promises, which Workerize-loader relies on. | ||
It is recommended that the polyfill be installed globally, since Webpack itself also needs Promises to load bundles. | ||
The smallest implementation is the one we recommend installing: | ||
`npm i promise-polyfill` | ||
Then, in the module you are "workerizing", just add it as your first import: | ||
```js | ||
import 'promise-polyfill/src/polyfill'; | ||
``` | ||
All worker code can now use Promises. | ||
### Testing | ||
## Without Webpack | ||
To test a module that is normally imported via `workerize-loader` when not using Webpack, import the module directly in your test: | ||
@@ -103,9 +207,25 @@ | ||
To test modules that rely on workerized imports when not using Webpack, you'll need to dig into your test runner a bit. For Jest, it's possible to define a custom `transform` that emulates workerize-loader on the main thread: | ||
## With Webpack and Jest | ||
In Jest, it's possible to define a custom `transform` that emulates workerize-loader on the main thread. | ||
First, install `babel-jest` and `identity-object-proxy`: | ||
```sh | ||
npm i -D babel-jest identity-object-proxy | ||
``` | ||
Then, add these properties to the `"transform"` and `"moduleNameMapper"` sections of your Jest config (generally located in your `package.json`): | ||
```js | ||
// in your Jest configuration | ||
{ | ||
"transform": { | ||
"workerize-loader(\\?.*)?!(.*)": "<rootDir>/workerize-jest.js" | ||
"jest": { | ||
"moduleNameMapper": { | ||
"workerize-loader(\\?.*)?!(.*)": "identity-obj-proxy" | ||
}, | ||
"transform": { | ||
"workerize-loader(\\?.*)?!(.*)": "<rootDir>/workerize-jest.js", | ||
"^.+\\.[jt]sx?$": "babel-jest", | ||
"^.+\\.[jt]s?$": "babel-jest" | ||
} | ||
} | ||
@@ -115,13 +235,21 @@ } | ||
... then add the `workerize-jest.js` shim to your project: | ||
Finally, create the custom Jest transformer referenced above as a file `workerize-jest.js` in your project's root directory (where the package.json is): | ||
```js | ||
module.exports = { | ||
process(src, filename, config, options) { | ||
return 'module.exports = () => require(' + JSON.stringify(filename.replace(/.+!/,'')) + ')'; | ||
}, | ||
process(src, filename) { | ||
return ` | ||
async function asyncify() { return this.apply(null, arguments); } | ||
module.exports = function() { | ||
const w = require(${JSON.stringify(filename.replace(/^.+!/, ''))}); | ||
const m = {}; | ||
for (let i in w) m[i] = asyncify.bind(w[i]); | ||
return m; | ||
}; | ||
`; | ||
} | ||
}; | ||
``` | ||
Now your tests and any modules they import can use `workerize-loader!` prefixes. | ||
Now your tests and any modules they import can use `workerize-loader!` prefixes, and the imports will be turned into async functions just like they are in Workerize. | ||
@@ -128,0 +256,0 @@ ### Credit |
@@ -41,7 +41,6 @@ import path from 'path'; | ||
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 = {}; | ||
@@ -51,7 +50,7 @@ | ||
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') { | ||
@@ -61,3 +60,3 @@ 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); | ||
@@ -89,24 +88,24 @@ (new WebWorkerTemplatePlugin(worker.options)).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) { | ||
@@ -123,2 +122,16 @@ exports[decl.id.name] = true; | ||
} | ||
// 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'); | ||
} | ||
}); | ||
@@ -132,5 +145,16 @@ }); | ||
if (entries[0]) { | ||
worker.file = entries[0].files[0]; | ||
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 = entries[0].entryModule.nameForCondition(); | ||
let key = entryModule.nameForCondition(); | ||
let contents = compilation.assets[worker.file].source(); | ||
@@ -157,5 +181,8 @@ let exports = Object.keys(CACHE[key] || {}); | ||
if (options.import) { | ||
workerUrl = `"data:,importScripts('"+location.origin+${workerUrl}+"')"`; | ||
workerUrl = `"data:,importScripts('"+new URL(${workerUrl},location.origin)+"')"`; | ||
} | ||
// workerUrl will be URL.revokeObjectURL() to avoid memory leaks on browsers | ||
// https://github.com/webpack-contrib/worker-loader/issues/208 | ||
return cb(null, ` | ||
@@ -166,2 +193,3 @@ var addMethods = require(${loaderUtils.stringifyRequest(this, path.resolve(__dirname, 'rpc-wrapper.js'))}) | ||
var w = new Worker(${workerUrl}, { name: ${JSON.stringify(filename)} }) | ||
URL.revokeObjectURL(${workerUrl}); | ||
addMethods(w, methods) | ||
@@ -168,0 +196,0 @@ ${ options.ready ? 'w.ready = new Promise(function(r) { w.addEventListener("ready", function(){ r(w) }) })' : '' } |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Install scripts
Supply chain riskInstall scripts are run when the package is installed. The majority of malware in npm is hidden in install scripts.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
45274
453
258
8
1