├── 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 | [](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 |
--------------------------------------------------------------------------------