Skip to content
This repository was archived by the owner on Sep 9, 2021. It is now read-only.

Commit 3fcc7a2

Browse files
committed
feat: add option crossOrigin
handle cross origin worker drawing inspiration from https://benohead.com/blog/2017/12/06/cross-domain-cross-browser-web-workers/
1 parent 2448e13 commit 3fcc7a2

File tree

9 files changed

+243
-23
lines changed

9 files changed

+243
-23
lines changed

README.md

Lines changed: 40 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -66,14 +66,15 @@ And run `webpack` via your preferred method.
6666

6767
## Options
6868

69-
| Name | Type | Default | Description |
70-
| :-----------------------------------: | :-------------------------: | :-----------------------------: | :-------------------------------------------------------------------------------- |
71-
| **[`worker`](#worker)** | `{String\|Object}` | `Worker` | Allows to set web worker constructor name and options |
72-
| **[`publicPath`](#publicpath)** | `{String\|Function}` | based on `output.publicPath` | specifies the public URL address of the output files when referenced in a browser |
73-
| **[`filename`](#filename)** | `{String\|Function}` | based on `output.filename` | The filename of entry chunks for web workers |
74-
| **[`chunkFilename`](#chunkfilename)** | `{String}` | based on `output.chunkFilename` | The filename of non-entry chunks for web workers |
75-
| **[`inline`](#inline)** | `'no-fallback'\|'fallback'` | `undefined` | Allow to inline the worker as a `BLOB` |
76-
| **[`esModule`](#esmodule)** | `{Boolean}` | `true` | Use ES modules syntax |
69+
| Name | Type | Default | Description |
70+
| :-----------------------------------: | :-------------------------: | :-----------------------------: | :-------------------------------------------------------------------------------------------- |
71+
| **[`worker`](#worker)** | `{String\|Object}` | `Worker` | Allows to set web worker constructor name and options |
72+
| **[`crossOrigin`](#crossorigin)** | `{String}` | `undefined` | Specifies origin and path to serve worker file from in case worker is from a different origin |
73+
| **[`publicPath`](#publicpath)** | `{String\|Function}` | based on `output.publicPath` | specifies the public URL address of the output files when referenced in a browser |
74+
| **[`filename`](#filename)** | `{String\|Function}` | based on `output.filename` | The filename of entry chunks for web workers |
75+
| **[`chunkFilename`](#chunkfilename)** | `{String}` | based on `output.chunkFilename` | The filename of non-entry chunks for web workers |
76+
| **[`inline`](#inline)** | `'no-fallback'\|'fallback'` | `undefined` | Allow to inline the worker as a `BLOB` |
77+
| **[`esModule`](#esmodule)** | `{Boolean}` | `true` | Use ES modules syntax |
7778

7879
### `worker`
7980

@@ -133,6 +134,32 @@ module.exports = {
133134
};
134135
```
135136

137+
### `crossOrigin`
138+
139+
Type: `String`
140+
Default: `undefined`
141+
142+
When worker file must be served from a different domain such as when using a CDN, set this to the domain and path that house the worker.
143+
Note that this should probably be unset during development.
144+
145+
**webpack.config.js**
146+
147+
```js
148+
module.exports = {
149+
module: {
150+
rules: [
151+
{
152+
test: /\.worker\.(c|m)?js$/i,
153+
loader: 'worker-loader',
154+
options: {
155+
crossOrigin: 'https://my-cdn.com/path/',
156+
},
157+
},
158+
],
159+
},
160+
};
161+
```
162+
136163
### `publicPath`
137164

138165
Type: `String|Function`
@@ -500,11 +527,12 @@ Even downloads from the `webpack-dev-server` could be blocked.
500527

501528
There are two workarounds:
502529

503-
Firstly, you can inline the worker as a blob instead of downloading it as an external script via the [`inline`](#inline) parameter
530+
Firstly, you can specifies the CDN domain and path via the [`crossOrigin`](#crossorigin) option
504531

505532
**App.js**
506533

507534
```js
535+
// This will cause the worker to be downloaded from `https://my-cdn.com/abc/file.worker.js`
508536
import Worker from './file.worker.js';
509537
```
510538

@@ -516,19 +544,18 @@ module.exports = {
516544
rules: [
517545
{
518546
loader: 'worker-loader',
519-
options: { inline: 'fallback' },
547+
options: { crossOrigin: 'https://my-cdn.com/abc/' },
520548
},
521549
],
522550
},
523551
};
524552
```
525553

526-
Secondly, you may override the base download URL for your worker script via the [`publicPath`](#publicpath) option
554+
Secondly, you may inline the worker as a blob instead of downloading it as an external script via the [`inline`](#inline) parameter
527555

528556
**App.js**
529557

530558
```js
531-
// This will cause the worker to be downloaded from `/workers/file.worker.js`
532559
import Worker from './file.worker.js';
533560
```
534561

@@ -540,7 +567,7 @@ module.exports = {
540567
rules: [
541568
{
542569
loader: 'worker-loader',
543-
options: { publicPath: '/workers/' },
570+
options: { inline: 'fallback' },
544571
},
545572
],
546573
},

src/index.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,11 +65,13 @@ export function pitch(request) {
6565
const publicPath = options.publicPath
6666
? options.publicPath
6767
: compilerOptions.output.publicPath;
68+
const crossOrigin = options.crossOrigin || false;
6869

6970
workerContext.options = {
7071
filename,
7172
chunkFilename,
7273
publicPath,
74+
crossOrigin,
7375
globalObject: 'self',
7476
};
7577

src/options.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,9 @@
5252
"inline": {
5353
"enum": ["no-fallback", "fallback"]
5454
},
55+
"crossOrigin": {
56+
"type": "string"
57+
},
5558
"esModule": {
5659
"type": "boolean"
5760
}

src/runtime/crossOrigin.js

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/* eslint-env browser */
2+
/* eslint-disable no-undef, no-use-before-define, new-cap */
3+
// initial solution by Benohead's Software Blog https://benohead.com/blog/2017/12/06/cross-domain-cross-browser-web-workers/
4+
5+
module.exports = (workerConstructor, workerOptions, workerUrl) => {
6+
let worker = null;
7+
let blob;
8+
9+
try {
10+
blob = new Blob([`importScripts('${workerUrl}');`], {
11+
type: 'application/javascript',
12+
});
13+
} catch (e) {
14+
const BlobBuilder =
15+
window.BlobBuilder ||
16+
window.WebKitBlobBuilder ||
17+
window.MozBlobBuilder ||
18+
window.MSBlobBuilder;
19+
20+
blobBuilder = new BlobBuilder();
21+
22+
blobBuilder.append(`importScripts('${workerUrl}');`);
23+
24+
blob = blobBuilder.getBlob('application/javascript');
25+
}
26+
27+
const URL = window.URL || window.webkitURL;
28+
const blobUrl = URL.createObjectURL(blob);
29+
worker = new window[workerConstructor](blobUrl, workerOptions);
30+
31+
URL.revokeObjectURL(blobUrl);
32+
33+
return worker;
34+
};

src/utils.js

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,28 @@ ${
7979
)}, ${fallbackWorkerPath});\n}\n`;
8080
}
8181

82+
if (options.crossOrigin) {
83+
const CrossOriginWorkerPath = stringifyRequest(
84+
loaderContext,
85+
`!!${require.resolve('./runtime/crossOrigin.js')}`
86+
);
87+
88+
return `
89+
${
90+
esModule
91+
? `import worker from ${CrossOriginWorkerPath};`
92+
: `var worker = require(${CrossOriginWorkerPath});`
93+
}
94+
95+
${
96+
esModule ? 'export default' : 'module.exports ='
97+
} function() {\n return worker(${JSON.stringify(
98+
workerConstructor
99+
)}, ${JSON.stringify(workerOptions)}, ${JSON.stringify(
100+
options.crossOrigin
101+
)} + ${JSON.stringify(workerFilename)});\n}\n`;
102+
}
103+
82104
return `${
83105
esModule ? 'export default' : 'module.exports ='
84106
} function() {\n return new ${workerConstructor}(__webpack_public_path__ + ${JSON.stringify(
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
// Jest Snapshot v1, https://goo.gl/fbAQLP
2+
3+
exports[`"crossOrigin" option should not work by default: errors 1`] = `Array []`;
4+
5+
exports[`"crossOrigin" option should not work by default: module 1`] = `
6+
"export default function() {
7+
return new Worker(__webpack_public_path__ + \\"test.worker.js\\");
8+
}
9+
"
10+
`;
11+
12+
exports[`"crossOrigin" option should not work by default: result 1`] = `"{\\"postMessage\\":true,\\"onmessage\\":true}"`;
13+
14+
exports[`"crossOrigin" option should not work by default: warnings 1`] = `Array []`;
15+
16+
exports[`"crossOrigin" option should work with crossOrigin enabled and "esModule" with "false" value: errors 1`] = `Array []`;
17+
18+
exports[`"crossOrigin" option should work with crossOrigin enabled and "esModule" with "false" value: result 1`] = `"{\\"postMessage\\":true,\\"onmessage\\":true}"`;
19+
20+
exports[`"crossOrigin" option should work with crossOrigin enabled and "esModule" with "false" value: warnings 1`] = `Array []`;
21+
22+
exports[`"crossOrigin" option should work with crossOrigin enabled and "esModule" with "true" value: errors 1`] = `Array []`;
23+
24+
exports[`"crossOrigin" option should work with crossOrigin enabled and "esModule" with "true" value: result 1`] = `"{\\"postMessage\\":true,\\"onmessage\\":true}"`;
25+
26+
exports[`"crossOrigin" option should work with crossOrigin enabled and "esModule" with "true" value: warnings 1`] = `Array []`;
27+
28+
exports[`"crossOrigin" option should work with crossOrigin enabled: errors 1`] = `Array []`;
29+
30+
exports[`"crossOrigin" option should work with crossOrigin enabled: result 1`] = `"{\\"postMessage\\":true,\\"onmessage\\":true}"`;
31+
32+
exports[`"crossOrigin" option should work with crossOrigin enabled: warnings 1`] = `Array []`;

test/__snapshots__/validate-options.test.js.snap

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -66,49 +66,49 @@ exports[`validate options should throw an error on the "publicPath" option with
6666
exports[`validate options should throw an error on the "unknown" option with "/test/" value 1`] = `
6767
"Invalid options object. Worker Loader has been initialized using an options object that does not match the API schema.
6868
- options has an unknown property 'unknown'. These properties are valid:
69-
object { worker?, publicPath?, filename?, chunkFilename?, inline?, esModule? }"
69+
object { worker?, publicPath?, filename?, chunkFilename?, inline?, crossOrigin?, esModule? }"
7070
`;
7171
7272
exports[`validate options should throw an error on the "unknown" option with "[]" value 1`] = `
7373
"Invalid options object. Worker Loader has been initialized using an options object that does not match the API schema.
7474
- options has an unknown property 'unknown'. These properties are valid:
75-
object { worker?, publicPath?, filename?, chunkFilename?, inline?, esModule? }"
75+
object { worker?, publicPath?, filename?, chunkFilename?, inline?, crossOrigin?, esModule? }"
7676
`;
7777
7878
exports[`validate options should throw an error on the "unknown" option with "{"foo":"bar"}" value 1`] = `
7979
"Invalid options object. Worker Loader has been initialized using an options object that does not match the API schema.
8080
- options has an unknown property 'unknown'. These properties are valid:
81-
object { worker?, publicPath?, filename?, chunkFilename?, inline?, esModule? }"
81+
object { worker?, publicPath?, filename?, chunkFilename?, inline?, crossOrigin?, esModule? }"
8282
`;
8383
8484
exports[`validate options should throw an error on the "unknown" option with "{}" value 1`] = `
8585
"Invalid options object. Worker Loader has been initialized using an options object that does not match the API schema.
8686
- options has an unknown property 'unknown'. These properties are valid:
87-
object { worker?, publicPath?, filename?, chunkFilename?, inline?, esModule? }"
87+
object { worker?, publicPath?, filename?, chunkFilename?, inline?, crossOrigin?, esModule? }"
8888
`;
8989
9090
exports[`validate options should throw an error on the "unknown" option with "1" value 1`] = `
9191
"Invalid options object. Worker Loader has been initialized using an options object that does not match the API schema.
9292
- options has an unknown property 'unknown'. These properties are valid:
93-
object { worker?, publicPath?, filename?, chunkFilename?, inline?, esModule? }"
93+
object { worker?, publicPath?, filename?, chunkFilename?, inline?, crossOrigin?, esModule? }"
9494
`;
9595
9696
exports[`validate options should throw an error on the "unknown" option with "false" value 1`] = `
9797
"Invalid options object. Worker Loader has been initialized using an options object that does not match the API schema.
9898
- options has an unknown property 'unknown'. These properties are valid:
99-
object { worker?, publicPath?, filename?, chunkFilename?, inline?, esModule? }"
99+
object { worker?, publicPath?, filename?, chunkFilename?, inline?, crossOrigin?, esModule? }"
100100
`;
101101
102102
exports[`validate options should throw an error on the "unknown" option with "test" value 1`] = `
103103
"Invalid options object. Worker Loader has been initialized using an options object that does not match the API schema.
104104
- options has an unknown property 'unknown'. These properties are valid:
105-
object { worker?, publicPath?, filename?, chunkFilename?, inline?, esModule? }"
105+
object { worker?, publicPath?, filename?, chunkFilename?, inline?, crossOrigin?, esModule? }"
106106
`;
107107
108108
exports[`validate options should throw an error on the "unknown" option with "true" value 1`] = `
109109
"Invalid options object. Worker Loader has been initialized using an options object that does not match the API schema.
110110
- options has an unknown property 'unknown'. These properties are valid:
111-
object { worker?, publicPath?, filename?, chunkFilename?, inline?, esModule? }"
111+
object { worker?, publicPath?, filename?, chunkFilename?, inline?, crossOrigin?, esModule? }"
112112
`;
113113
114114
exports[`validate options should throw an error on the "worker" option with "[]" value 1`] = `

test/crossOrigin-option.test.js

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
import getPort from 'get-port';
2+
3+
import {
4+
compile,
5+
getCompiler,
6+
getErrors,
7+
getModuleSource,
8+
getResultFromBrowser,
9+
getWarnings,
10+
} from './helpers';
11+
12+
describe('"crossOrigin" option', () => {
13+
it('should not work by default', async () => {
14+
const compiler = getCompiler('./basic/entry.js');
15+
const stats = await compile(compiler);
16+
const result = await getResultFromBrowser(stats);
17+
18+
expect(getModuleSource('./basic/worker.js', stats)).toMatchSnapshot(
19+
'module'
20+
);
21+
expect(stats.compilation.assets['test.worker.js']).toBeDefined();
22+
expect(result).toMatchSnapshot('result');
23+
expect(getWarnings(stats)).toMatchSnapshot('warnings');
24+
expect(getErrors(stats)).toMatchSnapshot('errors');
25+
});
26+
27+
it('should work with crossOrigin enabled', async () => {
28+
const port = await getPort();
29+
const compiler = getCompiler('./basic/entry.js', {
30+
crossOrigin: `http://localhost:${port}/public-path-static/`,
31+
});
32+
const stats = await compile(compiler);
33+
const result = await getResultFromBrowser(stats, port);
34+
const moduleSource = getModuleSource('./basic/worker.js', stats);
35+
36+
expect(moduleSource.indexOf('crossOrigin.js') > 0).toBe(true);
37+
expect(
38+
moduleSource.indexOf('__webpack_public_path__ + "test.worker.js"') === -1
39+
).toBe(true);
40+
expect(
41+
moduleSource.indexOf(
42+
`"http://localhost:${port}/public-path-static/" + "test.worker.js"`
43+
) > 0
44+
).toBe(true);
45+
expect(stats.compilation.assets['test.worker.js']).toBeDefined();
46+
expect(result).toMatchSnapshot('result');
47+
expect(getWarnings(stats)).toMatchSnapshot('warnings');
48+
expect(getErrors(stats)).toMatchSnapshot('errors');
49+
});
50+
51+
it('should work with crossOrigin enabled and "esModule" with "false" value', async () => {
52+
const port = await getPort();
53+
const compiler = getCompiler('./basic/entry.js', {
54+
crossOrigin: `http://localhost:${port}/public-path-static/`,
55+
esModule: false,
56+
});
57+
const stats = await compile(compiler);
58+
const result = await getResultFromBrowser(stats, port);
59+
const moduleSource = getModuleSource('./basic/worker.js', stats);
60+
61+
expect(moduleSource.indexOf('crossOrigin.js') > 0).toBe(true);
62+
expect(
63+
moduleSource.indexOf('__webpack_public_path__ + "test.worker.js"') === -1
64+
).toBe(true);
65+
expect(
66+
moduleSource.indexOf(
67+
`"http://localhost:${port}/public-path-static/" + "test.worker.js"`
68+
) > 0
69+
).toBe(true);
70+
expect(stats.compilation.assets['test.worker.js']).toBeDefined();
71+
expect(result).toMatchSnapshot('result');
72+
expect(getWarnings(stats)).toMatchSnapshot('warnings');
73+
expect(getErrors(stats)).toMatchSnapshot('errors');
74+
});
75+
76+
it('should work with crossOrigin enabled and "esModule" with "true" value', async () => {
77+
const port = await getPort();
78+
const compiler = getCompiler('./basic/entry.js', {
79+
crossOrigin: `http://localhost:${port}/public-path-static/`,
80+
esModule: true,
81+
});
82+
const stats = await compile(compiler);
83+
const result = await getResultFromBrowser(stats, port);
84+
const moduleSource = getModuleSource('./basic/worker.js', stats);
85+
86+
expect(moduleSource.indexOf('crossOrigin.js') > 0).toBe(true);
87+
expect(
88+
moduleSource.indexOf('__webpack_public_path__ + "test.worker.js"') === -1
89+
).toBe(true);
90+
expect(
91+
moduleSource.indexOf(
92+
`"http://localhost:${port}/public-path-static/" + "test.worker.js"`
93+
) > 0
94+
).toBe(true);
95+
expect(stats.compilation.assets['test.worker.js']).toBeDefined();
96+
expect(result).toMatchSnapshot('result');
97+
expect(getWarnings(stats)).toMatchSnapshot('warnings');
98+
expect(getErrors(stats)).toMatchSnapshot('errors');
99+
});
100+
});

test/helpers/getResultFromBrowser.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,10 @@ import getPort from 'get-port';
44
import express from 'express';
55
import puppeteer from 'puppeteer';
66

7-
export default async function getResultFromBrowser(stats) {
7+
export default async function getResultFromBrowser(stats, serverPort) {
88
const assets = Object.entries(stats.compilation.assets);
99
const app = express();
10-
const port = await getPort();
10+
const port = serverPort || (await getPort());
1111
const server = app.listen(port);
1212

1313
app.use(

0 commit comments

Comments
 (0)