webworkify-webpack
Advanced tools
Comparing version 1.1.8 to 2.0.0
@@ -1,19 +0,15 @@ | ||
var commonWorker = require('./common-js-worker.js'); | ||
import es6Worker from './es6-worker.js'; | ||
import work from '../' | ||
var work = require('../'); | ||
var w1 = work(commonWorker); | ||
var w1 = work(require.resolve('./common-js-worker.js')) | ||
w1.addEventListener('message', function (ev) { | ||
console.log('CommonJS Worker:', ev.data); | ||
}); | ||
console.log('CommonJS Worker:', ev.data) | ||
}) | ||
w1.postMessage(4); // send the worker a message | ||
w1.postMessage(4) // send the worker a message | ||
var w2 = work(es6Worker); | ||
var w2 = work(require.resolve('./es6-worker.js')) | ||
w2.addEventListener('message', function (ev) { | ||
console.log('ES6 Worker', ev.data); | ||
}); | ||
console.log('ES6 Worker', ev.data) | ||
}) | ||
w2.postMessage(4); // send the worker a message | ||
w2.postMessage(4) // send the worker a message |
module.exports = { | ||
entry: { | ||
app: ["./example/main.js"] | ||
}, | ||
output: { | ||
filename: "output.js" | ||
}, | ||
module: { | ||
loaders: [ | ||
{ | ||
test: /\.jsx?$/, | ||
exclude: /(node_modules|bower_components)/, | ||
loader: 'babel', | ||
query: { | ||
presets: ['es2015'] | ||
} | ||
} | ||
] | ||
} | ||
}; | ||
entry: { | ||
app: ['./example/main.js'] | ||
}, | ||
output: { | ||
filename: 'output.js' | ||
}, | ||
module: { | ||
loaders: [ | ||
{ | ||
test: /\.jsx?$/, | ||
exclude: /node_modules/, | ||
loader: 'babel', | ||
query: { | ||
presets: ['es2015'] | ||
} | ||
} | ||
] | ||
} | ||
} |
163
index.js
@@ -1,119 +0,76 @@ | ||
var __webpack_require__ = arguments[2]; | ||
var sources = __webpack_require__.m; | ||
var ENTRY_MODULE = __webpack_require__.s || "0"; | ||
// In webpack 2 the moduleId property is called `i` instead of `id`. | ||
var webworkifyWebpackModuleId = arguments[0].id || arguments[0].i; | ||
var webpackBootstrapFunc = function(modules) { | ||
var installedModules = {}; | ||
function __webpack_require__(moduleId) { | ||
if(installedModules[moduleId]) | ||
return installedModules[moduleId].exports; | ||
var module = installedModules[moduleId] = { | ||
exports: {}, | ||
id: moduleId, | ||
loaded: false | ||
}; | ||
modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); | ||
module.loaded = true; | ||
return module.exports; | ||
function webpackBootstrapFunc (modules) { | ||
var installedModules = {} | ||
function __webpack_require__ (moduleId) { | ||
if (installedModules[moduleId]) return installedModules[moduleId].exports | ||
var module = installedModules[moduleId] = { | ||
exports: {}, | ||
id: moduleId, | ||
loaded: false | ||
} | ||
__webpack_require__.m = modules; | ||
__webpack_require__.c = installedModules; | ||
__webpack_require__.oe = function(err) { throw err; }; | ||
__webpack_require__.p = ""; | ||
var f = __webpack_require__(__webpack_require__.s = entryModule); | ||
return f.default || f; // try to call default if defined to also support babel esmodule exports | ||
modules[moduleId].call(module.exports, module, module.exports, __webpack_require__) | ||
module.loaded = true | ||
return module.exports | ||
} | ||
__webpack_require__.m = modules | ||
__webpack_require__.c = installedModules | ||
__webpack_require__.oe = function (err) { throw err } | ||
__webpack_require__.p = '' | ||
var f = __webpack_require__(__webpack_require__.s = ENTRY_MODULE) | ||
return f.default || f // try to call default if defined to also support babel esmodule exports | ||
} | ||
// http://stackoverflow.com/a/2593661/130442 | ||
function quoteRegExp(str) { | ||
return (str+'').replace(/[.?*+^$[\]\\(){}|-]/g, "\\$&"); | ||
function quoteRegExp (str) { | ||
return (str + '').replace(/[.?*+^$[\]\\(){}|-]/g, '\\$&') | ||
} | ||
module.exports = function (fn, options) { | ||
var moduleWrapperStrings = []; | ||
var potentialFnModuleIds = [ENTRY_MODULE]; | ||
function getModuleDependencies (module) { | ||
var retval = [] | ||
var fnString = module.toString() | ||
var wrapperSignature = fnString.match(/^function\s?\(\w+,\s*\w+,\s*(\w+)\)/) | ||
if (!wrapperSignature) return retval | ||
var sourcesKeys = Object.keys(sources); // when using the CommonChunks plugin, webpacl sometimes storing sources in object instead of array | ||
var webpackRequireName = wrapperSignature[1] | ||
var re = new RegExp(quoteRegExp(webpackRequireName) + '\\([^)]*?(\\d+)\\)', 'g') // additional chars when output.pathinfo is true | ||
var match | ||
while ((match = re.exec(fnString))) { | ||
retval.push(parseInt(match[1], 10)) | ||
} | ||
return retval | ||
} | ||
function getRequiredModules (sources, moduleId) { | ||
var modulesQueue = [moduleId] | ||
var requiredModules = [] | ||
var seenModules = {} | ||
// first, find the modules that required webworkify-webpack, and note their requires so that we can limit the number of potential duplicate matches | ||
// while we're at it, save the string version for use later | ||
var key; | ||
for (var i = 0, l = sourcesKeys.length; i < l; i++) { | ||
var k = sourcesKeys[i]; | ||
if (!sources[k]) { | ||
continue; | ||
} | ||
var wrapperFuncString = sources[k].toString(); | ||
moduleWrapperStrings[k] = wrapperFuncString; | ||
while (modulesQueue.length) { | ||
var moduleToCheck = modulesQueue.pop() | ||
if (seenModules[moduleToCheck] || !sources[moduleToCheck]) continue | ||
seenModules[moduleToCheck] = true | ||
requiredModules.push(moduleToCheck) | ||
var newModules = getModuleDependencies(sources[moduleToCheck]) | ||
modulesQueue = modulesQueue.concat(newModules) | ||
} | ||
// __webpack_require__ is the third argument passed to the wrapper function, but it may have been renamed by minification | ||
var wrapperSignature = wrapperFuncString.match(/^function\s?\(\w+,\s*\w+,\s*(\w+)\)/); | ||
if (!wrapperSignature) { | ||
// no matches means the module doesn't use __webpack_require__, and we can skip it entirely | ||
continue; | ||
} | ||
return requiredModules | ||
} | ||
var webpackRequireName = wrapperSignature[1]; | ||
if (wrapperFuncString.indexOf(webpackRequireName + '(' + webworkifyWebpackModuleId + ')') > -1) { | ||
// find all calls that look like __webpack_require__(\d+), and aren't webworkify-webpack | ||
var re = new RegExp(quoteRegExp(webpackRequireName) + '\\((\\d+)\\)', 'g'); | ||
var match; | ||
while (match = re.exec(wrapperFuncString)) { | ||
if (match[1] != ('' + webworkifyWebpackModuleId)) { | ||
potentialFnModuleIds.push(match[1]); | ||
} | ||
} | ||
} | ||
} | ||
module.exports = function (moduleId, options) { | ||
options = options || {} | ||
var sources = __webpack_modules__ | ||
var fnString = fn.toString() | ||
// FF adds a "use strict"; to the function body | ||
.replace(/"use strict";\n\n/, ''); | ||
var requiredModules = options.all ? Object.keys(sources) : getRequiredModules(sources, moduleId) | ||
var src = '(' + webpackBootstrapFunc.toString().replace('ENTRY_MODULE', moduleId) + ')({' + requiredModules.map(function (id) { return '' + id + ': ' + sources[id].toString() }).join(',') + '})();' | ||
var fnStringNoSpace = fnString | ||
.replace(/^function\s?\((.*)\)(\s?)\{(\n"use strict";\n)?/, 'function($1)$2{'); | ||
var fnStringWithSpace = fnString | ||
.replace(/^function\s?\((.*)\)(\s?)\{(\n"use strict";\n)?/, 'function ($1)$2{'); | ||
var blob = new window.Blob([src], { type: 'text/javascript' }) | ||
if (options.bare) { return blob } | ||
// find the first moduleId from the above list that contains fnString | ||
var key = potentialFnModuleIds.find(function (moduleId) { | ||
var wrapperFuncString = sources[moduleId].toString(); | ||
if (wrapperFuncString.indexOf(fnStringNoSpace) > -1 || wrapperFuncString.indexOf(fnStringWithSpace) > -1) { | ||
var exp = __webpack_require__(moduleId); | ||
var URL = window.URL || window.webkitURL || window.mozURL || window.msURL | ||
// Using babel as a transpiler to use esmodule, the export will always | ||
// be an object with the default export as a property of it. To ensure | ||
// the existing api and babel esmodule exports are both supported we | ||
// check for both | ||
if (!(exp && (exp === fn || exp.default === fn))) { | ||
moduleWrapperStrings[moduleId] = wrapperFuncString.substring(0, wrapperFuncString.length - 1) + '\n' + fnString.match(/function\s?(.+?)\s?\(.*/)[1] + '();\n}'; | ||
} | ||
var workerUrl = URL.createObjectURL(blob) | ||
var worker = new window.Worker(workerUrl) | ||
worker.objectURL = workerUrl | ||
return true; | ||
} | ||
return false; | ||
}); | ||
if (typeof key === 'undefined') { | ||
throw new Error('webworkify-webpack: Could not locate module containing worker function! Make sure you aren\'t using eval sourcemaps and that you pass named functions to webworkify-webpack!'); | ||
} | ||
// window = {}; => https://github.com/borisirota/webworkify-webpack/issues/1 | ||
var src = 'window = {};\n' | ||
+ 'var fn = (' + webpackBootstrapFunc.toString().replace('entryModule', key) + ')([' | ||
+ moduleWrapperStrings.join(',') | ||
+ ']);\n' | ||
+ '(typeof fn === "function") && fn(self);'; // not a function when calling a function from the current scope | ||
var blob = new Blob([src], { type: 'text/javascript' }) | ||
if (options && options.bare) { return blob; } | ||
var URL = window.URL || window.webkitURL || window.mozURL || window.msURL; | ||
var workerUrl = URL.createObjectURL(blob); | ||
var worker = new Worker(workerUrl); | ||
worker.objectURL = workerUrl; | ||
return worker; | ||
}; | ||
return worker | ||
} |
{ | ||
"name": "webworkify-webpack", | ||
"version": "1.1.8", | ||
"description": "launch a web worker that can require() in the browser with webpack", | ||
"version": "2.0.0", | ||
"description": "launch a web worker at runtime that can require() in the browser with webpack", | ||
"main": "index.js", | ||
@@ -37,4 +37,4 @@ "scripts": { | ||
"http-server": "^0.8.5", | ||
"webpack": "^2.0.6-beta" | ||
"webpack": "^2.1.0-beta.25" | ||
} | ||
} |
# webworkify-webpack | ||
launch a web worker that can require() in the browser with webpack | ||
Generates a web worker at runtime from webpack's bundled modules with only the used dependencies. Possible because of webpack's module structure. Just `require.resolve(PATH_TO_MODULE)` the module you want to be the worker's entry point. | ||
inspired by [webworkify](https://github.com/substack/webworkify) | ||
# install | ||
```sh | ||
npm install webworkify-webpack --save | ||
``` | ||
# v2 vs v1 | ||
For v1 go to: [1.1.8](https://github.com/borisirota/webworkify-webpack/tree/1.1.8) | ||
Version 2 uses webpack's api as much as possible vs the hacky implementation of version 1 (I wasn't aware of webpack's api while writing it) which did the job but with some inconsistency between different browsers and some caveats. | ||
In v2: | ||
* no limitation on webpack's devtool - `eval` was forbidden in v1. | ||
* no issues with anonymous functions exported as module.exports - there were issues with anonymous functions in v1. | ||
* `require.resolve` instead of regular `require\import` - The only limitation is using `require.resolve` which means that currently the code using `webworkify-webpack` is coupled to the build tool (webpack - but who uses `webworkify-webpack` already uses webpack) and its not possible to use es2015 modules => checkout out the [future work](#future-work) section. | ||
# webworkify-webpack vs webpack's [worker-loader](https://github.com/webpack/worker-loader) and [target: 'webworker'](https://webpack.github.io/docs/configuration.html#target) | ||
`webworkify-webpack` allows to use one bundle for running same code both on browser and web worker environments. | ||
webpack's current alternatives for web workers are creating bundle which can be run in a web worker environment only and can results in 2 separate files like in the `worker-loader` case (one file for browser and one for web worker => code duplication). | ||
The motivation for `webworkify-webpack` was creating a library which expose to the user the same functionality both in sync and async forms. | ||
I wanted to keep one bundle in order to reduce complexity of using external library to the minimum and make bundle size as minimal as possible when using external library which supports both sync and async functionality (without code duplication). | ||
Since webpack's solutions for web workers are being constructed at compile time, the added value is that its possible to use dev tools like `hmr` (at least when using `target: 'webworker'`) which isn't possible with `webworkify-webpack`. | ||
In addition, regular `js` syntax is being used without the need to use `require.resolve` as in the `webworkify-webpack` case => checkout out the [future work](#future-work) section. | ||
# methods | ||
```js | ||
import work from 'webworkify-webpack' | ||
``` | ||
## let w = work(require.resolve(modulePath) [, options]) | ||
Return a new | ||
[web worker](https://developer.mozilla.org/en-US/docs/Web/API/Worker) | ||
from the module at `modulePath`. | ||
The file at `modulePath` should export its worker code in `module.exports` as a | ||
function that will be run with no arguments. | ||
Note that all the code outside of the `module.exports` function will be run in | ||
the main thread too so don't put any computationally intensive code in that | ||
part. It is necessary for the main code to `require()` the worker code to fetch | ||
the module reference and load `modulePath`'s dependency graph into the bundle | ||
output. | ||
### options | ||
- all - bundle all the dependencies in the web worker and not only the used ones. can be useful in edge cases that I'm not aware of when the used dependencies aren't being resolved as expected due to the runtime regex checking mechanism or just to avoid additional work at runtime to traverse the dependencies tree. | ||
- bare - the return value will be the blob constructed with the worker's code and not the web worker itself. | ||
# example | ||
@@ -14,3 +67,3 @@ | ||
let w = work(require('./worker.js')); | ||
let w = work(require.resolve('./worker.js')); | ||
w.addEventListener('message', event => { | ||
@@ -29,6 +82,5 @@ console.log(event.data); | ||
module.exports = function worker (self) { // use named function instead of anonymous to prevent possible issues (check the second caveat) | ||
module.exports = function worker (self) { | ||
self.addEventListener('message', (event) => { | ||
const startNum = parseInt(event.data); // ev.data=4 from main.js | ||
setInterval(() => { | ||
@@ -56,38 +108,16 @@ const r = startNum / Math.random() - 1; | ||
# methods | ||
# future work | ||
```js | ||
import work from 'webworkify-webpack' | ||
``` | ||
The goal is to make `webworkify-webpack` be based on webpack's api only. I'm not sure how to accomplish it since I never wrote a webpack plugin (is it possible other way?) so I'm asking for help :) | ||
## let w = work(require(modulePath)) | ||
Points of view: | ||
Return a new | ||
[web worker](https://developer.mozilla.org/en-US/docs/Web/API/Worker) | ||
from the module at `modulePath`. | ||
1. [webpackBootstrapFunc](https://github.com/borisirota/webworkify-webpack/blob/master/index.js#L1) - should be taken from webpack's source. | ||
2. ability to use regular module import\require (not `require.resolve`) but still passing the module id to 'webworkify-webpack'. | ||
3. ability to know all specific's module dependencies in compile time so there is no need to traverse the dependencies tree in runtime with regular expressions (when uglifying the code the web worker's bundle can include more dependencies than only the used ones because regular expressions nature). | ||
4. if there is going to be build in compile time, what about hmr as dev tool ? | ||
5. is the ability 'webworkify-webpack' provides should be part of webpack core as another form of web workers support or should it remain as external module ? | ||
The file at `modulePath` should export its worker code in `module.exports` as a | ||
function that will be run with no arguments. | ||
Note that all the code outside of the `module.exports` function will be run in | ||
the main thread too so don't put any computationally intensive code in that | ||
part. It is necessary for the main code to `require()` the worker code to fetch | ||
the module reference and load `modulePath`'s dependency graph into the bundle | ||
output. | ||
# install | ||
With [npm](https://npmjs.org) do: | ||
```sh | ||
npm install webworkify-webpack --save | ||
``` | ||
# caveats | ||
* While developing make sure not to set webpack's devtool option to be with some eval configuration because from 1.1.0 version it won't work - check [#7](https://github.com/borisirota/webworkify-webpack/pull/7) | ||
* When passing anonymous functions to webworkify-webpack, naming them can prevent potential issues - [#9](https://github.com/borisirota/webworkify-webpack/issues/9), [#10](https://github.com/borisirota/webworkify-webpack/issues/10) | ||
# license | ||
MIT |
11336
121
114