Skip to content
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Empty file added benchmark/fixtures/empty.mjs
Empty file.
34 changes: 34 additions & 0 deletions benchmark/fixtures/import-builtins.mjs
Copy link
Contributor

Choose a reason for hiding this comment

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

Is node:sqlite omitted intentionally from here?

Copy link
Member Author

@joyeecheung joyeecheung Feb 16, 2026

Choose a reason for hiding this comment

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

It's copied from the require-builtins which predated sqlite. Can update both in a follow up (I think there are probably a few other newer builtins not included too).

Copy link
Contributor

Choose a reason for hiding this comment

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

I'm wondering if there could be a new lint rule just to keep these 2 files up to date at all times? I'm not knowledgeable enough in ESLint to implement this, but maybe it is a viable path to ensure these files do not get left behind as builtins change (added, removed, renamed) over time.

Copy link
Member Author

@joyeecheung joyeecheung Feb 16, 2026

Choose a reason for hiding this comment

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

IMO a lint rule is a bit of an overkill - that kind of put the pressure on anyone who adds a new built-in to run the benchmark and measure its loading performance when that's not really a universally important metrics for all builtins. For builtins that are changed or removed we have already smoking benchmark tests to ensure these benchmarks don't throw.

Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import 'node:async_hooks';
import 'node:assert';
import 'node:buffer';
import 'node:child_process';
import 'node:console';
import 'node:constants';
import 'node:crypto';
import 'node:cluster';
import 'node:dgram';
import 'node:dns';
import 'node:domain';
import 'node:events';
import 'node:fs';
import 'node:http';
import 'node:http2';
import 'node:https';
import 'node:module';
import 'node:net';
import 'node:os';
import 'node:path';
import 'node:perf_hooks';
import 'node:process';
import 'node:querystring';
import 'node:readline';
import 'node:repl';
import 'node:stream';
import 'node:string_decoder';
import 'node:timers';
import 'node:tls';
import 'node:tty';
import 'node:url';
import 'node:util';
import 'node:vm';
import 'node:zlib';
10 changes: 6 additions & 4 deletions benchmark/misc/startup-core.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,11 @@ let Worker; // Lazy loaded in main

const bench = common.createBenchmark(main, {
script: [
'benchmark/fixtures/require-builtins',
'test/fixtures/semicolon',
'test/fixtures/snapshot/typescript',
'benchmark/fixtures/empty.mjs',
'benchmark/fixtures/import-builtins.mjs',
'benchmark/fixtures/require-builtins.js',
'test/fixtures/semicolon.js',
'test/fixtures/snapshot/typescript.js',
],
mode: ['process', 'worker'],
n: [30],
Expand Down Expand Up @@ -58,7 +60,7 @@ function spawnWorker(script, bench, state) {
}

function main({ n, script, mode }) {
script = path.resolve(__dirname, '../../', `${script}.js`);
script = path.resolve(__dirname, '../../', `${script}`);
const warmup = 3;
const state = { n, finished: -warmup };
if (mode === 'worker') {
Expand Down
1 change: 1 addition & 0 deletions lib/internal/bootstrap/switches/is_main_thread.js
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,7 @@ require('util');
require('url'); // eslint-disable-line no-restricted-modules
internalBinding('module_wrap');
require('internal/modules/cjs/loader');
require('internal/modules/esm/loader');
require('internal/modules/esm/utils');

// Needed to refresh the time origin.
Expand Down
70 changes: 0 additions & 70 deletions lib/internal/modules/esm/formats.js

This file was deleted.

64 changes: 58 additions & 6 deletions lib/internal/modules/esm/get_format.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,63 @@ const {
StringPrototypeSlice,
} = primordials;
const { getOptionValue } = require('internal/options');
const {
extensionFormatMap,
getFormatOfExtensionlessFile,
mimeToFormat,
} = require('internal/modules/esm/formats');
const { getValidatedPath } = require('internal/fs/utils');
const fsBindings = internalBinding('fs');
const { internal: internalConstants } = internalBinding('constants');

const extensionFormatMap = {
'__proto__': null,
'.cjs': 'commonjs',
'.js': 'module',
'.json': 'json',
'.mjs': 'module',
'.wasm': 'wasm',
};

function initializeExtensionFormatMap() {
if (getOptionValue('--experimental-addon-modules')) {
extensionFormatMap['.node'] = 'addon';
}

if (getOptionValue('--strip-types')) {
extensionFormatMap['.ts'] = 'module-typescript';
extensionFormatMap['.mts'] = 'module-typescript';
extensionFormatMap['.cts'] = 'commonjs-typescript';
}
}

const detectModule = getOptionValue('--experimental-detect-module');
/**
* @param {string} mime
* @returns {string | null}
*/
function mimeToFormat(mime) {
if (
RegExpPrototypeExec(
/^\s*(text|application)\/javascript\s*(;\s*charset=utf-?8\s*)?$/i,
mime,
) !== null
) { return 'module'; }
if (mime === 'application/json') { return 'json'; }
if (mime === 'application/wasm') { return 'wasm'; }
return null;
}

/**
* For extensionless files in a `module` package scope, we check the file contents to disambiguate between ES module
* JavaScript and Wasm.
* We do this by taking advantage of the fact that all Wasm files start with the header `0x00 0x61 0x73 0x6d` (`_asm`).
* @param {URL} url
* @returns {'wasm'|'module'}
*/
function getFormatOfExtensionlessFile(url) {
const path = getValidatedPath(url);
switch (fsBindings.getFormatOfExtensionlessFile(path)) {
case internalConstants.EXTENSIONLESS_FORMAT_WASM:
return 'wasm';
default:
return 'module';
}
}
const { containsModuleSyntax } = internalBinding('contextify');
const { getPackageScopeConfig, getPackageType } = require('internal/modules/package_json_reader');
const { fileURLToPath } = require('internal/url');
Expand All @@ -35,6 +85,7 @@ const protocolHandlers = {
* @returns {'module'|'commonjs'}
*/
function detectModuleFormat(source, url) {
const detectModule = getOptionValue('--experimental-detect-module');
if (!source) { return detectModule ? null : 'commonjs'; }
if (!detectModule) { return 'commonjs'; }
return containsModuleSyntax(`${source}`, fileURLToPath(url), url) ? 'module' : 'commonjs';
Expand Down Expand Up @@ -216,4 +267,5 @@ module.exports = {
defaultGetFormatWithoutErrors,
extensionFormatMap,
extname,
initializeExtensionFormatMap,
};
30 changes: 6 additions & 24 deletions lib/internal/modules/esm/loader.js
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,6 @@ const {
validateLoadSloppy,
} = require('internal/modules/customization_hooks');

let defaultResolve, defaultLoadSync;

const { tracingChannel } = require('diagnostics_channel');
const onImport = tracingChannel('module.import');

Expand Down Expand Up @@ -100,19 +98,9 @@ function newLoadCache() {
return new LoadCache();
}

let _translators;
function lazyLoadTranslators() {
_translators ??= require('internal/modules/esm/translators');
return _translators;
}

/**
* Lazy-load translators to avoid potentially unnecessary work at startup (ex if ESM is not used).
* @returns {import('./translators.js').Translators}
*/
function getTranslators() {
return lazyLoadTranslators().translators;
}
const { translators } = require('internal/modules/esm/translators');
const { defaultResolve } = require('internal/modules/esm/resolve');
const { defaultLoadSync, throwUnknownModuleFormat } = require('internal/modules/esm/load');

/**
* Generate message about potential race condition caused by requiring a cached module that has started
Expand Down Expand Up @@ -181,11 +169,6 @@ class ModuleLoader {
*/
loadCache = newLoadCache();

/**
* Methods which translate input code or other information into ES modules
*/
translators = getTranslators();

/**
* @see {AsyncLoaderHooks.isForAsyncLoaderHookWorker}
* Shortcut to this.#asyncLoaderHooks.isForAsyncLoaderHookWorker.
Expand Down Expand Up @@ -459,7 +442,7 @@ class ModuleLoader {
#translate(url, translateContext, parentURL) {
const { translatorKey, format } = translateContext;
this.validateLoadResult(url, format);
const translator = getTranslators().get(translatorKey);
const translator = translators.get(translatorKey);

if (!translator) {
throw new ERR_UNKNOWN_MODULE_FORMAT(translatorKey, url);
Expand Down Expand Up @@ -710,7 +693,7 @@ class ModuleLoader {
if (cachedResult != null) {
return cachedResult;
}
defaultResolve ??= require('internal/modules/esm/resolve').defaultResolve;

const result = defaultResolve(specifier, context);
this.#resolveCache.set(requestKey, parentURL, result);
return result;
Expand Down Expand Up @@ -787,7 +770,6 @@ class ModuleLoader {
if (this.#asyncLoaderHooks?.loadSync) {
return this.#asyncLoaderHooks.loadSync(url, context);
}
defaultLoadSync ??= require('internal/modules/esm/load').defaultLoadSync;
return defaultLoadSync(url, context);
}

Expand All @@ -813,7 +795,7 @@ class ModuleLoader {

validateLoadResult(url, format) {
if (format == null) {
require('internal/modules/esm/load').throwUnknownModuleFormat(url, format);
throwUnknownModuleFormat(url, format);
}
}

Expand Down
16 changes: 4 additions & 12 deletions lib/internal/modules/esm/resolve.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,6 @@ const { realpathSync } = require('fs');
const { getOptionValue } = require('internal/options');
// Do not eagerly grab .manifest, it may be in TDZ
const { sep, posix: { relative: relativePosixPath }, resolve } = require('path');
const preserveSymlinks = getOptionValue('--preserve-symlinks');
const preserveSymlinksMain = getOptionValue('--preserve-symlinks-main');
const inputTypeFlag = getOptionValue('--input-type');
const { URL, pathToFileURL, fileURLToPath, isURL, URLParse } = require('internal/url');
const { getCWDURL, setOwnProperty } = require('internal/util');
const { canParse: URLCanParse } = internalBinding('url');
Expand All @@ -49,8 +46,7 @@ const {
ERR_UNSUPPORTED_DIR_IMPORT,
ERR_UNSUPPORTED_RESOLVE_REQUEST,
} = require('internal/errors').codes;

const { Module: CJSModule } = require('internal/modules/cjs/loader');
const { defaultGetFormatWithoutErrors } = require('internal/modules/esm/get_format');
const { getConditionsSet } = require('internal/modules/esm/utils');
const packageJsonReader = require('internal/modules/package_json_reader');
const internalFsBinding = internalBinding('fs');
Expand Down Expand Up @@ -873,6 +869,7 @@ function moduleResolve(specifier, base, conditions, preserveSymlinks) {
*/
function resolveAsCommonJS(specifier, parentURL) {
try {
const { Module: CJSModule } = require('internal/modules/cjs/loader');
const parent = fileURLToPath(parentURL);
const tmpModule = new CJSModule(parent, null);
tmpModule.paths = CJSModule._nodeModulePaths(parent);
Expand Down Expand Up @@ -982,7 +979,7 @@ function defaultResolve(specifier, context = {}) {
// input, to avoid user confusion over how expansive the effect of the
// flag should be (i.e. entry point only, package scope surrounding the
// entry point, etc.).
if (inputTypeFlag) { throw new ERR_INPUT_TYPE_NOT_ALLOWED(); }
if (getOptionValue('--input-type')) { throw new ERR_INPUT_TYPE_NOT_ALLOWED(); }
}

conditions = getConditionsSet(conditions);
Expand All @@ -992,7 +989,7 @@ function defaultResolve(specifier, context = {}) {
specifier,
parentURL,
conditions,
isMain ? preserveSymlinksMain : preserveSymlinks,
isMain ? getOptionValue('--preserve-symlinks-main') : getOptionValue('--preserve-symlinks'),
);
} catch (error) {
// Try to give the user a hint of what would have been the
Expand Down Expand Up @@ -1046,8 +1043,3 @@ module.exports = {
packageResolve,
throwIfInvalidParentURL,
};

// cycle
const {
defaultGetFormatWithoutErrors,
} = require('internal/modules/esm/get_format');
Loading
Loading