Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
5 changes: 5 additions & 0 deletions doc/api/globals.md
Original file line number Diff line number Diff line change
Expand Up @@ -1373,6 +1373,10 @@ accepted and how failures are reported:
* For `blob:` URLs, the script must be held in memory, so blobs backed by a file,
such as those returned by [`fs.openAsBlob()`][], cannot be used.

[Type stripping][] only applies to module workers loaded from `file:` URLs. The
`type` option, not the file extension, decides how an entry is run, so a `.cts`
entry is still evaluated as an ES module.
Comment thread
osztenkurden marked this conversation as resolved.
Resolved

Comment thread
osztenkurden marked this conversation as resolved.
Resolved
### Differences from the HTML Standard

Besides script loading, mentioned above:
Expand Down Expand Up @@ -1467,6 +1471,7 @@ A browser-compatible implementation of [`WritableStreamDefaultWriter`][].
[Navigator API]: https://html.spec.whatwg.org/multipage/system-state.html#the-navigator-object
[RFC 5646]: https://www.rfc-editor.org/rfc/rfc5646.txt
[Refable protocol]: process.md#processrefmayberefable
[Type stripping]: typescript.md#type-stripping
[Web Crypto API]: webcrypto.md
[`--experimental-eventsource`]: cli.md#--experimental-eventsource
[`--experimental-web-worker`]: cli.md#--experimental-web-worker
Expand Down
22 changes: 19 additions & 3 deletions lib/internal/webworker.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ const {
globalThis,
} = primordials;

const { extname } = require('path');

const {
ERR_ILLEGAL_CONSTRUCTOR,
ERR_INVALID_STATE,
Expand All @@ -37,6 +39,8 @@ const {
initEventTarget,
} = require('internal/event_target');

const { getOptionValue } = require('internal/options');

const {
assignFunctionName,
defineOperation,
Expand Down Expand Up @@ -89,6 +93,7 @@ const {
const {
URL,
URLParse,
fileURLToPath,
} = require('internal/url');

const {
Expand Down Expand Up @@ -253,10 +258,21 @@ function runClassicScriptSource(source, url) {
* @returns {Promise}
*/
function runModuleScriptSource(source, url) {
// Necessary to reset RegExp statics before user code runs.
RegExpPrototypeExec(/^/, '');
return require('internal/modules/run_main').runEntryPointWithESMLoader(
(loader) => loader.eval(source, url, true),
(loader) => {
const parsedURL = new URL(url);
if (parsedURL.protocol === 'file:' && getOptionValue('--strip-types')) {
Comment thread
jasnell marked this conversation as resolved.
Resolved
const filename = fileURLToPath(parsedURL);
const extension = extname(filename);
if (extension === '.ts' || extension === '.mts' || extension === '.cts') {
const { stripTypeScriptModuleTypes } = require('internal/modules/typescript');
Comment thread
osztenkurden marked this conversation as resolved.
Resolved
source = stripTypeScriptModuleTypes(source, filename, url);
}
}
// Necessary to reset RegExp statics before user code runs.
RegExpPrototypeExec(/^/, '');
return loader.eval(source, url, true);
},
);
}

Expand Down
1 change: 1 addition & 0 deletions test/fixtures/web-worker/typescript/dependency.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const value: number = 42;
2 changes: 2 additions & 0 deletions test/fixtures/web-worker/typescript/entry.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
const value: number = 1;
postMessage(value);
4 changes: 4 additions & 0 deletions test/fixtures/web-worker/typescript/module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { value } from './dependency.ts';

const result: number = value;
postMessage(result);
13 changes: 13 additions & 0 deletions test/parallel/test-webworker-typescript-disabled.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// Flags: --experimental-web-worker --no-strip-types
'use strict';

const common = require('../common');
const assert = require('node:assert');
const fixtures = require('../common/fixtures');

// Without type stripping, annotated `.ts` entries fail to parse.
const worker = new Worker(fixtures.fileURL('web-worker', 'typescript', 'entry.ts'), { type: 'module' });
worker.onmessage = common.mustNotCall('types must not be stripped');
worker.onerror = common.mustCall(({ error }) => {
assert.strictEqual(error.name, 'SyntaxError');
});
64 changes: 64 additions & 0 deletions test/parallel/test-webworker-typescript.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
// Flags: --experimental-web-worker
'use strict';

const common = require('../common');
if (!process.config.variables.node_use_amaro) {
common.skip('Requires Amaro');
}

// Strip file entry types without changing the worker's module semantics.
const assert = require('node:assert');
const { mkdirSync, writeFileSync } = require('node:fs');
const { join } = require('node:path');
const { pathToFileURL } = require('node:url');
const fixtures = require('../common/fixtures');
const tmpdir = require('../common/tmpdir');

tmpdir.refresh();

function createEntry(name, source) {
const path = join(tmpdir.path, name);
writeFileSync(path, source);
return pathToFileURL(path);
}

function expectMessage(url, expected) {
const worker = new Worker(url, { type: 'module' });
worker.onerror = common.mustNotCall('worker failed');
worker.onmessage = common.mustCall(({ data }) => {
assert.strictEqual(data, expected);
worker.terminate();
});
}

function expectError(url, code, type = 'module') {
const worker = new Worker(url, { type });
worker.onmessage = common.mustNotCall('worker unexpectedly succeeded');
worker.onerror = common.mustCall(({ error }) => {
assert.strictEqual(error.code ?? error.name, code);
});
}

// Worker type takes precedence over both the extension and package type.
writeFileSync(join(tmpdir.path, 'package.json'), '{ "type": "commonjs" }');
for (const extension of ['ts', 'mts', 'cts']) {
const url = createEntry(`entry.${extension}`, 'const type: string = typeof require; postMessage(type);');
expectMessage(url, 'undefined');
}

// The entry can import TypeScript, and a query or hash does not affect detection.
{
const url = fixtures.fileURL('web-worker', 'typescript', 'module.ts');
url.search = '?version=1';
url.hash = '#entry';
expectMessage(url, 42);
}

// Stripping errors reach the parent's error handler.
mkdirSync(join(tmpdir.path, 'node_modules'));
expectError(createEntry('node_modules/entry.ts', 'postMessage(1);'),
'ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING');

// Classic workers and `.js` entries are not stripped.
expectError(fixtures.fileURL('web-worker', 'typescript', 'entry.ts'), 'SyntaxError', 'classic');
expectError(createEntry('entry.js', 'const value: number = 1;'), 'SyntaxError');
Loading