Skip to content
This repository was archived by the owner on Mar 4, 2025. It is now read-only.

Commit dc9a7d4

Browse files
committed
Initial release!
0 parents  commit dc9a7d4

File tree

5 files changed

+195
-0
lines changed

5 files changed

+195
-0
lines changed

.gitignore

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
node_modules
2+
dist
3+
package-lock.json
4+
.DS_Store

README.md

+47
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
# jsdom-worker
2+
3+
This is an experimental implementation of the Web Worker API (specifically Dedicated Worker) for JSDOM.
4+
5+
It does not currently do any real threading, rather it implements the `Worker` interface but all work is done in the current thread. `jsdom-worker` runs wherever JSDOM runs, and does not require Node.
6+
7+
It supports both "inline" _(created via Blob)_ and standard _(loaded via URL)_ workers.
8+
9+
> **Hot Take:** this module likely works in the browser, where it could act as a simple inline worker "poorlyfill".
10+
11+
## Why?
12+
13+
Jest uses a JSDOM environment by default, which means it doesn't support Workers. This means it is impossible to test code that requires both NodeJS functionality _and_ Web Workers. `jsdom-worker` implements enough of the Worker spec that it is now possible to do so.
14+
15+
## Installation
16+
17+
`npm i jsdom-worker`
18+
19+
## Example
20+
21+
```js
22+
import 'jsdom-global/register'
23+
import 'jsdom-worker'
24+
25+
let code = `onmessage = e => postMessage(e.data*2)`
26+
let worker = new Worker(URL.createObjectURL(new Blob([code])))
27+
worker.onmessage = console.log
28+
worker.postMessage(5) // 10
29+
```
30+
31+
## Usage with Jest
32+
33+
For single tests, simply add `import 'jsdom-worker'` to your module.
34+
35+
Otherwise, add it via the [setupFiles](https://facebook.github.io/jest/docs/en/configuration.html#setupfiles-array) Jest config option:
36+
37+
```js
38+
{
39+
"setupFiles": [
40+
"jsdom-worker"
41+
]
42+
}
43+
```
44+
45+
## License
46+
47+
[MIT License](https://oss.ninja/mit/developit)

package.json

+44
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
{
2+
"name": "jsdom-worker",
3+
"version": "0.1.0",
4+
"description": "Experimental Web Worker API implementation for JSDOM.",
5+
"main": "dist/jsdom-inline-worker.js",
6+
"module": "dist/jsdom-inline-worker.m.js",
7+
"scripts": {
8+
"prepare": "microbundle --external all",
9+
"test": "eslint src test && npm run -s prepare && jest"
10+
},
11+
"babel": {
12+
"presets": [
13+
"env"
14+
]
15+
},
16+
"keywords": [
17+
"jsdom",
18+
"web worker"
19+
],
20+
"eslintConfig": {
21+
"extends": "eslint-config-developit"
22+
},
23+
"author": "Jason Miller <[email protected]> (http://jasonformat.com)",
24+
"license": "MIT",
25+
"files": [
26+
"dist"
27+
],
28+
"devDependencies": {
29+
"babel-jest": "^22.1.0",
30+
"babel-preset-env": "^1.6.1",
31+
"eslint": "^4.16.0",
32+
"eslint-config-developit": "^1.1.1",
33+
"jest": "^22.1.4",
34+
"microbundle": "^0.4.3",
35+
"node-fetch": "^1.7.3"
36+
},
37+
"peerDependencies": {
38+
"node-fetch": "*"
39+
},
40+
"dependencies": {
41+
"mitt": "^1.1.3",
42+
"uuid-v4": "^0.1.0"
43+
}
44+
}

src/index.js

+86
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
import mitt from 'mitt';
2+
import uuid from 'uuid-v4';
3+
import fetch, { Response } from 'node-fetch';
4+
5+
if (!global.URL) global.URL = {};
6+
if (!global.URL.$$objects) {
7+
global.URL.$$objects = new Map();
8+
global.URL.createObjectURL = blob => {
9+
let id = uuid();
10+
global.URL.$$objects[id] = blob;
11+
return `blob:http://localhost/${id}`;
12+
};
13+
14+
let oldFetch = global.fetch || fetch;
15+
global.fetch = function(url, opts) {
16+
if (url.match(/^blob:/)) {
17+
return new Promise( (resolve, reject) => {
18+
let fr = new FileReader();
19+
fr.onload = () => {
20+
let Res = global.Response || Response;
21+
resolve(new Res(fr.result, { status: 200, statusText: 'OK' }));
22+
};
23+
fr.onerror = () => {
24+
reject(fr.error);
25+
};
26+
let id = url.match(/[^/]+$/)[0];
27+
fr.readAsText(global.URL.$$objects[id]);
28+
});
29+
}
30+
return oldFetch.call(this, url, opts);
31+
};
32+
}
33+
34+
if (!global.document) {
35+
global.document = {};
36+
}
37+
38+
function Event(type) { this.type = type; }
39+
if (!global.document.createEvent) {
40+
global.document.createEvent = function(type) {
41+
let Ctor = global[type] || Event;
42+
return new Ctor(type);
43+
};
44+
}
45+
46+
47+
global.Worker = function Worker(url) {
48+
let messageQueue = [],
49+
inside = mitt(),
50+
outside = mitt(),
51+
scope = {
52+
onmessage: null,
53+
dispatchEvent: inside.emit,
54+
addEventListener: inside.on,
55+
removeEventListener: inside.off,
56+
postMessage(data) {
57+
outside.emit('message', { data });
58+
},
59+
fetch: global.fetch
60+
},
61+
getScopeVar;
62+
inside.on('message', e => { let f = getScopeVar('onmessage'); if (f) f.call(scope, e); });
63+
this.addEventListener = outside.on;
64+
this.removeEventListener = outside.off;
65+
this.dispatchEvent = outside.emit;
66+
outside.on('message', e => { this.onmessage && this.onmessage(e); });
67+
this.postMessage = data => {
68+
if (messageQueue!=null) messageQueue.push(data);
69+
else inside.emit('message', { data });
70+
};
71+
this.terminate = () => {
72+
throw Error('Not Supported');
73+
};
74+
global.fetch(url)
75+
.then( r => r.text() )
76+
.then( code => {
77+
let vars = 'var self=this,global=self';
78+
for (let k in scope) vars += `,${k}=self.${k}`;
79+
// eval('(function() {'+vars+'\n'+code+'\n})').call(scope);
80+
getScopeVar = eval('(function() {'+vars+'\n'+code+'\nreturn function(__){return eval(__)}})').call(scope);
81+
let q = messageQueue;
82+
messageQueue = null;
83+
q.forEach(this.postMessage);
84+
})
85+
.catch( e => { outside.emit('error', e); console.error(e); });
86+
};

test/index.test.js

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import 'jsdom-worker';
2+
3+
const sleep = t => new Promise( r => { setTimeout(r, t); });
4+
5+
describe('jsdom-worker', () => {
6+
it('should work', async () => {
7+
let code = `onmessage = e => { postMessage(e.data*2) }`;
8+
let worker = new Worker(URL.createObjectURL(new Blob([code])));
9+
worker.onmessage = jest.fn();
10+
worker.postMessage(5);
11+
await sleep(10);
12+
expect(worker.onmessage).toHaveBeenCalledWith({ data: 10 });
13+
});
14+
});

0 commit comments

Comments
 (0)