diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..d579f75 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,6 @@ +language: node_js +node_js: + - "8" + - "10" +git: + depth: 5 \ No newline at end of file diff --git a/LICENSE.md b/LICENSE.md deleted file mode 100644 index 44f15d9..0000000 --- a/LICENSE.md +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2018 Jason Miller - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/README.md b/README.md index b61a097..a1a1cae 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ - If exported module methods are already async, signature is unchanged - Supports synchronous and asynchronous worker functions - Works beautifully with async/await -- Just **900 bytes** of gzipped ES3 +- Just **800 bytes** of gzipped ES3 ## Install @@ -33,7 +33,7 @@ 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); + while (Date.now()-start < 500); return a + b; } `); @@ -46,4 +46,4 @@ let worker = workerize(` ### License -[MIT License](LICENSE.md) © [Jason Miller](https://jasonformat.com) +[MIT License](https://oss.ninja/mit/developit/) © [Jason Miller](https://jasonformat.com) diff --git a/demo.html b/demo.html new file mode 100644 index 0000000..7ded2d0 --- /dev/null +++ b/demo.html @@ -0,0 +1,24 @@ + + + + Workerize Demo + + + + + diff --git a/loader.js b/loader.js new file mode 100644 index 0000000..cf3256a --- /dev/null +++ b/loader.js @@ -0,0 +1,9 @@ +try { + module.exports = require('workerize-loader'); +} +catch (e) { + console.warn("Warning: workerize-loader is not installed."); + module.exports = function() { + throw "To use workerize as a loader, you must install workerize-loader."; + } +} \ No newline at end of file diff --git a/package.json b/package.json index 3c7e226..d5e8577 100644 --- a/package.json +++ b/package.json @@ -1,25 +1,40 @@ { "name": "workerize", - "version": "0.1.2", + "version": "0.1.8", "description": "Run a module in a Web Worker.", "main": "dist/workerize.js", - "module": "src/index.js", + "module": "dist/workerize.m.js", + "source": "src/index.js", "repository": "developit/workerize", + "loader": "./loader.js", "scripts": { "build": "microbundle", "prepublishOnly": "npm run build", "release": "npm t && git commit -am $npm_package_version && git tag $npm_package_version && git push && git push --tags && npm publish", "test": "echo \"Error: no test specified\" && exit 0" }, + "eslintConfig": { + "extends": "eslint-config-developit", + "rules": { + "prefer-spread": 0, + "prefer-rest-params": 0 + } + }, + "files": [ + "src", + "dist", + "loader.js" + ], "keywords": [ "worker", "web workers", "threads" ], "author": "Jason Miller (http://jasonformat.com)", - "license": "ISC", + "license": "MIT", "devDependencies": { - "eslint": "^4.15.0", - "microbundle": "^0.2.4" + "eslint": "^4.16.0", + "eslint-config-developit": "^1.1.1", + "microbundle": "^0.4.3" } } diff --git a/src/index.js b/src/index.js index 386495e..75a5b44 100644 --- a/src/index.js +++ b/src/index.js @@ -17,98 +17,75 @@ * console.log('1 + 2 = ', await worker.add(1, 2)); * })(); */ - - -export default function workerize(code) { +export default function workerize(code, options) { 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), + let exportsObjName = `__xpo${Math.random().toString().substring(2)}__`; + if (typeof code==='function') code = `(${Function.prototype.toString.call(code)})(${exportsObjName})`; + code = toCjs(code, exportsObjName, exports) + `\n(${Function.prototype.toString.call(setup)})(self,${exportsObjName},{})`; + let url = URL.createObjectURL(new Blob([code],{ type: 'text/javascript' })), + worker = new Worker(url, options), + term = worker.terminate, + callbacks = {}, counter = 0, - callbacks = {}; + i; worker.kill = signal => { worker.postMessage({ type: 'KILL', signal }); setTimeout(worker.terminate); }; - let term = worker.terminate; worker.terminate = () => { URL.revokeObjectURL(url); - term(); + term.call(worker); }; - 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 }; + callbacks[id] = [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); - } - } + worker.rpcMethods = {}; + setup(worker, worker.rpcMethods, callbacks); + worker.expose = methodName => { + worker[methodName] = function() { + return worker.call(methodName, [].slice.call(arguments)); + }; + }; + for (i in exports) if (!(i in worker)) worker.expose(i); return worker; } -function toCode(func) { - return Function.prototype.toString.call(func); +function setup(ctx, rpcMethods, callbacks) { + ctx.addEventListener('message', ({ data }) => { + let id = data.id; + if (data.type!=='RPC' || id==null) return; + 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( err => { ctx.postMessage({ type: 'RPC', id, error: ''+err }); }); + } + } + else { + let callback = callbacks[id]; + if (callback==null) throw Error(`Unknown callback ${id}`); + delete callbacks[id]; + if (data.error) callback[1](Error(data.error)); + else callback[0](data.result); + } + }); } 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 = `; + 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) => { + 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) => { exports[name] = true; - return `${before}${exportsObjName}.${name} = ${type}${ws}${name}`; + return `${before}${exportsObjName}.${name}=${type}${ws}${name}`; }); - return `var ${exportsObjName} = {};\n${code}\n${exportsObjName};`; + return `var ${exportsObjName}={};\n${code}\n${exportsObjName};`; }