-
Notifications
You must be signed in to change notification settings - Fork 86
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit 12897fa
Showing
11 changed files
with
488 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
root = true | ||
|
||
[*] | ||
indent_style = tab | ||
end_of_line = lf | ||
charset = utf-8 | ||
trim_trailing_whitespace = true | ||
insert_final_newline = true | ||
|
||
[{package.json,.*rc,*.yml}] | ||
indent_style = space | ||
indent_size = 2 | ||
|
||
[*.md] | ||
trim_trailing_whitespace = false |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
node_modules | ||
.DS_Store | ||
.cache | ||
.mocha-puppeteer | ||
*.log | ||
build | ||
dist | ||
package-lock.json | ||
yarn.lock |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
<p align="center"> | ||
<img src="https://i.imgur.com/JLAwk0S.png" width="300" height="300" alt="workerize-loader"> | ||
<br> | ||
<a href="https://www.npmjs.org/package/workerize-loader"><img src="https://img.shields.io/npm/v/workerize-loader.svg?style=flat" alt="npm"></a> <a href="https://travis-ci.org/developit/workerize-loader"><img src="https://travis-ci.org/developit/workerize-loader.svg?branch=master" alt="travis"></a> | ||
</p> | ||
|
||
# workerize-loader | ||
|
||
> A webpack loader that moves a module and its dependencies into a Web Worker, automatically reflecting exported functions as asynchronous proxies. | ||
- Bundles a tiny, purpose-built RPC implementation into your app | ||
- If exported module methods are already async, signature is unchanged | ||
- Supports synchronous and asynchronous worker functions | ||
- Works beautifully with async/await | ||
- Imported value is instantiable, just a decorated `Worker` | ||
|
||
|
||
## Install | ||
|
||
```sh | ||
npm install --save-dev workerize-loader | ||
``` | ||
|
||
|
||
### Usage | ||
|
||
**worker.js**: | ||
|
||
```js | ||
// block for `time` ms, then return the number of loops we could run in that time: | ||
export function expensive(time) { | ||
let start = Date.now(), | ||
count = 0 | ||
while (Date.now() - start < time) count++ | ||
return count | ||
} | ||
``` | ||
|
||
**index.js**: _(our demo)_ | ||
|
||
```js | ||
import worker from 'workerize-loader!./worker' | ||
|
||
let instance = worker() // `new` is optional | ||
|
||
instance.expensive(1000).then( count => { | ||
console.log(`Ran ${count} loops`) | ||
}) | ||
``` | ||
|
||
### License | ||
|
||
[MIT License](LICENSE.md) © [Jason Miller](https://jasonformat.com) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
{ | ||
"name": "workerize-loader", | ||
"version": "1.0.0", | ||
"description": "Automatically move a module into a Web Worker (Webpack loader)", | ||
"main": "dist/index.js", | ||
"scripts": { | ||
"build": "microbundle --inline none --format cjs --no-compress src/*.js", | ||
"prepublishOnly": "npm run build", | ||
"dev": "webpack-dev-server --no-hot --config test/webpack.config.js", | ||
"test": "npm run build && webpack --config test/webpack.config.js && npm run -s mocha", | ||
"mocha": "concurrently -r --kill-others \"serve -p 42421 test/dist\" \"mocha-chrome http://localhost:42421\"" | ||
}, | ||
"eslintConfig": { | ||
"extends": "eslint-config-developit", | ||
"rules": { | ||
"jest/valid-expect": 0, | ||
"no-console": 0 | ||
} | ||
}, | ||
"files": [ | ||
"src", | ||
"dist" | ||
], | ||
"keywords": [ | ||
"webpack", | ||
"loader", | ||
"worker", | ||
"web worker", | ||
"thread", | ||
"workerize" | ||
], | ||
"author": "Jason Miller <[email protected]> (http://jasonformat.com)", | ||
"license": "MIT", | ||
"devDependencies": { | ||
"babel-core": "^6.26.0", | ||
"babel-loader": "^7.1.2", | ||
"babel-preset-env": "^1.6.1", | ||
"chai": "^4.1.2", | ||
"chai-as-promised": "^7.1.1", | ||
"concurrently": "^3.5.1", | ||
"css-loader": "^0.28.8", | ||
"eslint": "^4.14.0", | ||
"eslint-config-developit": "^1.1.1", | ||
"exports-loader": "^0.6.4", | ||
"fast-async": "^6.3.0", | ||
"html-webpack-plugin": "^2.30.1", | ||
"microbundle": "^0.2.4", | ||
"mocha": "^4.1.0", | ||
"mocha-chrome": "^1.0.3", | ||
"mocha-puppeteer": "^0.13.0", | ||
"serve": "^6.4.4", | ||
"style-loader": "^0.19.1", | ||
"webpack": "^3.10.0", | ||
"webpack-dev-server": "^2.10.1" | ||
}, | ||
"dependencies": { | ||
"loader-utils": "^1.1.0" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,113 @@ | ||
import path from 'path'; | ||
import loaderUtils from 'loader-utils'; | ||
|
||
import NodeTargetPlugin from 'webpack/lib/node/NodeTargetPlugin'; | ||
import SingleEntryPlugin from 'webpack/lib/SingleEntryPlugin'; | ||
import WebWorkerTemplatePlugin from 'webpack/lib/webworker/WebWorkerTemplatePlugin'; | ||
|
||
export default function loader() {} | ||
|
||
const CACHE = {}; | ||
|
||
loader.pitch = function(request) { | ||
this.cacheable(false); | ||
|
||
const options = loaderUtils.getOptions(this) || {}; | ||
|
||
const cb = this.async(); | ||
|
||
const filename = loaderUtils.interpolateName(this, `${options.name || '[hash]'}.worker.js`, { | ||
context: options.context || this.options.context, | ||
regExp: options.regExp | ||
}); | ||
|
||
const worker = {}; | ||
|
||
worker.options = { | ||
filename, | ||
chunkFilename: `[id].${filename}`, | ||
namedChunkFilename: null | ||
}; | ||
|
||
worker.compiler = this._compilation.createChildCompiler('worker', worker.options); | ||
|
||
worker.compiler.apply(new WebWorkerTemplatePlugin(worker.options)); | ||
|
||
if (this.target!=='webworker' && this.target!=='web') { | ||
worker.compiler.apply(new NodeTargetPlugin()); | ||
} | ||
|
||
worker.compiler.apply(new SingleEntryPlugin(this.context, `!!${path.resolve(__dirname, 'rpc-worker-loader.js')}!${request}`, 'main')); | ||
|
||
const subCache = `subcache ${__dirname} ${request}`; | ||
|
||
worker.compiler.plugin('compilation', (compilation, data) => { | ||
if (compilation.cache) { | ||
if (!compilation.cache[subCache]) compilation.cache[subCache] = {}; | ||
|
||
compilation.cache = compilation.cache[subCache]; | ||
} | ||
|
||
data.normalModuleFactory.plugin('parser', (parser, options) => { | ||
parser.plugin('export declaration', expr => { | ||
let decl = expr.declaration || expr, | ||
{ compilation, current } = parser.state, | ||
entry = compilation.entries[0].resource; | ||
|
||
// only process entry exports | ||
if (current.resource!==entry) return; | ||
|
||
let exports = compilation.__workerizeExports || (compilation.__workerizeExports = {}); | ||
|
||
if (decl.id) { | ||
exports[decl.id.name] = true; | ||
} | ||
else if (decl.declarations) { | ||
for (let i=0; i<decl.declarations.length; i++) { | ||
exports[decl.declarations[i].id.name] = true; | ||
} | ||
} | ||
else { | ||
console.warn('[workerize] unknown export declaration: ', expr); | ||
} | ||
}); | ||
}); | ||
}); | ||
|
||
worker.compiler.runAsChild((err, entries, compilation) => { | ||
if (err) return cb(err); | ||
|
||
if (entries[0]) { | ||
worker.file = entries[0].files[0]; | ||
|
||
let contents = compilation.assets[worker.file].source(); | ||
let exports = Object.keys(CACHE[worker.file] = compilation.__workerizeExports || CACHE[worker.file] || {}); | ||
|
||
// console.log('Workerized exports: ', exports.join(', ')); | ||
|
||
if (options.inline) { | ||
worker.url = `URL.createObjectURL(new Blob([${JSON.stringify(contents)}]))`; | ||
} | ||
else { | ||
worker.url = `__webpack_public_path__ + ${JSON.stringify(worker.file)}`; | ||
} | ||
|
||
if (options.fallback === false) { | ||
delete this._compilation.assets[worker.file]; | ||
} | ||
|
||
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(${worker.url}, { name: ${JSON.stringify(filename)} }) | ||
addMethods(w, methods) | ||
${ options.ready ? 'w.ready = new Promise(function(r) { w.addEventListener("ready", function(){ r(w) }) })' : '' } | ||
return w | ||
} | ||
`); | ||
} | ||
|
||
return cb(null, null); | ||
}); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
/* global __webpack_exports__ */ | ||
|
||
function workerSetup() { | ||
addEventListener('message', (e) => { | ||
let { type, method, id, params } = e.data, f, p; | ||
if (type==='RPC' && method) { | ||
if ((f = __webpack_exports__[method])) { | ||
p = Promise.resolve().then( () => f.apply(__webpack_exports__, params) ); | ||
} | ||
else { | ||
p = Promise.reject('No such method'); | ||
} | ||
p.then( | ||
result => { | ||
postMessage({ type: 'RPC', id, result }); | ||
}, | ||
error => { | ||
postMessage({ type: 'RPC', id, error }); | ||
}); | ||
} | ||
}); | ||
postMessage({ type: 'RPC', method: 'ready' }); | ||
} | ||
|
||
const workerScript = '\n' + Function.prototype.toString.call(workerSetup).replace(/(^.*\{|\}.*$|\n\s*)/g, ''); | ||
|
||
export default function rpcWorkerLoader(content) { | ||
return content + workerScript; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
export default function addMethods(worker, methods) { | ||
let c = 0; | ||
let callbacks = {}; | ||
worker.addEventListener('message', (e) => { | ||
let d = e.data; | ||
if (d.type!=='RPC') return; | ||
if (d.id) { | ||
let f = callbacks[d.id]; | ||
if (f) { | ||
delete callbacks[d.id]; | ||
if (d.error) f[1](d.error); | ||
else f[0](d.result); | ||
} | ||
} | ||
else { | ||
let evt = document.createEvent('Event'); | ||
evt.initEvent(d.method); | ||
evt.data = d.params; | ||
worker.dispatchEvent(evt); | ||
} | ||
}); | ||
methods.forEach( method => { | ||
worker[method] = (...params) => new Promise( (a, b) => { | ||
let id = ++c; | ||
callbacks[id] = [a, b]; | ||
worker.postMessage({ type: 'RPC', id, method, params }); | ||
}); | ||
}); | ||
} |
Oops, something went wrong.