Skip to content

Commit

Permalink
Initial commit!
Browse files Browse the repository at this point in the history
  • Loading branch information
developit committed Jan 9, 2018
0 parents commit 93bdc8b
Show file tree
Hide file tree
Showing 5 changed files with 200 additions and 0 deletions.
15 changes: 15 additions & 0 deletions .editorconfig
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
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
node_modules
dist
build
.DS_Store
package-lock.json
48 changes: 48 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
<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"><img src="https://img.shields.io/npm/v/workerize.svg?style=flat" alt="npm"></a> <a href="https://travis-ci.org/developit/workerize"><img src="https://travis-ci.org/developit/workerize.svg?branch=master" alt="travis"></a>
</p>

# Workerize

**NOTE:** If you're using Webpack, try [workerize-loader](https://github.com/developit/workerize-loader).

> Moves a module 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


## Install

```sh
npm install --save workerize
```


### Usage

**worker.js**:

```js
let worker = workerize(`
export function add(a, b) {
// block for half a second to demonstrate asynchronicity
let start = Date.now();
while (Date.now()-start < 250);
return a + b;
}
`);

(async () => {
console.log('3 + 9 = ', await worker.add(3, 9));
console.log('1 + 2 = ', await worker.add(1, 2));
})();
```

### License

[MIT License](LICENSE.md) © [Jason Miller](https://jasonformat.com)
18 changes: 18 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"name": "workerize",
"version": "0.1.0",
"description": "Run a module into a Web Worker.",
"main": "dist/workerize.js",
"module": "src/index.js",
"scripts": {
"build": "microbundle",
"prepublishOnly": "npm run build",
"test": "echo \"Error: no test specified\" && exit 0"
},
"keywords": [],
"author": "Jason Miller <jason@developit.ca> (http://jasonformat.com)",
"license": "ISC",
"devDependencies": {
"microbundle": "^0.2.4"
}
}
114 changes: 114 additions & 0 deletions src/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@

/** TODO:
* - pooling (+ load balancing by tracking # of open calls)
* - queueing (worth it? sortof free via postMessage already)
*
* @example
* let worker = workerize(`
* export function add(a, b) {
* // block for half a second to demonstrate asynchronicity
* let start = Date.now();
* while (Date.now()-start < 250);
* return a + b;
* }
* `);
* (async () => {
* console.log('3 + 9 = ', await worker.add(3, 9));
* console.log('1 + 2 = ', await worker.add(1, 2));
* })();
*/


export default function workerize(code) {
let exports = {};
let exportsObjName = `__EXPORTS_${Math.random().toString().substring(2)}__`;
if (typeof code==='function') code = `(${toCode(code)})(${exportsObjName})`;
code = toCjs(code, exportsObjName, exports);
code += `\n(${toCode(setup)})(self, ${exportsObjName}, {})`;
let blob = new Blob([code], {
type: 'application/javascript'
}),
url = URL.createObjectURL(blob),
worker = new Worker(url),
counter = 0,
callbacks = {};
worker.kill = signal => {
worker.postMessage({ type: 'KILL', signal });
setTimeout(worker.terminate);
};
let term = worker.terminate;
worker.terminate = () => {
URL.revokeObjectURL(url);
worker.terminate();
};
worker.rpcMethods = {};
function setup(ctx, rpcMethods, callbacks) {
/*
ctx.expose = (methods, replace) => {
if (typeof methods==='string') {
rpcMethods[methods] = replace;
}
else {
if (replace===true) rpcMethods = {};
Object.assign(rpcMethods, methods);
}
};
*/
ctx.addEventListener('message', ({ data }) => {
if (data.type==='RPC') {
let id = data.id;
if (id!=null) {
if (data.method) {
let method = rpcMethods[data.method];
if (method==null) {
ctx.postMessage({ type: 'RPC', id, error: 'NO_SUCH_METHOD' });
}
else {
Promise.resolve()
.then( () => method.apply(null, data.params) )
.then( result => { ctx.postMessage({ type: 'RPC', id, result }); })
.catch( error => { ctx.postMessage({ type: 'RPC', id, error }); });
}
}
else {
let callback = callbacks[id];
if (callback==null) throw Error(`Unknown callback ${id}`);
delete callbacks[id];
if (data.error) callback.reject(Error(data.error));
else callback.resolve(data.result);
}
}
}
});
}
setup(worker, worker.rpcMethods, callbacks);
worker.call = (method, params) => new Promise( (resolve, reject) => {
let id = `rpc${++counter}`;
callbacks[id] = { method, resolve, reject };
worker.postMessage({ type: 'RPC', id, method, params });
});
for (let i in exports) {
if (exports.hasOwnProperty(i) && !(i in worker)) {
worker[i] = (...args) => worker.call(i, args);
}
}
return worker;
}

function toCode(func) {
return Function.prototype.toString.call(func);

This comment has been minimized.

Copy link
@vutran

vutran Jan 11, 2018

Is there a difference in calling Function.prototype.toString.call(func) vs. func.toString()?

}

function toCjs(code, exportsObjName, exports) {
exportsObjName = exportsObjName || 'exports';
exports = exports || {};
code = code.replace(/^(\s*)export\s+default\s+/m, (s, before) => {
exports.default = true;
return `${before}${exportsObjName}.default = `;
});
code = code.replace(/^(\s*)export\s+(function|const|let|var)(\s+)([a-zA-Z$_][a-zA-Z0-9$_]*)/m, (s, before, type, ws, name) => {
exports[name] = true;
return `${before}${exportsObjName}.${name} = ${type}${ws}${name}`;
});
return `var ${exportsObjName} = {};\n${code}\n${exportsObjName};`;
}

0 comments on commit 93bdc8b

Please sign in to comment.