Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

module,src: add --experimental-entry-url flag #49975

Open
wants to merge 2 commits into
base: main
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
15 changes: 15 additions & 0 deletions doc/api/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -617,6 +617,20 @@ files with no extension will be treated as WebAssembly if they begin with the
WebAssembly magic number (`\0asm`); otherwise they will be treated as ES module
JavaScript.

### `--experimental-entry-url`
Copy link
Member

Choose a reason for hiding this comment

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

I think we can call the flag --entry-url, with a printed experimental warning and the docs listing it as experimental. This isn’t a very risky feature that we need the experimental part in the flag name.


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

> Stability: 1.0 - Early development

When present, Node.js will interpret the entry point as a URL, rather than a
path. The URL must either start with `./` (e.g. `./entry.js`) or be absolute
(e.g. `file:///home/user/entry.js`). Bare specifier (e.g. `entry.js`) won't
work.
Comment on lines +630 to +632
Copy link
Contributor

Choose a reason for hiding this comment

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

This part sounds a bit vague: does "won't work" mean throwing an error, or being interpreted as relative URL? Can relative URL start with ../? Would relative URL starting from /, ?, #, %2E/, etc. throw as well?


### `--experimental-import-meta-resolve`

<!-- YAML
Expand Down Expand Up @@ -2274,6 +2288,7 @@ Node.js options that are allowed are:
* `--enable-source-maps`
* `--experimental-abortcontroller`
* `--experimental-default-type`
* `--experimental-entry-url`
* `--experimental-import-meta-resolve`
* `--experimental-json-modules`
* `--experimental-loader`
Expand Down
3 changes: 2 additions & 1 deletion lib/internal/main/run_main_module.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@ const {
prepareMainThreadExecution,
markBootstrapComplete,
} = require('internal/process/pre_execution');
const { getOptionValue } = require('internal/options');

prepareMainThreadExecution(true);
prepareMainThreadExecution(!getOptionValue('--experimental-entry-url'));

markBootstrapComplete();

Expand Down
6 changes: 3 additions & 3 deletions lib/internal/modules/esm/resolve.js
Original file line number Diff line number Diff line change
Expand Up @@ -1028,6 +1028,7 @@ function throwIfInvalidParentURL(parentURL) {
*/
function defaultResolve(specifier, context = {}) {
let { parentURL, conditions } = context;
const isMain = parentURL === undefined;
throwIfInvalidParentURL(parentURL);
if (parentURL && policy?.manifest) {
const redirects = policy.manifest.getDependencyMapper(parentURL);
Expand Down Expand Up @@ -1083,8 +1084,8 @@ function defaultResolve(specifier, context = {}) {
) {
return { __proto__: null, url: parsed.href };
}
} catch {
// Ignore exception
} catch (err) {
if (isMain && !shouldBeTreatedAsRelativeOrAbsolutePath(specifier)) { throw err; }
Copy link
Member

Choose a reason for hiding this comment

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

Can you please add a comment explaining this?

}

// There are multiple deep branches that can either throw or return; instead
Expand All @@ -1102,7 +1103,6 @@ function defaultResolve(specifier, context = {}) {
if (parsed && parsed.protocol === 'node:') { return { __proto__: null, url: specifier }; }


const isMain = parentURL === undefined;
if (isMain) {
parentURL = getCWDURL().href;

Expand Down
7 changes: 5 additions & 2 deletions lib/internal/modules/run_main.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ const path = require('path');
* @param {string} main Entry point path
*/
function resolveMainPath(main) {
if (getOptionValue('--experimental-default-type') === 'module' || getOptionValue('--experimental-entry-url')) {
return;
}
// Note extension resolution for the main entry point can be deprecated in a
// future major.
// Module._findPath is monkey-patchable here.
Expand All @@ -33,6 +36,7 @@ function resolveMainPath(main) {
* @param {string} mainPath Absolute path to the main entry point
*/
function shouldUseESMLoader(mainPath) {
if (getOptionValue('--experimental-entry-url')) { return true; }
/**
* @type {string[]} userLoaders A list of custom loaders registered by the user
* (or an empty list when none have been registered).
Expand Down Expand Up @@ -64,8 +68,7 @@ function runMainESM(mainPath) {
const { pathToFileURL } = require('internal/url');

handleMainPromise(loadESM((esmLoader) => {
const main = path.isAbsolute(mainPath) ?
pathToFileURL(mainPath).href : mainPath;
const main = getOptionValue('--experimental-entry-url') ? mainPath : pathToFileURL(mainPath).href;
return esmLoader.import(main, undefined, { __proto__: null });
}));
}
Expand Down
4 changes: 2 additions & 2 deletions lib/internal/process/pre_execution.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,15 +52,15 @@ const {
} = require('internal/v8/startup_snapshot');

function prepareMainThreadExecution(expandArgv1 = false, initializeModules = true) {
prepareExecution({
return prepareExecution({
expandArgv1,
initializeModules,
isMainThread: true,
});
}

function prepareWorkerThreadExecution() {
prepareExecution({
return prepareExecution({
expandArgv1: false,
initializeModules: false, // Will need to initialize it after policy setup
isMainThread: false,
Expand Down
4 changes: 4 additions & 0 deletions src/node_options.cc
Original file line number Diff line number Diff line change
Expand Up @@ -657,6 +657,10 @@ EnvironmentOptionsParser::EnvironmentOptionsParser() {
"set module system to use by default",
&EnvironmentOptions::type,
kAllowedInEnvvar);
AddOption("--experimental-entry-url",
"set module system to use by default",
&EnvironmentOptions::entry_is_url,
kAllowedInEnvvar);
AddOption("--extra-info-on-fatal-exception",
"hide extra information on fatal exception that causes exit",
&EnvironmentOptions::extra_info_on_fatal_exception,
Expand Down
1 change: 1 addition & 0 deletions src/node_options.h
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,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;
std::string experimental_policy;
std::string experimental_policy_integrity;
bool has_policy_integrity_string = false;
Expand Down
122 changes: 122 additions & 0 deletions test/es-module/test-esm-loader-entry-is-url.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
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,
[
'--experimental-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,
[
'--experimental-entry-url',
fixtures.fileURL('es-modules/test-esm-double-encoding-native%20.mjs').pathname,
]
);

assert.strictEqual(stderr, '');
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,
[
'--experimental-entry-url',
fixtures.fileURL('printA.js'),
]
);

assert.strictEqual(stderr, '');
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,
[
'--experimental-entry-url',
'./printA.js?key=value',
Copy link
Contributor

Choose a reason for hiding this comment

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

Doesn't have to be in this PR, but we'll need to test search and hash being passed correctly and not stripped by fileURLToPath() anywhere.

],
{
cwd: fixtures.fileURL('./'),
}
);

assert.strictEqual(stderr, '');
assert.match(stdout, /^A\r?\n$/);
assert.strictEqual(code, 0);
assert.strictEqual(signal, null);
});

it('should reject loading relative URLs without trailing `./`', async () => {
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
it('should reject loading relative URLs without trailing `./`', async () => {
it('should reject loading relative URLs without leading `./`', async () => {

const { code, signal, stderr, stdout } = await spawnPromisified(
execPath,
[
'--experimental-entry-url',
'printA.js?key=value',
],
{
cwd: fixtures.fileURL('./'),
}
);

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

it('should reject loading bare specifiers from node_modules', async () => {
const { code, signal, stderr, stdout } = await spawnPromisified(
execPath,
[
'--experimental-entry-url',
'explicit-main',
],
{
cwd: fixtures.fileURL('./es-module-specifiers/'),
}
);

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

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

assert.strictEqual(stderr, '');
assert.match(stdout, /^0\r?\n$/);
assert.strictEqual(code, 0);
assert.strictEqual(signal, null);
});

});