├── test ├── fixture │ ├── secret.js │ └── echo.js └── test.js ├── .gitignore ├── example ├── echo.js └── index.js ├── LICENSE-MIT ├── package.json ├── README.md └── index.js /test/fixture/secret.js: -------------------------------------------------------------------------------- 1 | module.exports = 'secret' 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules/ 2 | /test/out.js 3 | /example/node_modules/ -------------------------------------------------------------------------------- /example/echo.js: -------------------------------------------------------------------------------- 1 | // echo back messages 2 | self.onmessage = function(event) { 3 | self.postMessage(event.data) 4 | } 5 | -------------------------------------------------------------------------------- /test/fixture/echo.js: -------------------------------------------------------------------------------- 1 | var secret = require('./secret') 2 | self.onmessage = function(event) { 3 | self.postMessage(event.data + ':' + secret) 4 | } 5 | -------------------------------------------------------------------------------- /example/index.js: -------------------------------------------------------------------------------- 1 | var workerstream = require('workerstream') 2 | 3 | // inline the worker with the workerify keyword 4 | var echo = workerify './echo.js' 5 | var worker = workerstream(echo) 6 | 7 | // If we get messages from the worker 8 | worker.on('data', function(data) { 9 | msg(data) 10 | }) 11 | 12 | // Write some messages to echo back 13 | worker.write({sending: 'message to worker'}) 14 | worker.write({echo: 'hi'}) 15 | worker.write({yep: 'it worked'}) 16 | 17 | // print helper 18 | function msg(str) { 19 | document.body.innerHTML = document.body.innerHTML + '
\n' + JSON.stringify(str) 20 | } 21 | -------------------------------------------------------------------------------- /test/test.js: -------------------------------------------------------------------------------- 1 | window.URL = (window.URL || window.webkitURL || window.mozURL) 2 | var test = require('tape') 3 | 4 | function testEcho(worker, t) { 5 | worker.onmessage = function(e) { 6 | t.equal(e.data, 'echo:secret') 7 | } 8 | worker.postMessage('echo') 9 | } 10 | 11 | test('workers get inlined and still work', function(t) { 12 | t.plan(1) 13 | testEcho(new Worker('./fixture/echo.js'), t) 14 | }) 15 | 16 | test('with variable', function(t) { 17 | t.plan(1) 18 | var variable = './fixture/echo.js' 19 | testEcho(new Worker(variable), t) 20 | }) 21 | 22 | test('with keyword', function(t) { 23 | t.plan(1) 24 | var keyword = workerify './fixture/echo.js' 25 | testEcho(new Worker(keyword), t) 26 | }) 27 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright (c) 2017 Kyle Robinson Young 2 | 3 | Permission is hereby granted, free of charge, to any person 4 | obtaining a copy of this software and associated documentation 5 | files (the "Software"), to deal in the Software without 6 | restriction, including without limitation the rights to use, 7 | copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the 9 | Software is furnished to do so, subject to the following 10 | conditions: 11 | 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 17 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 19 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "workerify", 3 | "description": "Transform web workers into browserified inline Blobs with browserify.", 4 | "version": "1.1.0", 5 | "homepage": "https://github.com/shama/workerify", 6 | "author": { 7 | "name": "Kyle Robinson Young", 8 | "email": "kyle@dontkry.com", 9 | "url": "http://dontkry.com" 10 | }, 11 | "main": "index.js", 12 | "keywords": [ 13 | "worker", 14 | "browserify", 15 | "transform" 16 | ], 17 | "repository": { 18 | "type": "git", 19 | "url": "git://github.com/shama/workerify.git" 20 | }, 21 | "bugs": { 22 | "url": "https://github.com/shama/workerify/issues" 23 | }, 24 | "license": "MIT", 25 | "engines": { 26 | "node": ">= 0.8.0" 27 | }, 28 | "scripts": { 29 | "test": "browserify test/test.js -t ./ -o test/out.js", 30 | "start": "budo example/index.js -- -t ./" 31 | }, 32 | "files": [ 33 | "LICENSE-MIT", 34 | "index.js" 35 | ], 36 | "dependencies": { 37 | "browserify": "^14.5.0", 38 | "falafel": "^2.1.0", 39 | "jsesc": "^2.5.0", 40 | "through": "^2.3.4" 41 | }, 42 | "devDependencies": { 43 | "budo": "^10.0.3", 44 | "workerstream": "^1.2.1", 45 | "tape": "^4.8.0" 46 | }, 47 | "testling": { 48 | "files": "test/out.js", 49 | "browsers": [ 50 | "ielatest", 51 | "chrome/latest", 52 | "firefoxlatest", 53 | "safari/latest", 54 | "opera/latest", 55 | "iphone/latest", 56 | "ipad/latest", 57 | "android-browser/latest" 58 | ] 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # workerify 2 | 3 | Transform web workers into browserified inline Blobs with browserify. 4 | 5 | [![browser support](https://ci.testling.com/shama/workerify.png)](https://ci.testling.com/shama/workerify) 6 | 7 | ## example 8 | 9 | Your entry point `main.js`: 10 | ```js 11 | var mod = require('module') 12 | var worker = new Worker('worker.js') 13 | ``` 14 | 15 | Your worker entry point `worker.js`: 16 | ```js 17 | self.onmessage = function(e) { 18 | var ab = new Uint8Array(10) 19 | for (var n = 0; n < ab.length; n++) ab[n] = 1 20 | self.postMessage(ab.buffer, [ab.buffer]) 21 | } 22 | ``` 23 | 24 | Browserify with this workerify transform: 25 | ```shell 26 | browserify -t workerify main.js > bundle.js 27 | ``` 28 | 29 | and your `bundle.js` will look like: 30 | ```js 31 | var mod = require('module') 32 | var worker = new Worker(window.URL.createObjectURL(new Blob(['BROWSERIFIED CONTENTS OF worker.js']))); 33 | ``` 34 | 35 | ### further example 36 | Take a look at the [example module](https://github.com/shama/workerify/tree/master/example) for using with [workerstream](https://github.com/maxogden/workerstream). 37 | 38 | ## Modular Workers 39 | The main reason for this is modular workers. 40 | 41 | Let's say you create a module that would like to use web workers. Users would 42 | need to configure the URL to the worker. When your module becomes a dependency 43 | of a dependency and so on, the setup becomes really cumbersome. Especially when 44 | your worker needs to be browserified. 45 | 46 | With this transform you simply `npm install workerify --save` and configure your 47 | module's `package.json` to apply the transform: 48 | 49 | ``` json 50 | { 51 | "name": "mymodule", 52 | "browserify": { 53 | "transform": "workerify" 54 | } 55 | } 56 | ``` 57 | 58 | Now when end users `browserify` your module, anywhere in the dependency tree, it 59 | will browserify and inline the worker. No URLs, no extra build steps and no 60 | additional end user requirements. 61 | 62 | ## Notes 63 | Currently it will transform the following: 64 | 65 | ```js 66 | // String literal 67 | new Worker('./path/to/worker.js') 68 | 69 | // Variable Init Earlier 70 | var myworker = './path/to/worker.js' 71 | new Worker(myworker) 72 | 73 | // Or specify the workerify keyword to browserify a string anywhere 74 | // Useful if you want to inline your worker when working with other libs 75 | var myworker = workerify './path/to/worker.js' 76 | var workerstream = require('workerstream')(myworker) 77 | ``` 78 | 79 | ### Using with coffeescript 80 | 81 | ```shell 82 | browserify file.coffee -t coffeeify -t workerify 83 | ``` 84 | 85 | ## install 86 | 87 | With [npm](https://npmjs.org) do: 88 | 89 | ``` 90 | npm install workerify 91 | ``` 92 | 93 | ## release history 94 | * 1.1.0 - Support for Workers as modules (@moin-qidwai). 95 | * 1.0.0 - Upgrade browserify to 14.0.0 (@runn1ng) and other deps. Prefer window.URL over window.webkitURL. 96 | * 0.3.0 - Upgrade browserify to 3.41.0. Allow worker to be used with watchify (@tmpvar) 97 | * 0.2.3 - support compilation from coffeescript original source file 98 | * 0.2.2 - string-escape dep renamed to jsesc (@mathiasbynens) 99 | * 0.2.1 - Add missing falafel dep and bug fixes (@mikolalysenko) 100 | * 0.2.0 - use falafel and support more formats 101 | * 0.1.0 - initial release 102 | 103 | ## license 104 | Copyright (c) 2017 Kyle Robinson Young
105 | Licensed under the MIT license. 106 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | var browserify = require('browserify') 2 | var through = require('through') 3 | var falafel = require('falafel') 4 | var strescape = require('jsesc') 5 | var path = require('path') 6 | var fs = require('fs') 7 | 8 | module.exports = function(file) { 9 | if (!/\.(js|coffee)$/.test(file)) return through() 10 | var cwd = path.dirname(file) 11 | var data = '' 12 | return through(write, end) 13 | function write(buf) { data += buf } 14 | function end() { 15 | var self = this, i = 0, allDone = false, output, vars = Object.create(null) 16 | 17 | function done() { 18 | i-- 19 | if (allDone && i < 1) { 20 | self.queue(String(output)) 21 | self.queue(null) 22 | } 23 | } 24 | 25 | // Collect vars 26 | falafel(data, {isKeyword:isKeyword}, function(node) { 27 | if (isVarLiteral(node)) { 28 | vars[node.id.name] = node.init.value 29 | } 30 | }) 31 | 32 | // process source 33 | output = falafel(data, {isKeyword:isKeyword}, function(node) { 34 | var filename = false, withWorker = false, asModule = false 35 | if (isWorkerifyKeyword(node)) { 36 | filename = node.argument.value 37 | } else if (isWorker(node)) { 38 | withWorker = true 39 | if (node.arguments[0].type === 'Literal') { 40 | filename = node.arguments[0].value 41 | } 42 | if (node.arguments[0].type === 'Identifier' && vars[node.arguments[0].name]) { 43 | filename = vars[node.arguments[0].name] 44 | } 45 | if(isModuleMode(node)) { 46 | asModule = true; 47 | } 48 | } 49 | // browserify and update node 50 | if (filename !== false) { 51 | i++ 52 | var resolvedFile = resolveEntry(filename, cwd); 53 | 54 | self.emit('file', resolvedFile) 55 | 56 | bfy(resolvedFile, function(err, data) { 57 | node.update(makeBlob(data, withWorker, asModule)) 58 | done() 59 | }) 60 | } 61 | }) 62 | 63 | if (i === 0) { 64 | // no workers found skip 65 | self.queue(data) 66 | self.queue(null) 67 | } else { 68 | // otherwise ready to finish 69 | allDone = true 70 | } 71 | } 72 | } 73 | 74 | function isKeyword(id) { 75 | if (id === 'workerify') return true 76 | } 77 | 78 | function isWorker(node) { 79 | return node.type === 'NewExpression' 80 | && node.callee 81 | && node.callee.type === 'Identifier' 82 | && node.callee.name === 'Worker' 83 | } 84 | 85 | function isModuleMode(node) { 86 | return node.arguments.length > 1 87 | && node.arguments[1].type === 'ObjectExpression' 88 | && node.arguments[1].properties 89 | && node.arguments[1].properties.length > 0 90 | && node.arguments[1].properties[0].key.name === 'type' 91 | && node.arguments[1].properties[0].value.value === 'module' 92 | } 93 | 94 | function isVarLiteral(node) { 95 | return node.type === 'VariableDeclarator' 96 | && node.init 97 | && node.init.type === 'Literal' 98 | } 99 | 100 | function isWorkerifyKeyword(node) { 101 | return node.type === 'UnaryExpression' 102 | && node.operator === 'workerify' 103 | && node.argument.type === 'Literal' 104 | } 105 | 106 | function makeBlob(str, withWorker, asModule) { 107 | var src = '(window.URL || window.webkitURL).createObjectURL(new Blob([""],{type:"text/javascript"}))' 108 | if (withWorker === true && asModule) src = 'new Worker(' + src + ', {type: "module"})' 109 | else if(withWorker === true) src = 'new Worker(' + src + ')' 110 | return falafel(src, function(node) { 111 | if (node.type === 'Literal' && node.value === '') { 112 | node.update(strescape(str, {'wrap': true})) 113 | } 114 | }) 115 | } 116 | 117 | function resolveEntry(entry, cwd) { 118 | if (entry.slice(0, 2) !== './') { 119 | entry = './node_modules/' + entry 120 | } 121 | 122 | return path.join(cwd, entry); 123 | } 124 | 125 | // TODO: get process.argv browserify args 126 | function bfy(entry, done) { 127 | var data = '' 128 | var b = browserify(); 129 | 130 | b.add(entry) 131 | var bundle = b.bundle() 132 | bundle.on('data', function(buf) { data += buf }) 133 | bundle.on('end', function() { done(null, data) }) 134 | } 135 | --------------------------------------------------------------------------------