├── .gitignore ├── .travis.yml ├── loader.js ├── .editorconfig ├── demo.html ├── package.json ├── README.md └── src └── index.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | build 4 | .DS_Store 5 | package-lock.json 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "8" 4 | - "10" 5 | git: 6 | depth: 5 -------------------------------------------------------------------------------- /loader.js: -------------------------------------------------------------------------------- 1 | try { 2 | module.exports = require('workerize-loader'); 3 | } 4 | catch (e) { 5 | console.warn("Warning: workerize-loader is not installed."); 6 | module.exports = function() { 7 | throw "To use workerize as a loader, you must install workerize-loader."; 8 | } 9 | } -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = tab 5 | end_of_line = lf 6 | charset = utf-8 7 | trim_trailing_whitespace = true 8 | insert_final_newline = true 9 | 10 | [{package.json,.*rc,*.yml}] 11 | indent_style = space 12 | indent_size = 2 13 | 14 | [*.md] 15 | trim_trailing_whitespace = false 16 | -------------------------------------------------------------------------------- /demo.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 |
6 |
7 | # Workerize [](https://www.npmjs.org/package/workerize) [](https://travis-ci.org/developit/workerize)
8 |
9 | > Moves a module into a Web Worker, automatically reflecting exported functions as asynchronous proxies.
10 |
11 | - Bundles a tiny, purpose-built RPC implementation into your app
12 | - If exported module methods are already async, signature is unchanged
13 | - Supports synchronous and asynchronous worker functions
14 | - Works beautifully with async/await
15 | - Just **800 bytes** of gzipped ES3
16 |
17 |
18 | ## Install
19 |
20 | ```sh
21 | npm install --save workerize
22 | ```
23 |
24 |
25 | ### Usage
26 |
27 | Pass either a function or a string containing code.
28 |
29 | **worker.js**:
30 |
31 | ```js
32 | let worker = workerize(`
33 | export function add(a, b) {
34 | // block for half a second to demonstrate asynchronicity
35 | let start = Date.now();
36 | while (Date.now()-start < 500);
37 | return a + b;
38 | }
39 | `);
40 |
41 | (async () => {
42 | console.log('3 + 9 = ', await worker.add(3, 9));
43 | console.log('1 + 2 = ', await worker.add(1, 2));
44 | })();
45 | ```
46 |
47 | ### License
48 |
49 | [MIT License](https://oss.ninja/mit/developit/) © [Jason Miller](https://jasonformat.com)
50 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 |
2 | /** TODO:
3 | * - pooling (+ load balancing by tracking # of open calls)
4 | * - queueing (worth it? sortof free via postMessage already)
5 | *
6 | * @example
7 | * let worker = workerize(`
8 | * export function add(a, b) {
9 | * // block for a quarter of a second to demonstrate asynchronicity
10 | * let start = Date.now();
11 | * while (Date.now()-start < 250);
12 | * return a + b;
13 | * }
14 | * `);
15 | * (async () => {
16 | * console.log('3 + 9 = ', await worker.add(3, 9));
17 | * console.log('1 + 2 = ', await worker.add(1, 2));
18 | * })();
19 | */
20 | export default function workerize(code, options) {
21 | let exports = {};
22 | let exportsObjName = `__xpo${Math.random().toString().substring(2)}__`;
23 | if (typeof code==='function') code = `(${Function.prototype.toString.call(code)})(${exportsObjName})`;
24 | code = toCjs(code, exportsObjName, exports) + `\n(${Function.prototype.toString.call(setup)})(self,${exportsObjName},{})`;
25 | let url = URL.createObjectURL(new Blob([code],{ type: 'text/javascript' })),
26 | worker = new Worker(url, options),
27 | term = worker.terminate,
28 | callbacks = {},
29 | counter = 0,
30 | i;
31 | worker.kill = signal => {
32 | worker.postMessage({ type: 'KILL', signal });
33 | setTimeout(worker.terminate);
34 | };
35 | worker.terminate = () => {
36 | URL.revokeObjectURL(url);
37 | term.call(worker);
38 | };
39 | worker.call = (method, params) => new Promise( (resolve, reject) => {
40 | let id = `rpc${++counter}`;
41 | callbacks[id] = [resolve, reject];
42 | worker.postMessage({ type: 'RPC', id, method, params });
43 | });
44 | worker.rpcMethods = {};
45 | setup(worker, worker.rpcMethods, callbacks);
46 | worker.expose = methodName => {
47 | worker[methodName] = function() {
48 | return worker.call(methodName, [].slice.call(arguments));
49 | };
50 | };
51 | for (i in exports) if (!(i in worker)) worker.expose(i);
52 | return worker;
53 | }
54 |
55 | function setup(ctx, rpcMethods, callbacks) {
56 | ctx.addEventListener('message', ({ data }) => {
57 | let id = data.id;
58 | if (data.type!=='RPC' || id==null) return;
59 | if (data.method) {
60 | let method = rpcMethods[data.method];
61 | if (method==null) {
62 | ctx.postMessage({ type: 'RPC', id, error: 'NO_SUCH_METHOD' });
63 | }
64 | else {
65 | Promise.resolve()
66 | .then( () => method.apply(null, data.params) )
67 | .then( result => { ctx.postMessage({ type: 'RPC', id, result }); })
68 | .catch( err => { ctx.postMessage({ type: 'RPC', id, error: ''+err }); });
69 | }
70 | }
71 | else {
72 | let callback = callbacks[id];
73 | if (callback==null) throw Error(`Unknown callback ${id}`);
74 | delete callbacks[id];
75 | if (data.error) callback[1](Error(data.error));
76 | else callback[0](data.result);
77 | }
78 | });
79 | }
80 |
81 | function toCjs(code, exportsObjName, exports) {
82 | code = code.replace(/^(\s*)export\s+default\s+/m, (s, before) => {
83 | exports.default = true;
84 | return `${before}${exportsObjName}.default=`;
85 | });
86 | code = code.replace(/^(\s*)export\s+((?:async\s*)?function(?:\s*\*)?|const|let|var)(\s+)([a-zA-Z$_][a-zA-Z0-9$_]*)/mg, (s, before, type, ws, name) => {
87 | exports[name] = true;
88 | return `${before}${exportsObjName}.${name}=${type}${ws}${name}`;
89 | });
90 | return `var ${exportsObjName}={};\n${code}\n${exportsObjName};`;
91 | }
92 |
--------------------------------------------------------------------------------