Skip to content

Commit

Permalink
module,src: support loading entrypoint as url
Browse files Browse the repository at this point in the history
Co-Authored-By: Antoine du Hamel <[email protected]>
  • Loading branch information
RedYetiDev and aduh95 committed Sep 14, 2024
1 parent 75e4d0d commit 9d90d6c
Show file tree
Hide file tree
Showing 7 changed files with 128 additions and 6 deletions.
24 changes: 24 additions & 0 deletions doc/api/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -805,6 +805,28 @@ when `Error.stack` is accessed. If you access `Error.stack` frequently
in your application, take into account the performance implications
of `--enable-source-maps`.

### `--entry-url`

<!-- YAML
added:
- REPLACEME
-->

> Stability: 1 - Experimental
When present, Node.js will interpret the entry point as a URL, rather than a
path.

Follows [ECMAScript module][] resolution rules.

Any query parameter or hash in the URL will be accessible via [`import.meta.url`][].

```bash
node --entry-url 'file:///path/to/file.js?queryparams=work'
node --entry-url 'file.js'
node --entry-url 'data:text/javascript,console.log("Hello")'
```

### `--env-file=config`

> Stability: 1.1 - Active development
Expand Down Expand Up @@ -2981,6 +3003,7 @@ one is included in the list below.
* `--enable-fips`
* `--enable-network-family-autoselection`
* `--enable-source-maps`
* `--entry-url`
* `--experimental-abortcontroller`
* `--experimental-async-context-frame`
* `--experimental-default-type`
Expand Down Expand Up @@ -3571,6 +3594,7 @@ node --stack-trace-limit=12 -p -e "Error.stackTraceLimit" # prints 12
[`dns.lookup()`]: dns.md#dnslookuphostname-options-callback
[`dns.setDefaultResultOrder()`]: dns.md#dnssetdefaultresultorderorder
[`dnsPromises.lookup()`]: dns.md#dnspromiseslookuphostname-options
[`import.meta.url`]: esm.md#importmetaurl
[`import` specifier]: esm.md#import-specifiers
[`net.getDefaultAutoSelectFamilyAttemptTimeout()`]: net.md#netgetdefaultautoselectfamilyattempttimeout
[`node:sqlite`]: sqlite.md
Expand Down
3 changes: 3 additions & 0 deletions doc/node.1
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,9 @@ Requires Node.js to be built with
.It Fl -enable-source-maps
Enable Source Map V3 support for stack traces.
.
.It Fl -entry-url
Interpret the entry point as a URL.
.
.It Fl -experimental-default-type Ns = Ns Ar type
Interpret as either ES modules or CommonJS modules input via --eval or STDIN, when --input-type is unspecified;
.js or extensionless files with no sibling or parent package.json;
Expand Down
8 changes: 7 additions & 1 deletion lib/internal/main/run_main_module.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,20 @@ const {
markBootstrapComplete,
} = require('internal/process/pre_execution');
const { getOptionValue } = require('internal/options');
const { emitExperimentalWarning } = require('internal/util');

const mainEntry = prepareMainThreadExecution(true);
const isEntryURL = getOptionValue('--entry-url');
const mainEntry = prepareMainThreadExecution(!isEntryURL);

markBootstrapComplete();

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

if (isEntryURL) {
emitExperimentalWarning('--entry-url');
}

if (getOptionValue('--experimental-default-type') === 'module') {
require('internal/modules/run_main').executeUserEntryPoint(mainEntry);
} else {
Expand Down
9 changes: 4 additions & 5 deletions lib/internal/modules/run_main.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ const {
* @param {string} main - Entry point path
*/
function resolveMainPath(main) {
if (getOptionValue('--entry-url')) { return; }

const defaultType = getOptionValue('--experimental-default-type');
/** @type {string} */
let mainPath;
Expand Down Expand Up @@ -63,7 +65,7 @@ function resolveMainPath(main) {
* @param {string} mainPath - Absolute path to the main entry point
*/
function shouldUseESMLoader(mainPath) {
if (getOptionValue('--experimental-default-type') === 'module') { return true; }
if (getOptionValue('--entry-url') || getOptionValue('--experimental-default-type') === 'module') { return true; }

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

runEntryPointWithESMLoader((cascadedLoader) => {
// Note that if the graph contains unsettled TLA, this may never resolve
Expand Down
4 changes: 4 additions & 0 deletions src/node_options.cc
Original file line number Diff line number Diff line change
Expand Up @@ -406,6 +406,10 @@ EnvironmentOptionsParser::EnvironmentOptionsParser() {
"Source Map V3 support for stack traces",
&EnvironmentOptions::enable_source_maps,
kAllowedInEnvvar);
AddOption("--entry-url",
"Treat the entrypoint as a URL",
&EnvironmentOptions::entry_is_url,
kAllowedInEnvvar);
AddOption("--experimental-abortcontroller", "", NoOp{}, kAllowedInEnvvar);
AddOption("--experimental-eventsource",
"experimental EventSource API",
Expand Down
1 change: 1 addition & 0 deletions src/node_options.h
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@ class EnvironmentOptions : public Options {
bool experimental_import_meta_resolve = false;
std::string input_type; // Value of --input-type
std::string type; // Value of --experimental-default-type
bool entry_is_url = false;
bool experimental_permission = false;
std::vector<std::string> allow_fs_read;
std::vector<std::string> allow_fs_write;
Expand Down
85 changes: 85 additions & 0 deletions test/es-module/test-esm-loader-entry-url.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import { spawnPromisified } from '../common/index.mjs';
import * as fixtures from '../common/fixtures.mjs';
import assert from 'node:assert';
import { execPath } from 'node:process';
import { describe, it } from 'node:test';

describe('--entry-url', { concurrency: true }, () => {
it('should reject loading absolute path that contains %', async () => {
const { code, signal, stderr, stdout } = await spawnPromisified(
execPath,
[
'--entry-url',
fixtures.path('es-modules/test-esm-double-encoding-native%20.mjs'),
]
);

assert.match(stderr, /ERR_MODULE_NOT_FOUND/);
assert.strictEqual(stdout, '');
assert.strictEqual(code, 1);
assert.strictEqual(signal, null);
});

it('should support loading absolute Unix path properly encoded', async () => {
const { code, signal, stderr, stdout } = await spawnPromisified(
execPath,
[
'--entry-url',
fixtures.fileURL('es-modules/test-esm-double-encoding-native%20.mjs').pathname,
]
);

assert.match(stderr, /--entry-url is an experimental feature/);
assert.strictEqual(stdout, '');
assert.strictEqual(code, 0);
assert.strictEqual(signal, null);
});

it('should support loading absolute URLs', async () => {
const { code, signal, stderr, stdout } = await spawnPromisified(
execPath,
[
'--entry-url',
fixtures.fileURL('printA.js'),
]
);

assert.match(stderr, /--entry-url is an experimental feature/);
assert.match(stdout, /^A\r?\n$/);
assert.strictEqual(code, 0);
assert.strictEqual(signal, null);
});

it('should support loading relative URLs', async () => {
const { code, signal, stderr, stdout } = await spawnPromisified(
execPath,
[
'--entry-url',
'./printA.js?key=value',
],
{
cwd: fixtures.fileURL('./'),
}
);

assert.match(stderr, /--entry-url is an experimental feature/);
assert.match(stdout, /^A\r?\n$/);
assert.strictEqual(code, 0);
assert.strictEqual(signal, null);
});

it('should support loading `data:` URLs', async () => {
const { code, signal, stderr, stdout } = await spawnPromisified(
execPath,
[
'--entry-url',
'data:text/javascript,console.log(0)',
],
);

assert.match(stderr, /--entry-url is an experimental feature/);
assert.match(stdout, /^0\r?\n$/);
assert.strictEqual(code, 0);
assert.strictEqual(signal, null);
});
});

0 comments on commit 9d90d6c

Please sign in to comment.