diff --git a/doc/api/errors.md b/doc/api/errors.md
index 31138fab0b23f1..17de5b0b96a27f 100644
--- a/doc/api/errors.md
+++ b/doc/api/errors.md
@@ -1754,10 +1754,11 @@ The fulfilled value of a linking promise is not a `vm.Module` object.
The current module's status does not allow for this operation. The specific
meaning of the error depends on the specific function.
-
-### ERR_WORKER_NEED_ABSOLUTE_PATH
+
+### ERR_WORKER_PATH
-The path for the main script of a worker is not an absolute path.
+The path for the main script of a worker is neither an absolute path
+nor a relative path starting with `./` or `../`.
### ERR_WORKER_UNSERIALIZABLE_ERROR
diff --git a/doc/api/worker_threads.md b/doc/api/worker_threads.md
index b56fb32aec6525..9578403c5ca94d 100644
--- a/doc/api/worker_threads.md
+++ b/doc/api/worker_threads.md
@@ -306,7 +306,9 @@ if (isMainThread) {
### new Worker(filename[, options])
-* `filename` {string} The absolute path to the Worker’s main script.
+* `filename` {string} The path to the Worker’s main script. Must be
+ either an absolute path or a relative path (i.e. relative to the
+ current working directory) starting with `./` or `../`.
If `options.eval` is true, this is a string containing JavaScript code rather
than a path.
* `options` {Object}
diff --git a/lib/internal/errors.js b/lib/internal/errors.js
index 469917c40fbca1..e37400c822aa5a 100644
--- a/lib/internal/errors.js
+++ b/lib/internal/errors.js
@@ -854,8 +854,9 @@ E('ERR_VM_MODULE_NOT_LINKED',
E('ERR_VM_MODULE_NOT_MODULE',
'Provided module is not an instance of Module', Error);
E('ERR_VM_MODULE_STATUS', 'Module status %s', Error);
-E('ERR_WORKER_NEED_ABSOLUTE_PATH',
- 'The worker script filename must be an absolute path. Received "%s"',
+E('ERR_WORKER_PATH',
+ 'The worker script filename must be an absolute path or a relative ' +
+ 'path starting with \'./\' or \'../\'. Received "%s"',
TypeError);
E('ERR_WORKER_UNSERIALIZABLE_ERROR',
'Serializing an uncaught exception failed', Error);
diff --git a/lib/internal/worker.js b/lib/internal/worker.js
index de00f20d4f0e1d..df4f28cf749fe9 100644
--- a/lib/internal/worker.js
+++ b/lib/internal/worker.js
@@ -7,7 +7,7 @@ const util = require('util');
const { Readable, Writable } = require('stream');
const {
ERR_INVALID_ARG_TYPE,
- ERR_WORKER_NEED_ABSOLUTE_PATH,
+ ERR_WORKER_PATH,
ERR_WORKER_UNSERIALIZABLE_ERROR,
ERR_WORKER_UNSUPPORTED_EXTENSION,
} = require('internal/errors').codes;
@@ -212,9 +212,15 @@ class Worker extends EventEmitter {
}
if (!options.eval) {
- if (!path.isAbsolute(filename)) {
- throw new ERR_WORKER_NEED_ABSOLUTE_PATH(filename);
+ if (!path.isAbsolute(filename) &&
+ !filename.startsWith('./') &&
+ !filename.startsWith('../') &&
+ !filename.startsWith('.' + path.sep) &&
+ !filename.startsWith('..' + path.sep)) {
+ throw new ERR_WORKER_PATH(filename);
}
+ filename = path.resolve(filename);
+
const ext = path.extname(filename);
if (ext !== '.js' && ext !== '.mjs') {
throw new ERR_WORKER_UNSUPPORTED_EXTENSION(ext);
diff --git a/test/parallel/test-worker-relative-path-double-dot.js b/test/parallel/test-worker-relative-path-double-dot.js
new file mode 100644
index 00000000000000..ecd294a4d3cba4
--- /dev/null
+++ b/test/parallel/test-worker-relative-path-double-dot.js
@@ -0,0 +1,17 @@
+// Flags: --experimental-worker
+'use strict';
+const path = require('path');
+const assert = require('assert');
+const common = require('../common');
+const { Worker, isMainThread, parentPort } = require('worker_threads');
+
+if (isMainThread) {
+ const cwdName = path.relative('../', '.');
+ const relativePath = path.relative('.', __filename);
+ const w = new Worker(path.join('..', cwdName, relativePath));
+ w.on('message', common.mustCall((message) => {
+ assert.strictEqual(message, 'Hello, world!');
+ }));
+} else {
+ parentPort.postMessage('Hello, world!');
+}
diff --git a/test/parallel/test-worker-relative-path.js b/test/parallel/test-worker-relative-path.js
new file mode 100644
index 00000000000000..30d2a5dde3e779
--- /dev/null
+++ b/test/parallel/test-worker-relative-path.js
@@ -0,0 +1,15 @@
+// Flags: --experimental-worker
+'use strict';
+const path = require('path');
+const assert = require('assert');
+const common = require('../common');
+const { Worker, isMainThread, parentPort } = require('worker_threads');
+
+if (isMainThread) {
+ const w = new Worker('./' + path.relative('.', __filename));
+ w.on('message', common.mustCall((message) => {
+ assert.strictEqual(message, 'Hello, world!');
+ }));
+} else {
+ parentPort.postMessage('Hello, world!');
+}
diff --git a/test/parallel/test-worker-unsupported-path.js b/test/parallel/test-worker-unsupported-path.js
index b4de6fd1976013..71999eb5fe44ec 100644
--- a/test/parallel/test-worker-unsupported-path.js
+++ b/test/parallel/test-worker-unsupported-path.js
@@ -1,21 +1,11 @@
// Flags: --experimental-worker
'use strict';
+const path = require('path');
const common = require('../common');
const assert = require('assert');
const { Worker } = require('worker_threads');
-{
- const expectedErr = common.expectsError({
- code: 'ERR_WORKER_NEED_ABSOLUTE_PATH',
- type: TypeError
- }, 4);
- assert.throws(() => { new Worker('a.js'); }, expectedErr);
- assert.throws(() => { new Worker('b'); }, expectedErr);
- assert.throws(() => { new Worker('c/d.js'); }, expectedErr);
- assert.throws(() => { new Worker('a.mjs'); }, expectedErr);
-}
-
{
const expectedErr = common.expectsError({
code: 'ERR_WORKER_UNSUPPORTED_EXTENSION',
@@ -25,3 +15,15 @@ const { Worker } = require('worker_threads');
assert.throws(() => { new Worker('/c.wasm'); }, expectedErr);
assert.throws(() => { new Worker('/d.txt'); }, expectedErr);
}
+
+{
+ const expectedErr = common.expectsError({
+ code: 'ERR_WORKER_PATH',
+ type: TypeError
+ }, 4);
+ const existingRelPathNoDot = path.relative('.', __filename);
+ assert.throws(() => { new Worker(existingRelPathNoDot); }, expectedErr);
+ assert.throws(() => { new Worker('relative_no_dot'); }, expectedErr);
+ assert.throws(() => { new Worker('file:///file_url'); }, expectedErr);
+ assert.throws(() => { new Worker('https://www.url.com'); }, expectedErr);
+}