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

feat: add option crossOrigin #291

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 40 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,14 +66,15 @@ And run `webpack` via your preferred method.

## Options

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

### `worker`

Expand Down Expand Up @@ -133,6 +134,32 @@ module.exports = {
};
```

### `crossOrigin`
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am fine with this PR, but let's do better name for this option, crossOrigin sounds misleading even for me although I understand that we are trying to solve this

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe originPath?

Copy link
Member

@alexander-akait alexander-akait Nov 24, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change runtime logic, so I think we should point it out

Copy link
Member

@alexander-akait alexander-akait Nov 24, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe workerChunkUrl? Hard for me too, here we have two changes, using import-scripts and pass filename like URL, it can be misleading with inline, I think ideally we should have chunkLoadingType: 'inline-fallback' | 'inline-no-fallback' | 'import-scripts' and set publicPath to Absolute URL, in runtime we should do publicPath + filename for import-scripts

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I understand the need to use publicPath but there's a need to have different public paths for different assets. Maybe I should test whether I can change publicPath on the fly before importing worker. Also why do you refer to worker as "chunk" instead of "script"? Maybe have something like scriptType: "inline-fallback" | "inline-no-fallback" | "import-scripts"?


Type: `String`
Default: `undefined`

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.
Note that this should probably be unset during development.

**webpack.config.js**

```js
module.exports = {
module: {
rules: [
{
test: /\.worker\.(c|m)?js$/i,
loader: 'worker-loader',
options: {
crossOrigin: 'https://my-cdn.com/path/',
},
},
],
},
};
```

### `publicPath`

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

There are two workarounds:

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

**App.js**

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

Expand All @@ -516,19 +544,18 @@ module.exports = {
rules: [
{
loader: 'worker-loader',
options: { inline: 'fallback' },
options: { crossOrigin: 'https://my-cdn.com/abc/' },
},
],
},
};
```

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

**App.js**

```js
// This will cause the worker to be downloaded from `/workers/file.worker.js`
import Worker from './file.worker.js';
```

Expand All @@ -540,7 +567,7 @@ module.exports = {
rules: [
{
loader: 'worker-loader',
options: { publicPath: '/workers/' },
options: { inline: 'fallback' },
},
],
},
Expand Down
2 changes: 2 additions & 0 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -65,11 +65,13 @@ export function pitch(request) {
const publicPath = options.publicPath
? options.publicPath
: compilerOptions.output.publicPath;
const crossOrigin = options.crossOrigin || false;

workerContext.options = {
filename,
chunkFilename,
publicPath,
crossOrigin,
globalObject: 'self',
};

Expand Down
3 changes: 3 additions & 0 deletions src/options.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,9 @@
"inline": {
"enum": ["no-fallback", "fallback"]
},
"crossOrigin": {
"type": "string"
},
"esModule": {
"type": "boolean"
}
Expand Down
34 changes: 34 additions & 0 deletions src/runtime/crossOrigin.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/* eslint-env browser */
/* eslint-disable no-undef, no-use-before-define, new-cap */
// initial solution by Benohead's Software Blog https://benohead.com/blog/2017/12/06/cross-domain-cross-browser-web-workers/

module.exports = (workerConstructor, workerOptions, workerUrl) => {
let worker = null;

Check warning on line 6 in src/runtime/crossOrigin.js

View check run for this annotation

Codecov / codecov/patch

src/runtime/crossOrigin.js#L5-L6

Added lines #L5 - L6 were not covered by tests
let blob;

try {
blob = new Blob([`importScripts('${workerUrl}');`], {

Check warning on line 10 in src/runtime/crossOrigin.js

View check run for this annotation

Codecov / codecov/patch

src/runtime/crossOrigin.js#L9-L10

Added lines #L9 - L10 were not covered by tests
type: 'application/javascript',
});
} catch (e) {
const BlobBuilder =
window.BlobBuilder ||
window.WebKitBlobBuilder ||
window.MozBlobBuilder ||
window.MSBlobBuilder;

Check warning on line 18 in src/runtime/crossOrigin.js

View check run for this annotation

Codecov / codecov/patch

src/runtime/crossOrigin.js#L16-L18

Added lines #L16 - L18 were not covered by tests

blobBuilder = new BlobBuilder();

Check warning on line 20 in src/runtime/crossOrigin.js

View check run for this annotation

Codecov / codecov/patch

src/runtime/crossOrigin.js#L20

Added line #L20 was not covered by tests

blobBuilder.append(`importScripts('${workerUrl}');`);

Check warning on line 22 in src/runtime/crossOrigin.js

View check run for this annotation

Codecov / codecov/patch

src/runtime/crossOrigin.js#L22

Added line #L22 was not covered by tests

blob = blobBuilder.getBlob('application/javascript');

Check warning on line 24 in src/runtime/crossOrigin.js

View check run for this annotation

Codecov / codecov/patch

src/runtime/crossOrigin.js#L24

Added line #L24 was not covered by tests
}

const URL = window.URL || window.webkitURL;
const blobUrl = URL.createObjectURL(blob);
worker = new window[workerConstructor](blobUrl, workerOptions);

Check warning on line 29 in src/runtime/crossOrigin.js

View check run for this annotation

Codecov / codecov/patch

src/runtime/crossOrigin.js#L28-L29

Added lines #L28 - L29 were not covered by tests

URL.revokeObjectURL(blobUrl);

Check warning on line 31 in src/runtime/crossOrigin.js

View check run for this annotation

Codecov / codecov/patch

src/runtime/crossOrigin.js#L31

Added line #L31 was not covered by tests

return worker;

Check warning on line 33 in src/runtime/crossOrigin.js

View check run for this annotation

Codecov / codecov/patch

src/runtime/crossOrigin.js#L33

Added line #L33 was not covered by tests
};
22 changes: 22 additions & 0 deletions src/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,28 @@ ${
)}, ${fallbackWorkerPath});\n}\n`;
}

if (options.crossOrigin) {
const CrossOriginWorkerPath = stringifyRequest(
loaderContext,
`!!${require.resolve('./runtime/crossOrigin.js')}`
);

return `
${
esModule
? `import worker from ${CrossOriginWorkerPath};`
: `var worker = require(${CrossOriginWorkerPath});`
}

${
esModule ? 'export default' : 'module.exports ='
} function() {\n return worker(${JSON.stringify(
workerConstructor
)}, ${JSON.stringify(workerOptions)}, ${JSON.stringify(
options.crossOrigin
)} + ${JSON.stringify(workerFilename)});\n}\n`;
}

return `${
esModule ? 'export default' : 'module.exports ='
} function() {\n return new ${workerConstructor}(__webpack_public_path__ + ${JSON.stringify(
Expand Down
32 changes: 32 additions & 0 deletions test/__snapshots__/crossOrigin-option.test.js.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`"crossOrigin" option should not work by default: errors 1`] = `Array []`;

exports[`"crossOrigin" option should not work by default: module 1`] = `
"export default function() {
return new Worker(__webpack_public_path__ + \\"test.worker.js\\");
}
"
`;

exports[`"crossOrigin" option should not work by default: result 1`] = `"{\\"postMessage\\":true,\\"onmessage\\":true}"`;

exports[`"crossOrigin" option should not work by default: warnings 1`] = `Array []`;

exports[`"crossOrigin" option should work with crossOrigin enabled and "esModule" with "false" value: errors 1`] = `Array []`;

exports[`"crossOrigin" option should work with crossOrigin enabled and "esModule" with "false" value: result 1`] = `"{\\"postMessage\\":true,\\"onmessage\\":true}"`;

exports[`"crossOrigin" option should work with crossOrigin enabled and "esModule" with "false" value: warnings 1`] = `Array []`;

exports[`"crossOrigin" option should work with crossOrigin enabled and "esModule" with "true" value: errors 1`] = `Array []`;

exports[`"crossOrigin" option should work with crossOrigin enabled and "esModule" with "true" value: result 1`] = `"{\\"postMessage\\":true,\\"onmessage\\":true}"`;

exports[`"crossOrigin" option should work with crossOrigin enabled and "esModule" with "true" value: warnings 1`] = `Array []`;

exports[`"crossOrigin" option should work with crossOrigin enabled: errors 1`] = `Array []`;

exports[`"crossOrigin" option should work with crossOrigin enabled: result 1`] = `"{\\"postMessage\\":true,\\"onmessage\\":true}"`;

exports[`"crossOrigin" option should work with crossOrigin enabled: warnings 1`] = `Array []`;
16 changes: 8 additions & 8 deletions test/__snapshots__/validate-options.test.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -66,49 +66,49 @@ exports[`validate options should throw an error on the "publicPath" option with
exports[`validate options should throw an error on the "unknown" option with "/test/" value 1`] = `
"Invalid options object. Worker Loader has been initialized using an options object that does not match the API schema.
- options has an unknown property 'unknown'. These properties are valid:
object { worker?, publicPath?, filename?, chunkFilename?, inline?, esModule? }"
object { worker?, publicPath?, filename?, chunkFilename?, inline?, crossOrigin?, esModule? }"
`;

exports[`validate options should throw an error on the "unknown" option with "[]" value 1`] = `
"Invalid options object. Worker Loader has been initialized using an options object that does not match the API schema.
- options has an unknown property 'unknown'. These properties are valid:
object { worker?, publicPath?, filename?, chunkFilename?, inline?, esModule? }"
object { worker?, publicPath?, filename?, chunkFilename?, inline?, crossOrigin?, esModule? }"
`;

exports[`validate options should throw an error on the "unknown" option with "{"foo":"bar"}" value 1`] = `
"Invalid options object. Worker Loader has been initialized using an options object that does not match the API schema.
- options has an unknown property 'unknown'. These properties are valid:
object { worker?, publicPath?, filename?, chunkFilename?, inline?, esModule? }"
object { worker?, publicPath?, filename?, chunkFilename?, inline?, crossOrigin?, esModule? }"
`;

exports[`validate options should throw an error on the "unknown" option with "{}" value 1`] = `
"Invalid options object. Worker Loader has been initialized using an options object that does not match the API schema.
- options has an unknown property 'unknown'. These properties are valid:
object { worker?, publicPath?, filename?, chunkFilename?, inline?, esModule? }"
object { worker?, publicPath?, filename?, chunkFilename?, inline?, crossOrigin?, esModule? }"
`;

exports[`validate options should throw an error on the "unknown" option with "1" value 1`] = `
"Invalid options object. Worker Loader has been initialized using an options object that does not match the API schema.
- options has an unknown property 'unknown'. These properties are valid:
object { worker?, publicPath?, filename?, chunkFilename?, inline?, esModule? }"
object { worker?, publicPath?, filename?, chunkFilename?, inline?, crossOrigin?, esModule? }"
`;

exports[`validate options should throw an error on the "unknown" option with "false" value 1`] = `
"Invalid options object. Worker Loader has been initialized using an options object that does not match the API schema.
- options has an unknown property 'unknown'. These properties are valid:
object { worker?, publicPath?, filename?, chunkFilename?, inline?, esModule? }"
object { worker?, publicPath?, filename?, chunkFilename?, inline?, crossOrigin?, esModule? }"
`;

exports[`validate options should throw an error on the "unknown" option with "test" value 1`] = `
"Invalid options object. Worker Loader has been initialized using an options object that does not match the API schema.
- options has an unknown property 'unknown'. These properties are valid:
object { worker?, publicPath?, filename?, chunkFilename?, inline?, esModule? }"
object { worker?, publicPath?, filename?, chunkFilename?, inline?, crossOrigin?, esModule? }"
`;

exports[`validate options should throw an error on the "unknown" option with "true" value 1`] = `
"Invalid options object. Worker Loader has been initialized using an options object that does not match the API schema.
- options has an unknown property 'unknown'. These properties are valid:
object { worker?, publicPath?, filename?, chunkFilename?, inline?, esModule? }"
object { worker?, publicPath?, filename?, chunkFilename?, inline?, crossOrigin?, esModule? }"
`;

exports[`validate options should throw an error on the "worker" option with "[]" value 1`] = `
Expand Down
100 changes: 100 additions & 0 deletions test/crossOrigin-option.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import getPort from 'get-port';

import {
compile,
getCompiler,
getErrors,
getModuleSource,
getResultFromBrowser,
getWarnings,
} from './helpers';

describe('"crossOrigin" option', () => {
it('should not work by default', async () => {
const compiler = getCompiler('./basic/entry.js');
const stats = await compile(compiler);
const result = await getResultFromBrowser(stats);

expect(getModuleSource('./basic/worker.js', stats)).toMatchSnapshot(
'module'
);
expect(stats.compilation.assets['test.worker.js']).toBeDefined();
expect(result).toMatchSnapshot('result');
expect(getWarnings(stats)).toMatchSnapshot('warnings');
expect(getErrors(stats)).toMatchSnapshot('errors');
});

it('should work with crossOrigin enabled', async () => {
const port = await getPort();
const compiler = getCompiler('./basic/entry.js', {
crossOrigin: `http://localhost:${port}/public-path-static/`,
});
const stats = await compile(compiler);
const result = await getResultFromBrowser(stats, port);
const moduleSource = getModuleSource('./basic/worker.js', stats);

expect(moduleSource.indexOf('crossOrigin.js') > 0).toBe(true);
expect(
moduleSource.indexOf('__webpack_public_path__ + "test.worker.js"') === -1
).toBe(true);
expect(
moduleSource.indexOf(
`"http://localhost:${port}/public-path-static/" + "test.worker.js"`
) > 0
).toBe(true);
expect(stats.compilation.assets['test.worker.js']).toBeDefined();
expect(result).toMatchSnapshot('result');
expect(getWarnings(stats)).toMatchSnapshot('warnings');
expect(getErrors(stats)).toMatchSnapshot('errors');
});

it('should work with crossOrigin enabled and "esModule" with "false" value', async () => {
const port = await getPort();
const compiler = getCompiler('./basic/entry.js', {
crossOrigin: `http://localhost:${port}/public-path-static/`,
esModule: false,
});
const stats = await compile(compiler);
const result = await getResultFromBrowser(stats, port);
const moduleSource = getModuleSource('./basic/worker.js', stats);

expect(moduleSource.indexOf('crossOrigin.js') > 0).toBe(true);
expect(
moduleSource.indexOf('__webpack_public_path__ + "test.worker.js"') === -1
).toBe(true);
expect(
moduleSource.indexOf(
`"http://localhost:${port}/public-path-static/" + "test.worker.js"`
) > 0
).toBe(true);
expect(stats.compilation.assets['test.worker.js']).toBeDefined();
expect(result).toMatchSnapshot('result');
expect(getWarnings(stats)).toMatchSnapshot('warnings');
expect(getErrors(stats)).toMatchSnapshot('errors');
});

it('should work with crossOrigin enabled and "esModule" with "true" value', async () => {
const port = await getPort();
const compiler = getCompiler('./basic/entry.js', {
crossOrigin: `http://localhost:${port}/public-path-static/`,
esModule: true,
});
const stats = await compile(compiler);
const result = await getResultFromBrowser(stats, port);
const moduleSource = getModuleSource('./basic/worker.js', stats);

expect(moduleSource.indexOf('crossOrigin.js') > 0).toBe(true);
expect(
moduleSource.indexOf('__webpack_public_path__ + "test.worker.js"') === -1
).toBe(true);
expect(
moduleSource.indexOf(
`"http://localhost:${port}/public-path-static/" + "test.worker.js"`
) > 0
).toBe(true);
expect(stats.compilation.assets['test.worker.js']).toBeDefined();
expect(result).toMatchSnapshot('result');
expect(getWarnings(stats)).toMatchSnapshot('warnings');
expect(getErrors(stats)).toMatchSnapshot('errors');
});
});
4 changes: 2 additions & 2 deletions test/helpers/getResultFromBrowser.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@ import getPort from 'get-port';
import express from 'express';
import puppeteer from 'puppeteer';

export default async function getResultFromBrowser(stats) {
export default async function getResultFromBrowser(stats, serverPort) {
const assets = Object.entries(stats.compilation.assets);
const app = express();
const port = await getPort();
const port = serverPort || (await getPort());
const server = app.listen(port);

app.use(
Expand Down