mirror of
https://github.com/nodejs/node.git
synced 2024-11-21 10:59:27 +00:00
module: support loading entrypoint as url
Co-Authored-By: Antoine du Hamel <duhamelantoine1995@gmail.com> PR-URL: https://github.com/nodejs/node/pull/54933 Refs: https://github.com/nodejs/node/pull/49975 Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: LiviaMedeiros <livia@cirno.name> Reviewed-By: Stephen Belanger <admin@stephenbelanger.com> Reviewed-By: Matteo Collina <matteo.collina@gmail.com> Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com>
This commit is contained in:
parent
66a2cb210a
commit
772b35bdc4
@ -805,6 +805,28 @@ when `Error.stack` is accessed. If you access `Error.stack` frequently
|
|||||||
in your application, take into account the performance implications
|
in your application, take into account the performance implications
|
||||||
of `--enable-source-maps`.
|
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#and-hashes-too'
|
||||||
|
node --entry-url --experimental-strip-types 'file.ts?query#hash'
|
||||||
|
node --entry-url 'data:text/javascript,console.log("Hello")'
|
||||||
|
```
|
||||||
|
|
||||||
### `--env-file=config`
|
### `--env-file=config`
|
||||||
|
|
||||||
> Stability: 1.1 - Active development
|
> Stability: 1.1 - Active development
|
||||||
@ -3017,6 +3039,7 @@ one is included in the list below.
|
|||||||
* `--enable-fips`
|
* `--enable-fips`
|
||||||
* `--enable-network-family-autoselection`
|
* `--enable-network-family-autoselection`
|
||||||
* `--enable-source-maps`
|
* `--enable-source-maps`
|
||||||
|
* `--entry-url`
|
||||||
* `--experimental-abortcontroller`
|
* `--experimental-abortcontroller`
|
||||||
* `--experimental-async-context-frame`
|
* `--experimental-async-context-frame`
|
||||||
* `--experimental-default-type`
|
* `--experimental-default-type`
|
||||||
@ -3606,6 +3629,7 @@ node --stack-trace-limit=12 -p -e "Error.stackTraceLimit" # prints 12
|
|||||||
[`dns.lookup()`]: dns.md#dnslookuphostname-options-callback
|
[`dns.lookup()`]: dns.md#dnslookuphostname-options-callback
|
||||||
[`dns.setDefaultResultOrder()`]: dns.md#dnssetdefaultresultorderorder
|
[`dns.setDefaultResultOrder()`]: dns.md#dnssetdefaultresultorderorder
|
||||||
[`dnsPromises.lookup()`]: dns.md#dnspromiseslookuphostname-options
|
[`dnsPromises.lookup()`]: dns.md#dnspromiseslookuphostname-options
|
||||||
|
[`import.meta.url`]: esm.md#importmetaurl
|
||||||
[`import` specifier]: esm.md#import-specifiers
|
[`import` specifier]: esm.md#import-specifiers
|
||||||
[`net.getDefaultAutoSelectFamilyAttemptTimeout()`]: net.md#netgetdefaultautoselectfamilyattempttimeout
|
[`net.getDefaultAutoSelectFamilyAttemptTimeout()`]: net.md#netgetdefaultautoselectfamilyattempttimeout
|
||||||
[`node:sqlite`]: sqlite.md
|
[`node:sqlite`]: sqlite.md
|
||||||
|
@ -160,6 +160,9 @@ Requires Node.js to be built with
|
|||||||
.It Fl -enable-source-maps
|
.It Fl -enable-source-maps
|
||||||
Enable Source Map V3 support for stack traces.
|
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
|
.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;
|
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;
|
.js or extensionless files with no sibling or parent package.json;
|
||||||
|
@ -9,14 +9,20 @@ const {
|
|||||||
markBootstrapComplete,
|
markBootstrapComplete,
|
||||||
} = require('internal/process/pre_execution');
|
} = require('internal/process/pre_execution');
|
||||||
const { getOptionValue } = require('internal/options');
|
const { getOptionValue } = require('internal/options');
|
||||||
|
const { emitExperimentalWarning } = require('internal/util');
|
||||||
|
|
||||||
const mainEntry = prepareMainThreadExecution(true);
|
const isEntryURL = getOptionValue('--entry-url');
|
||||||
|
const mainEntry = prepareMainThreadExecution(!isEntryURL);
|
||||||
|
|
||||||
markBootstrapComplete();
|
markBootstrapComplete();
|
||||||
|
|
||||||
// Necessary to reset RegExp statics before user code runs.
|
// Necessary to reset RegExp statics before user code runs.
|
||||||
RegExpPrototypeExec(/^/, '');
|
RegExpPrototypeExec(/^/, '');
|
||||||
|
|
||||||
|
if (isEntryURL) {
|
||||||
|
emitExperimentalWarning('--entry-url');
|
||||||
|
}
|
||||||
|
|
||||||
if (getOptionValue('--experimental-default-type') === 'module') {
|
if (getOptionValue('--experimental-default-type') === 'module') {
|
||||||
require('internal/modules/run_main').executeUserEntryPoint(mainEntry);
|
require('internal/modules/run_main').executeUserEntryPoint(mainEntry);
|
||||||
} else {
|
} else {
|
||||||
|
@ -8,7 +8,7 @@ const {
|
|||||||
const { getNearestParentPackageJSONType } = internalBinding('modules');
|
const { getNearestParentPackageJSONType } = internalBinding('modules');
|
||||||
const { getOptionValue } = require('internal/options');
|
const { getOptionValue } = require('internal/options');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const { pathToFileURL } = require('internal/url');
|
const { pathToFileURL, URL } = require('internal/url');
|
||||||
const { kEmptyObject, getCWDURL } = require('internal/util');
|
const { kEmptyObject, getCWDURL } = require('internal/util');
|
||||||
const {
|
const {
|
||||||
hasUncaughtExceptionCaptureCallback,
|
hasUncaughtExceptionCaptureCallback,
|
||||||
@ -154,9 +154,14 @@ function runEntryPointWithESMLoader(callback) {
|
|||||||
* @param {string} main - First positional CLI argument, such as `'entry.js'` from `node entry.js`
|
* @param {string} main - First positional CLI argument, such as `'entry.js'` from `node entry.js`
|
||||||
*/
|
*/
|
||||||
function executeUserEntryPoint(main = process.argv[1]) {
|
function executeUserEntryPoint(main = process.argv[1]) {
|
||||||
const resolvedMain = resolveMainPath(main);
|
let useESMLoader;
|
||||||
const useESMLoader = shouldUseESMLoader(resolvedMain);
|
let resolvedMain;
|
||||||
let mainURL;
|
if (getOptionValue('--entry-url')) {
|
||||||
|
useESMLoader = true;
|
||||||
|
} else {
|
||||||
|
resolvedMain = resolveMainPath(main);
|
||||||
|
useESMLoader = shouldUseESMLoader(resolvedMain);
|
||||||
|
}
|
||||||
// Unless we know we should use the ESM loader to handle the entry point per the checks in `shouldUseESMLoader`, first
|
// 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.
|
// try to run the entry point via the CommonJS loader; and if that fails under certain conditions, retry as ESM.
|
||||||
if (!useESMLoader) {
|
if (!useESMLoader) {
|
||||||
@ -165,9 +170,7 @@ function executeUserEntryPoint(main = process.argv[1]) {
|
|||||||
wrapModuleLoad(main, null, true);
|
wrapModuleLoad(main, null, true);
|
||||||
} else {
|
} else {
|
||||||
const mainPath = resolvedMain || main;
|
const mainPath = resolvedMain || main;
|
||||||
if (mainURL === undefined) {
|
const mainURL = getOptionValue('--entry-url') ? new URL(mainPath, getCWDURL()) : pathToFileURL(mainPath);
|
||||||
mainURL = pathToFileURL(mainPath).href;
|
|
||||||
}
|
|
||||||
|
|
||||||
runEntryPointWithESMLoader((cascadedLoader) => {
|
runEntryPointWithESMLoader((cascadedLoader) => {
|
||||||
// Note that if the graph contains unsettled TLA, this may never resolve
|
// Note that if the graph contains unsettled TLA, this may never resolve
|
||||||
|
@ -407,6 +407,10 @@ EnvironmentOptionsParser::EnvironmentOptionsParser() {
|
|||||||
"Source Map V3 support for stack traces",
|
"Source Map V3 support for stack traces",
|
||||||
&EnvironmentOptions::enable_source_maps,
|
&EnvironmentOptions::enable_source_maps,
|
||||||
kAllowedInEnvvar);
|
kAllowedInEnvvar);
|
||||||
|
AddOption("--entry-url",
|
||||||
|
"Treat the entrypoint as a URL",
|
||||||
|
&EnvironmentOptions::entry_is_url,
|
||||||
|
kAllowedInEnvvar);
|
||||||
AddOption("--experimental-abortcontroller", "", NoOp{}, kAllowedInEnvvar);
|
AddOption("--experimental-abortcontroller", "", NoOp{}, kAllowedInEnvvar);
|
||||||
AddOption("--experimental-eventsource",
|
AddOption("--experimental-eventsource",
|
||||||
"experimental EventSource API",
|
"experimental EventSource API",
|
||||||
|
@ -132,6 +132,7 @@ class EnvironmentOptions : public Options {
|
|||||||
bool experimental_import_meta_resolve = false;
|
bool experimental_import_meta_resolve = false;
|
||||||
std::string input_type; // Value of --input-type
|
std::string input_type; // Value of --input-type
|
||||||
std::string type; // Value of --experimental-default-type
|
std::string type; // Value of --experimental-default-type
|
||||||
|
bool entry_is_url = false;
|
||||||
bool experimental_permission = false;
|
bool experimental_permission = false;
|
||||||
std::vector<std::string> allow_fs_read;
|
std::vector<std::string> allow_fs_read;
|
||||||
std::vector<std::string> allow_fs_write;
|
std::vector<std::string> allow_fs_write;
|
||||||
|
97
test/es-module/test-esm-loader-entry-url.mjs
Normal file
97
test/es-module/test-esm-loader-entry-url.mjs
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
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';
|
||||||
|
|
||||||
|
// Helper function to assert the spawned process
|
||||||
|
async function assertSpawnedProcess(args, options = {}, expected = {}) {
|
||||||
|
const { code, signal, stderr, stdout } = await spawnPromisified(execPath, args, options);
|
||||||
|
|
||||||
|
if (expected.stderr) {
|
||||||
|
assert.match(stderr, expected.stderr);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (expected.stdout) {
|
||||||
|
assert.match(stdout, expected.stdout);
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.strictEqual(code, expected.code ?? 0);
|
||||||
|
assert.strictEqual(signal, expected.signal ?? null);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Common expectation for experimental feature warning in stderr
|
||||||
|
const experimentalFeatureWarning = { stderr: /--entry-url is an experimental feature/ };
|
||||||
|
|
||||||
|
describe('--entry-url', { concurrency: true }, () => {
|
||||||
|
it('should reject loading a path that contains %', async () => {
|
||||||
|
await assertSpawnedProcess(
|
||||||
|
['--entry-url', './test-esm-double-encoding-native%20.mjs'],
|
||||||
|
{ cwd: fixtures.fileURL('es-modules') },
|
||||||
|
{
|
||||||
|
code: 1,
|
||||||
|
stderr: /ERR_MODULE_NOT_FOUND/,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should support loading properly encoded Unix path', async () => {
|
||||||
|
await assertSpawnedProcess(
|
||||||
|
['--entry-url', fixtures.fileURL('es-modules/test-esm-double-encoding-native%20.mjs').pathname],
|
||||||
|
{},
|
||||||
|
experimentalFeatureWarning
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should support loading absolute URLs', async () => {
|
||||||
|
await assertSpawnedProcess(
|
||||||
|
['--entry-url', fixtures.fileURL('printA.js')],
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
...experimentalFeatureWarning,
|
||||||
|
stdout: /^A\r?\n$/,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should support loading relative URLs', async () => {
|
||||||
|
await assertSpawnedProcess(
|
||||||
|
['--entry-url', 'es-modules/print-entrypoint.mjs?key=value#hash'],
|
||||||
|
{ cwd: fixtures.fileURL('./') },
|
||||||
|
{
|
||||||
|
...experimentalFeatureWarning,
|
||||||
|
stdout: /print-entrypoint\.mjs\?key=value#hash\r?\n$/,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should support loading `data:` URLs', async () => {
|
||||||
|
await assertSpawnedProcess(
|
||||||
|
['--entry-url', 'data:text/javascript,console.log(import.meta.url)'],
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
...experimentalFeatureWarning,
|
||||||
|
stdout: /^data:text\/javascript,console\.log\(import\.meta\.url\)\r?\n$/,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should support loading TypeScript URLs', async () => {
|
||||||
|
const typescriptUrls = [
|
||||||
|
'typescript/cts/test-require-ts-file.cts',
|
||||||
|
'typescript/mts/test-import-ts-file.mts',
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const url of typescriptUrls) {
|
||||||
|
await assertSpawnedProcess(
|
||||||
|
['--entry-url', '--experimental-strip-types', fixtures.fileURL(url)],
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
...experimentalFeatureWarning,
|
||||||
|
stdout: /Hello, TypeScript!/,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
1
test/fixtures/es-modules/print-entrypoint.mjs
vendored
Normal file
1
test/fixtures/es-modules/print-entrypoint.mjs
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
console.log(import.meta.url);
|
Loading…
Reference in New Issue
Block a user