worker-plugin
Advanced tools
Comparing version 3.2.0 to 4.0.0
@@ -30,3 +30,4 @@ function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; } | ||
var compilerOptions = this._compiler.options || {}; | ||
var pluginOptions = compilerOptions.plugins.find(function (p) { return p[WORKER_PLUGIN_SYMBOL]; }).options; | ||
var plugin = compilerOptions.plugins.find(function (p) { return p[WORKER_PLUGIN_SYMBOL]; }) || {}; | ||
var pluginOptions = plugin && plugin.options || {}; | ||
@@ -82,3 +83,3 @@ if (pluginOptions.globalObject == null && !hasWarned && compilerOptions.output && compilerOptions.output.globalObject === 'window') { | ||
if (err) { return cb(err); } | ||
return cb(null, ("module.exports = __webpack_public_path__ + " + (JSON.stringify(entry)))); | ||
return cb(null, ((options.esModule ? 'export default' : 'module.exports =') + " __webpack_public_path__ + " + (JSON.stringify(entry)))); | ||
}); | ||
@@ -85,0 +86,0 @@ } |
function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; } | ||
var path = _interopDefault(require('path')); | ||
var WORKER_PLUGIN_SYMBOL = _interopDefault(require('./symbol.js')); | ||
var ParserHelpers = _interopDefault(require('webpack/lib/ParserHelpers')); | ||
var WORKER_PLUGIN_SYMBOL = _interopDefault(require('./symbol.js')); | ||
@@ -22,4 +22,9 @@ /** | ||
*/ | ||
var HarmonyImportSpecifierDependency; | ||
try { | ||
HarmonyImportSpecifierDependency = require('webpack/lib/dependencies/HarmonyImportSpecifierDependency'); | ||
} catch (e) {} | ||
var NAME = 'WorkerPlugin'; | ||
var JS_TYPES = ['auto', 'esm', 'dynamic']; | ||
var workerLoader = path.resolve(__dirname, 'loader.js'); | ||
@@ -36,61 +41,97 @@ var WorkerPlugin = function WorkerPlugin(options) { | ||
var workerId = 0; | ||
factory.hooks.parser.for('javascript/auto').tap(NAME, function (parser) { return parse(parser, false); }); | ||
factory.hooks.parser.for('javascript/dynamic').tap(NAME, function (parser) { return parse(parser, false); }); | ||
factory.hooks.parser.for('javascript/esm').tap(NAME, function (parser) { return parse(parser, true); }); | ||
for (var i = 0, list = JS_TYPES; i < list.length; i += 1) { | ||
var type = list[i]; | ||
var parse = function (parser, esModule) { | ||
var handleWorker = function (workerTypeString) { return function (expr) { | ||
var dep = parser.evaluateExpression(expr.arguments[0]); | ||
factory.hooks.parser.for(("javascript/" + type)).tap(NAME, function (parser) { | ||
parser.hooks.new.for('Worker').tap(NAME, function (expr) { | ||
var dep = parser.evaluateExpression(expr.arguments[0]); | ||
if (!dep.isString()) { | ||
parser.state.module.warnings.push({ | ||
message: ("new " + workerTypeString + "() will only be bundled if passed a String.") | ||
}); | ||
return false; | ||
} | ||
if (!dep.isString()) { | ||
parser.state.module.warnings.push({ | ||
message: 'new Worker() will only be bundled if passed a String.' | ||
}); | ||
return false; | ||
} | ||
var optsExpr = expr.arguments[1]; | ||
var hasInitOptions = false; | ||
var typeModuleExpr; | ||
var opts; | ||
var optsExpr = expr.arguments[1]; | ||
var typeModuleExpr; | ||
var opts; | ||
if (optsExpr) { | ||
opts = {}; | ||
if (optsExpr) { | ||
opts = {}; | ||
for (var i = optsExpr.properties.length; i--;) { | ||
var prop = optsExpr.properties[i]; | ||
for (var i = optsExpr.properties.length; i--;) { | ||
var prop = optsExpr.properties[i]; | ||
if (prop.type === 'Property' && !prop.computed && !prop.shorthand && !prop.method) { | ||
opts[prop.key.name] = parser.evaluateExpression(prop.value).string; | ||
if (prop.type === 'Property' && !prop.computed && !prop.shorthand && !prop.method) { | ||
opts[prop.key.name] = parser.evaluateExpression(prop.value).string; | ||
if (prop.key.name === 'type') { | ||
typeModuleExpr = prop; | ||
} | ||
if (prop.key.name === 'type') { | ||
typeModuleExpr = prop; | ||
} else { | ||
hasInitOptions = true; | ||
} | ||
} | ||
} | ||
} | ||
if (!opts || opts.type !== 'module') { | ||
parser.state.module.warnings.push({ | ||
message: ("new Worker() will only be bundled if passed options that include { type: 'module' }." + (opts ? ("\n Received: new Worker(" + (JSON.stringify(dep.string)) + ", " + (JSON.stringify(opts)) + ")") : '')) | ||
}); | ||
return false; | ||
} | ||
if (!opts || opts.type !== 'module') { | ||
parser.state.module.warnings.push({ | ||
message: ("new " + workerTypeString + "() will only be bundled if passed options that include { type: 'module' }." + (opts ? ("\n Received: new " + workerTypeString + "()(" + (JSON.stringify(dep.string)) + ", " + (JSON.stringify(opts)) + ")") : '')) | ||
}); | ||
return false; | ||
} | ||
var loaderOptions = { | ||
name: opts.name || workerId + '' | ||
}; | ||
var req = "require(" + (JSON.stringify(workerLoader + '?' + JSON.stringify(loaderOptions) + '!' + dep.string)) + ")"; | ||
var id = "__webpack__worker__" + (workerId++); | ||
var isStrictModule = esModule || parser.state.buildMeta && parser.state.buildMeta.strictHarmonyModule; // Querystring-encoded loader prefix (faster/cleaner than JSON parameters): | ||
var loaderRequest = workerLoader + "?name=" + (encodeURIComponent(opts.name || workerId)) + (isStrictModule ? '&esModule' : '') + "!" + (dep.string); // Unique ID for the worker URL variable: | ||
var id = "__webpack__worker__" + (workerId++); // .mjs / strict harmony mode | ||
if (isStrictModule) { | ||
var module = parser.state.current; | ||
if (!HarmonyImportSpecifierDependency) { | ||
throw Error((NAME + ": Failed to import HarmonyImportSpecifierDependency. This plugin requires Webpack version 4.")); | ||
} // This is essentially the internals of "prepend an import to the module": | ||
var dependency = new HarmonyImportSpecifierDependency(loaderRequest, module, workerId, // no idea if this actually needs to be unique. 0 seemed to work. safety first? | ||
parser.scope, 'default', id, // this never gets used | ||
expr.arguments[0].range, // replace the usage/callsite with the generated reference: X_IMPORT_0["default"] | ||
true); // avoid serializing the full loader filepath: (this gets prepended to unique suffix) | ||
dependency.userRequest = dep.string; | ||
module.addDependency(dependency); | ||
} else { | ||
// For CommonJS/Auto | ||
var req = "require(" + (JSON.stringify(loaderRequest)) + ")"; | ||
ParserHelpers.toConstantDependency(parser, id)(expr.arguments[0]); | ||
ParserHelpers.addParsedVariableToModule(parser, id, req); | ||
} // update/remove the WorkerInitOptions argument | ||
if (this$1.options.workerType) { | ||
ParserHelpers.toConstantDependency(parser, JSON.stringify(this$1.options.workerType))(typeModuleExpr.value); | ||
} else if (this$1.options.preserveTypeModule !== true) { | ||
ParserHelpers.toConstantDependency(parser, '')(typeModuleExpr); | ||
if (this$1.options.workerType) { | ||
ParserHelpers.toConstantDependency(parser, JSON.stringify(this$1.options.workerType))(typeModuleExpr.value); | ||
} else if (this$1.options.preserveTypeModule !== true) { | ||
if (hasInitOptions) { | ||
// there might be other options - to avoid trailing comma issues, replace the type value with undefined but *leave the key*: | ||
ParserHelpers.toConstantDependency(parser, 'type:undefined')(typeModuleExpr); | ||
} else { | ||
// there was only a `{type}` option, so we can remove the whole second argument: | ||
ParserHelpers.toConstantDependency(parser, '')(optsExpr); | ||
} | ||
} | ||
return ParserHelpers.addParsedVariableToModule(parser, id, req); | ||
}); | ||
}); | ||
} | ||
return true; | ||
}; }; | ||
parser.hooks.new.for('Worker').tap(NAME, handleWorker('Worker')); | ||
if (this$1.options.sharedWorker) { | ||
parser.hooks.new.for('SharedWorker').tap(NAME, handleWorker('SharedWorker')); | ||
} | ||
}; | ||
}); | ||
@@ -97,0 +138,0 @@ }; |
{ | ||
"name": "worker-plugin", | ||
"version": "3.2.0", | ||
"version": "4.0.0", | ||
"description": "Webpack plugin to bundle Workers automagically.", | ||
@@ -8,3 +8,3 @@ "main": "dist/worker-plugin.js", | ||
"scripts": { | ||
"build": "microbundle --inline none --format cjs --no-compress src/*.js", | ||
"build": "microbundle --raw --inline none --format cjs --no-compress src/*.js", | ||
"prepack": "npm run build", | ||
@@ -11,0 +11,0 @@ "dev": "jest --verbose --watchAll", |
@@ -18,5 +18,5 @@ <p align="center"> | ||
The best part? That worker constructor works just fine without bundling turned on too. | ||
The best part? That worker constructor works just fine without bundling turned on, but when bundled the result is **supported in all browsers** that support Web Workers - all the way back to IE 10! | ||
Workers created from Blob & data URLs or without the `{ type:'module' }` option are left unchanged. | ||
Workers with fully dynamic URLs, Blob URLs, data URLs or with no `{ type:'module' }` option are left unchanged. | ||
@@ -66,2 +66,4 @@ ## Installation | ||
> **Note:** in order to ensure WorkerPlugin bundles your worker, make sure you're passing a **string** URL/filename to the Worker constructor. WorkerPlugin cannot bundle workers with dynamic/variable filenames, Blob or data URLs - it will leave them unmodified and print a warning during your build. | ||
## Options | ||
@@ -71,5 +73,5 @@ | ||
### `globalObject` | ||
### `globalObject` _(string | false)_ | ||
WorkerPlugin will warn you if your Webpack configuration has `output.globalObject` set to `window`, since doing so breaks Hot Module Replacement in web workers. | ||
WorkerPlugin will print a warning if your Webpack configuration has `output.globalObject` set to `window`, since doing so breaks Hot Module Replacement in web workers. | ||
@@ -94,5 +96,5 @@ If you're not using HMR and want to disable this warning, pass `globalObject:false`: | ||
### `plugins` | ||
### `plugins` _(array)_ | ||
By default, `WorkerPlugin` doesn't run any of your configured Webpack plugins when bundling worker code - this avoids running things like `html-webpack-plugin` twice. For cases where it's necessary to apply a plugin to Worker code, use the `plugins` option. | ||
By default, WorkerPlugin doesn't run any of your configured Webpack plugins when bundling worker code - this avoids running things like `html-webpack-plugin` twice. For cases where it's necessary to apply a plugin to Worker code, use the `plugins` option. | ||
@@ -122,4 +124,79 @@ Here you can specify the names of plugins to "copy" from your existing Webpack configuration, or provide specific plugins to apply only to worker code: | ||
### `sharedWorker` _(boolean)_ | ||
If set to `true`, this option enables the bundling of [SharedWorker](https://developer.mozilla.org/en-US/docs/Web/API/SharedWorker): | ||
```js | ||
const shared = new SharedWorker('./my-shared-worker.js', { type: 'module' }); | ||
``` | ||
### `preserveTypeModule` _(boolean)_ | ||
### `workerType` _(string)_ | ||
Normally, WorkerPlugin will transform `new Worker('./a.js', { type: 'module' })` to completely remove the `type` option, outputting something like `new Worker('a.worker.js')`. This allows the plugin to compile Module Workers to Classic Workers, which are supported in all browsers. | ||
To instead retain `{type:'module'}` in bundled output, set the `preserveTypeModule` option to `true`: | ||
```js | ||
plugins: [ | ||
new WorkerPlugin({ | ||
preserveTypeModule: true | ||
}) | ||
] | ||
``` | ||
Similarly, if you need to have WorkerPlugin output a specific `type` value, use the `workerType` option to spefify it: | ||
```js | ||
plugins: [ | ||
new WorkerPlugin({ | ||
workerType: 'foo' // note: this isn't a thing! | ||
}) | ||
] | ||
``` | ||
## Loader | ||
At its core, worker-plugin provides two features: parsing and handling of `new Worker()`, and standalone bundling of modules for use in a different JavaScript context. | ||
If all you want is to compile separate bundles for a module, `worker-plugin/loader` provides the bundling functionality of worker-plugin as a standalone Webpack loader. This is useful for generating bundles for use in iframes, Service Workers or Worklets. Applying `worker-plugin/loader` to an import will bundle that module and return its URL: | ||
```js | ||
import workerUrl from 'worker-plugin/loader!./my-worker'; | ||
console.log(workerUrl); // "/0.worker.js" | ||
CSS.paintWorklet.addModule(workerUrl); | ||
``` | ||
Two options are available: | ||
| Option | Type | Description | ||
|---|---|:--| | ||
| `name` | _string_ | Controls the name of the generated chunk.<br>The name is used to generate a URL according to `output.chunkFilename`. | ||
| `esModule` | _boolean_ | Export the URL from an ES Module (`export default url`).<br>The default is CommonJS (`module.exports = url`). | ||
Options can be supplied inline: | ||
```js | ||
import url from 'worker-plugin/loader?name=foo&esModule!./foo'; | ||
``` | ||
... or by setting up a loader alias: | ||
```js | ||
// webpack.config.js to enable this: | ||
// import url from 'worker!./foo'; | ||
{ | ||
resolveLoader: { | ||
alias: { | ||
worker: 'worker-plugin/loader?esModule' | ||
} | ||
} | ||
} | ||
``` | ||
## License | ||
Apache-2.0 |
133
src/index.js
@@ -18,7 +18,10 @@ /** | ||
import path from 'path'; | ||
import WORKER_PLUGIN_SYMBOL from './symbol'; | ||
import ParserHelpers from 'webpack/lib/ParserHelpers'; | ||
import WORKER_PLUGIN_SYMBOL from './symbol'; | ||
let HarmonyImportSpecifierDependency; | ||
try { | ||
HarmonyImportSpecifierDependency = require('webpack/lib/dependencies/HarmonyImportSpecifierDependency'); | ||
} catch (e) {} | ||
const NAME = 'WorkerPlugin'; | ||
const JS_TYPES = ['auto', 'esm', 'dynamic']; | ||
const workerLoader = path.resolve(__dirname, 'loader.js'); | ||
@@ -35,55 +38,105 @@ | ||
let workerId = 0; | ||
for (const type of JS_TYPES) { | ||
factory.hooks.parser.for(`javascript/${type}`).tap(NAME, parser => { | ||
parser.hooks.new.for('Worker').tap(NAME, expr => { | ||
const dep = parser.evaluateExpression(expr.arguments[0]); | ||
factory.hooks.parser.for('javascript/auto').tap(NAME, parser => parse(parser, false)); | ||
factory.hooks.parser.for('javascript/dynamic').tap(NAME, parser => parse(parser, false)); | ||
factory.hooks.parser.for('javascript/esm').tap(NAME, parser => parse(parser, true)); | ||
if (!dep.isString()) { | ||
parser.state.module.warnings.push({ | ||
message: 'new Worker() will only be bundled if passed a String.' | ||
}); | ||
return false; | ||
} | ||
const parse = (parser, esModule) => { | ||
const handleWorker = workerTypeString => expr => { | ||
const dep = parser.evaluateExpression(expr.arguments[0]); | ||
const optsExpr = expr.arguments[1]; | ||
let typeModuleExpr; | ||
let opts; | ||
if (optsExpr) { | ||
opts = {}; | ||
for (let i = optsExpr.properties.length; i--;) { | ||
const prop = optsExpr.properties[i]; | ||
if (prop.type === 'Property' && !prop.computed && !prop.shorthand && !prop.method) { | ||
opts[prop.key.name] = parser.evaluateExpression(prop.value).string; | ||
if (!dep.isString()) { | ||
parser.state.module.warnings.push({ | ||
message: `new ${workerTypeString}() will only be bundled if passed a String.` | ||
}); | ||
return false; | ||
} | ||
if (prop.key.name === 'type') { | ||
typeModuleExpr = prop; | ||
} | ||
const optsExpr = expr.arguments[1]; | ||
let hasInitOptions = false; | ||
let typeModuleExpr; | ||
let opts; | ||
if (optsExpr) { | ||
opts = {}; | ||
for (let i = optsExpr.properties.length; i--;) { | ||
const prop = optsExpr.properties[i]; | ||
if (prop.type === 'Property' && !prop.computed && !prop.shorthand && !prop.method) { | ||
opts[prop.key.name] = parser.evaluateExpression(prop.value).string; | ||
if (prop.key.name === 'type') { | ||
typeModuleExpr = prop; | ||
} else { | ||
hasInitOptions = true; | ||
} | ||
} | ||
} | ||
} | ||
if (!opts || opts.type !== 'module') { | ||
parser.state.module.warnings.push({ | ||
message: `new Worker() will only be bundled if passed options that include { type: 'module' }.${opts ? `\n Received: new Worker(${JSON.stringify(dep.string)}, ${JSON.stringify(opts)})` : ''}` | ||
}); | ||
return false; | ||
if (!opts || opts.type !== 'module') { | ||
parser.state.module.warnings.push({ | ||
message: `new ${workerTypeString}() will only be bundled if passed options that include { type: 'module' }.${opts ? `\n Received: new ${workerTypeString}()(${JSON.stringify(dep.string)}, ${JSON.stringify(opts)})` : ''}` | ||
}); | ||
return false; | ||
} | ||
const isStrictModule = esModule || (parser.state.buildMeta && parser.state.buildMeta.strictHarmonyModule); | ||
// Querystring-encoded loader prefix (faster/cleaner than JSON parameters): | ||
const loaderRequest = `${workerLoader}?name=${encodeURIComponent(opts.name || workerId)}${isStrictModule ? '&esModule' : ''}!${dep.string}`; | ||
// Unique ID for the worker URL variable: | ||
const id = `__webpack__worker__${workerId++}`; | ||
// .mjs / strict harmony mode | ||
if (isStrictModule) { | ||
const module = parser.state.current; | ||
if (!HarmonyImportSpecifierDependency) { | ||
throw Error(`${NAME}: Failed to import HarmonyImportSpecifierDependency. This plugin requires Webpack version 4.`); | ||
} | ||
const loaderOptions = { name: opts.name || workerId + '' }; | ||
const req = `require(${JSON.stringify(workerLoader + '?' + JSON.stringify(loaderOptions) + '!' + dep.string)})`; | ||
const id = `__webpack__worker__${workerId++}`; | ||
// This is essentially the internals of "prepend an import to the module": | ||
const dependency = new HarmonyImportSpecifierDependency( | ||
loaderRequest, | ||
module, | ||
workerId, // no idea if this actually needs to be unique. 0 seemed to work. safety first? | ||
parser.scope, | ||
'default', | ||
id, // this never gets used | ||
expr.arguments[0].range, // replace the usage/callsite with the generated reference: X_IMPORT_0["default"] | ||
true | ||
); | ||
// avoid serializing the full loader filepath: (this gets prepended to unique suffix) | ||
dependency.userRequest = dep.string; | ||
module.addDependency(dependency); | ||
} else { | ||
// For CommonJS/Auto | ||
const req = `require(${JSON.stringify(loaderRequest)})`; | ||
ParserHelpers.toConstantDependency(parser, id)(expr.arguments[0]); | ||
ParserHelpers.addParsedVariableToModule(parser, id, req); | ||
} | ||
if (this.options.workerType) { | ||
ParserHelpers.toConstantDependency(parser, JSON.stringify(this.options.workerType))(typeModuleExpr.value); | ||
} else if (this.options.preserveTypeModule !== true) { | ||
ParserHelpers.toConstantDependency(parser, '')(typeModuleExpr); | ||
// update/remove the WorkerInitOptions argument | ||
if (this.options.workerType) { | ||
ParserHelpers.toConstantDependency(parser, JSON.stringify(this.options.workerType))(typeModuleExpr.value); | ||
} else if (this.options.preserveTypeModule !== true) { | ||
if (hasInitOptions) { | ||
// there might be other options - to avoid trailing comma issues, replace the type value with undefined but *leave the key*: | ||
ParserHelpers.toConstantDependency(parser, 'type:undefined')(typeModuleExpr); | ||
} else { | ||
// there was only a `{type}` option, so we can remove the whole second argument: | ||
ParserHelpers.toConstantDependency(parser, '')(optsExpr); | ||
} | ||
} | ||
return ParserHelpers.addParsedVariableToModule(parser, id, req); | ||
}); | ||
}); | ||
} | ||
return true; | ||
}; | ||
parser.hooks.new.for('Worker').tap(NAME, handleWorker('Worker')); | ||
if (this.options.sharedWorker) { | ||
parser.hooks.new.for('SharedWorker').tap(NAME, handleWorker('SharedWorker')); | ||
} | ||
}; | ||
}); | ||
} | ||
} |
@@ -32,3 +32,4 @@ /** | ||
const pluginOptions = compilerOptions.plugins.find(p => p[WORKER_PLUGIN_SYMBOL]).options; | ||
const plugin = compilerOptions.plugins.find(p => p[WORKER_PLUGIN_SYMBOL]) || {}; | ||
const pluginOptions = plugin && plugin.options || {}; | ||
@@ -82,3 +83,3 @@ if (pluginOptions.globalObject == null && !hasWarned && compilerOptions.output && compilerOptions.output.globalObject === 'window') { | ||
if (err) return cb(err); | ||
return cb(null, `module.exports = __webpack_public_path__ + ${JSON.stringify(entry)}`); | ||
return cb(null, `${options.esModule ? 'export default' : 'module.exports ='} __webpack_public_path__ + ${JSON.stringify(entry)}`); | ||
}); | ||
@@ -85,0 +86,0 @@ }; |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
58369
421
198