Skip to content

Commit 9d90d6c

Browse files
avivkelleraduh95
andcommitted
module,src: support loading entrypoint as url
Co-Authored-By: Antoine du Hamel <[email protected]>
1 parent 75e4d0d commit 9d90d6c

File tree

7 files changed

+128
-6
lines changed

7 files changed

+128
-6
lines changed

doc/api/cli.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -805,6 +805,28 @@ when `Error.stack` is accessed. If you access `Error.stack` frequently
805805
in your application, take into account the performance implications
806806
of `--enable-source-maps`.
807807

808+
### `--entry-url`
809+
810+
<!-- YAML
811+
added:
812+
- REPLACEME
813+
-->
814+
815+
> Stability: 1 - Experimental
816+
817+
When present, Node.js will interpret the entry point as a URL, rather than a
818+
path.
819+
820+
Follows [ECMAScript module][] resolution rules.
821+
822+
Any query parameter or hash in the URL will be accessible via [`import.meta.url`][].
823+
824+
```bash
825+
node --entry-url 'file:///path/to/file.js?queryparams=work'
826+
node --entry-url 'file.js'
827+
node --entry-url 'data:text/javascript,console.log("Hello")'
828+
```
829+
808830
### `--env-file=config`
809831

810832
> Stability: 1.1 - Active development
@@ -2981,6 +3003,7 @@ one is included in the list below.
29813003
* `--enable-fips`
29823004
* `--enable-network-family-autoselection`
29833005
* `--enable-source-maps`
3006+
* `--entry-url`
29843007
* `--experimental-abortcontroller`
29853008
* `--experimental-async-context-frame`
29863009
* `--experimental-default-type`
@@ -3571,6 +3594,7 @@ node --stack-trace-limit=12 -p -e "Error.stackTraceLimit" # prints 12
35713594
[`dns.lookup()`]: dns.md#dnslookuphostname-options-callback
35723595
[`dns.setDefaultResultOrder()`]: dns.md#dnssetdefaultresultorderorder
35733596
[`dnsPromises.lookup()`]: dns.md#dnspromiseslookuphostname-options
3597+
[`import.meta.url`]: esm.md#importmetaurl
35743598
[`import` specifier]: esm.md#import-specifiers
35753599
[`net.getDefaultAutoSelectFamilyAttemptTimeout()`]: net.md#netgetdefaultautoselectfamilyattempttimeout
35763600
[`node:sqlite`]: sqlite.md

doc/node.1

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,9 @@ Requires Node.js to be built with
160160
.It Fl -enable-source-maps
161161
Enable Source Map V3 support for stack traces.
162162
.
163+
.It Fl -entry-url
164+
Interpret the entry point as a URL.
165+
.
163166
.It Fl -experimental-default-type Ns = Ns Ar type
164167
Interpret as either ES modules or CommonJS modules input via --eval or STDIN, when --input-type is unspecified;
165168
.js or extensionless files with no sibling or parent package.json;

lib/internal/main/run_main_module.js

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,20 @@ const {
99
markBootstrapComplete,
1010
} = require('internal/process/pre_execution');
1111
const { getOptionValue } = require('internal/options');
12+
const { emitExperimentalWarning } = require('internal/util');
1213

13-
const mainEntry = prepareMainThreadExecution(true);
14+
const isEntryURL = getOptionValue('--entry-url');
15+
const mainEntry = prepareMainThreadExecution(!isEntryURL);
1416

1517
markBootstrapComplete();
1618

1719
// Necessary to reset RegExp statics before user code runs.
1820
RegExpPrototypeExec(/^/, '');
1921

22+
if (isEntryURL) {
23+
emitExperimentalWarning('--entry-url');
24+
}
25+
2026
if (getOptionValue('--experimental-default-type') === 'module') {
2127
require('internal/modules/run_main').executeUserEntryPoint(mainEntry);
2228
} else {

lib/internal/modules/run_main.js

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ const {
2626
* @param {string} main - Entry point path
2727
*/
2828
function resolveMainPath(main) {
29+
if (getOptionValue('--entry-url')) { return; }
30+
2931
const defaultType = getOptionValue('--experimental-default-type');
3032
/** @type {string} */
3133
let mainPath;
@@ -63,7 +65,7 @@ function resolveMainPath(main) {
6365
* @param {string} mainPath - Absolute path to the main entry point
6466
*/
6567
function shouldUseESMLoader(mainPath) {
66-
if (getOptionValue('--experimental-default-type') === 'module') { return true; }
68+
if (getOptionValue('--entry-url') || getOptionValue('--experimental-default-type') === 'module') { return true; }
6769

6870
/**
6971
* @type {string[]} userLoaders A list of custom loaders registered by the user
@@ -157,7 +159,6 @@ function runEntryPointWithESMLoader(callback) {
157159
function executeUserEntryPoint(main = process.argv[1]) {
158160
const resolvedMain = resolveMainPath(main);
159161
const useESMLoader = shouldUseESMLoader(resolvedMain);
160-
let mainURL;
161162
// Unless we know we should use the ESM loader to handle the entry point per the checks in `shouldUseESMLoader`, first
162163
// try to run the entry point via the CommonJS loader; and if that fails under certain conditions, retry as ESM.
163164
if (!useESMLoader) {
@@ -166,9 +167,7 @@ function executeUserEntryPoint(main = process.argv[1]) {
166167
wrapModuleLoad(main, null, true);
167168
} else {
168169
const mainPath = resolvedMain || main;
169-
if (mainURL === undefined) {
170-
mainURL = pathToFileURL(mainPath).href;
171-
}
170+
const mainURL = getOptionValue('--entry-url') ? mainPath : pathToFileURL(mainPath).href;
172171

173172
runEntryPointWithESMLoader((cascadedLoader) => {
174173
// Note that if the graph contains unsettled TLA, this may never resolve

src/node_options.cc

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -406,6 +406,10 @@ EnvironmentOptionsParser::EnvironmentOptionsParser() {
406406
"Source Map V3 support for stack traces",
407407
&EnvironmentOptions::enable_source_maps,
408408
kAllowedInEnvvar);
409+
AddOption("--entry-url",
410+
"Treat the entrypoint as a URL",
411+
&EnvironmentOptions::entry_is_url,
412+
kAllowedInEnvvar);
409413
AddOption("--experimental-abortcontroller", "", NoOp{}, kAllowedInEnvvar);
410414
AddOption("--experimental-eventsource",
411415
"experimental EventSource API",

src/node_options.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,7 @@ class EnvironmentOptions : public Options {
132132
bool experimental_import_meta_resolve = false;
133133
std::string input_type; // Value of --input-type
134134
std::string type; // Value of --experimental-default-type
135+
bool entry_is_url = false;
135136
bool experimental_permission = false;
136137
std::vector<std::string> allow_fs_read;
137138
std::vector<std::string> allow_fs_write;
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
import { spawnPromisified } from '../common/index.mjs';
2+
import * as fixtures from '../common/fixtures.mjs';
3+
import assert from 'node:assert';
4+
import { execPath } from 'node:process';
5+
import { describe, it } from 'node:test';
6+
7+
describe('--entry-url', { concurrency: true }, () => {
8+
it('should reject loading absolute path that contains %', async () => {
9+
const { code, signal, stderr, stdout } = await spawnPromisified(
10+
execPath,
11+
[
12+
'--entry-url',
13+
fixtures.path('es-modules/test-esm-double-encoding-native%20.mjs'),
14+
]
15+
);
16+
17+
assert.match(stderr, /ERR_MODULE_NOT_FOUND/);
18+
assert.strictEqual(stdout, '');
19+
assert.strictEqual(code, 1);
20+
assert.strictEqual(signal, null);
21+
});
22+
23+
it('should support loading absolute Unix path properly encoded', async () => {
24+
const { code, signal, stderr, stdout } = await spawnPromisified(
25+
execPath,
26+
[
27+
'--entry-url',
28+
fixtures.fileURL('es-modules/test-esm-double-encoding-native%20.mjs').pathname,
29+
]
30+
);
31+
32+
assert.match(stderr, /--entry-url is an experimental feature/);
33+
assert.strictEqual(stdout, '');
34+
assert.strictEqual(code, 0);
35+
assert.strictEqual(signal, null);
36+
});
37+
38+
it('should support loading absolute URLs', async () => {
39+
const { code, signal, stderr, stdout } = await spawnPromisified(
40+
execPath,
41+
[
42+
'--entry-url',
43+
fixtures.fileURL('printA.js'),
44+
]
45+
);
46+
47+
assert.match(stderr, /--entry-url is an experimental feature/);
48+
assert.match(stdout, /^A\r?\n$/);
49+
assert.strictEqual(code, 0);
50+
assert.strictEqual(signal, null);
51+
});
52+
53+
it('should support loading relative URLs', async () => {
54+
const { code, signal, stderr, stdout } = await spawnPromisified(
55+
execPath,
56+
[
57+
'--entry-url',
58+
'./printA.js?key=value',
59+
],
60+
{
61+
cwd: fixtures.fileURL('./'),
62+
}
63+
);
64+
65+
assert.match(stderr, /--entry-url is an experimental feature/);
66+
assert.match(stdout, /^A\r?\n$/);
67+
assert.strictEqual(code, 0);
68+
assert.strictEqual(signal, null);
69+
});
70+
71+
it('should support loading `data:` URLs', async () => {
72+
const { code, signal, stderr, stdout } = await spawnPromisified(
73+
execPath,
74+
[
75+
'--entry-url',
76+
'data:text/javascript,console.log(0)',
77+
],
78+
);
79+
80+
assert.match(stderr, /--entry-url is an experimental feature/);
81+
assert.match(stdout, /^0\r?\n$/);
82+
assert.strictEqual(code, 0);
83+
assert.strictEqual(signal, null);
84+
});
85+
});

0 commit comments

Comments
 (0)