Skip to content

Commit

Permalink
src,lib: reducing C++ calls of esm legacy main resolve
Browse files Browse the repository at this point in the history
Instead of many C++ calls, now we make only one C++ call
to return a enum number that represents the selected state.

Backport-PR-URL: nodejs#48325
  • Loading branch information
H4ad committed Dec 2, 2023
1 parent 7fa1551 commit f260c35
Show file tree
Hide file tree
Showing 10 changed files with 707 additions and 43 deletions.
53 changes: 53 additions & 0 deletions benchmark/esm/esm-legacyMainResolve.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
// Tests the impact on eager operations required for policies affecting
// general startup, does not test lazy operations
'use strict';
const fs = require('node:fs');
const path = require('node:path');
const common = require('../common.js');

const tmpdir = require('../../test/common/tmpdir.js');
const { pathToFileURL } = require('node:url');

const benchmarkDirectory =
path.resolve(tmpdir.path, 'benchmark-import-meta-resolve');

const configs = {
n: [1e4],
packageJsonUrl: [
'node_modules/test/package.json',
],
packageConfigMain: ['', './index.js'],
resolvedFile: [
'node_modules/test/index.js',
'node_modules/test/index.json',
'node_modules/test/index.node',
'node_modules/non-exist',
],
};

const options = {
flags: ['--expose-internals'],
};

const bench = common.createBenchmark(main, configs, options);

function main(conf) {
const { legacyMainResolve } = require('internal/modules/esm/resolve');
tmpdir.refresh();

fs.mkdirSync(path.join(benchmarkDirectory, 'node_modules', 'test'), { recursive: true });
fs.writeFileSync(path.join(benchmarkDirectory, conf.resolvedFile), '\n');

const packageJsonUrl = pathToFileURL(conf.packageJsonUrl);
const packageConfigMain = { main: conf.packageConfigMain };

bench.start();

for (let i = 0; i < conf.n; i++) {
try {
legacyMainResolve(packageJsonUrl, packageConfigMain, undefined);
} catch { /* empty */ }
}

bench.end(conf.n);
}
87 changes: 45 additions & 42 deletions lib/internal/modules/esm/resolve.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ const inputTypeFlag = getOptionValue('--input-type');
const { URL, pathToFileURL, fileURLToPath, isURL, toPathIfFileURL } = require('internal/url');
const { getCWDURL } = require('internal/util');
const { canParse: URLCanParse } = internalBinding('url');
const { legacyMainResolve: FSLegacyMainResolve } = internalBinding('fs');
const {
ERR_INPUT_TYPE_NOT_ALLOWED,
ERR_INVALID_ARG_TYPE,
Expand Down Expand Up @@ -161,6 +162,35 @@ function fileExists(url) {
return internalModuleStat(toNamespacedPath(toPathIfFileURL(url))) === 0;
}

const legacyMainResolveExtensions = [
'',
'.js',
'.json',
'.node',
'/index.js',
'/index.json',
'/index.node',
'./index.js',
'./index.json',
'./index.node',
];

const legacyMainResolveExtensionsIndexes = {
// 0-6: when packageConfig.main is defined
kResolvedByMain: 0,
kResolvedByMainJs: 1,
kResolvedByMainJson: 2,
kResolvedByMainNode: 3,
kResolvedByMainIndexJs: 4,
kResolvedByMainIndexJson: 5,
kResolvedByMainIndexNode: 6,
// 7-9: when packageConfig.main is NOT defined,
// or when the previous case didn't found the file
kResolvedByPackageAndJs: 7,
kResolvedByPackageAndJson: 8,
kResolvedByPackageAndNode: 9,
};

/**
* Legacy CommonJS main resolution:
* 1. let M = pkg_url + (json main field)
Expand All @@ -174,49 +204,22 @@ function fileExists(url) {
* @returns {URL}
*/
function legacyMainResolve(packageJSONUrl, packageConfig, base) {
let guess;
if (packageConfig.main !== undefined) {
// Note: fs check redundances will be handled by Descriptor cache here.
if (fileExists(guess = new URL(`./${packageConfig.main}`, packageJSONUrl))) {
return guess;
} else if (fileExists(guess = new URL(`./${packageConfig.main}.js`, packageJSONUrl))) {
// Handled below.
} else if (fileExists(guess = new URL(`./${packageConfig.main}.json`, packageJSONUrl))) {
// Handled below.
} else if (fileExists(guess = new URL(`./${packageConfig.main}.node`, packageJSONUrl))) {
// Handled below.
} else if (fileExists(guess = new URL(`./${packageConfig.main}/index.js`, packageJSONUrl))) {
// Handled below.
} else if (fileExists(guess = new URL(`./${packageConfig.main}/index.json`, packageJSONUrl))) {
// Handled below.
} else if (fileExists(guess = new URL(`./${packageConfig.main}/index.node`, packageJSONUrl))) {
// Handled below.
} else {
guess = undefined;
}
if (guess) {
emitLegacyIndexDeprecation(guess, packageJSONUrl, base,
packageConfig.main);
return guess;
}
// Fallthrough.
}
if (fileExists(guess = new URL('./index.js', packageJSONUrl))) {
// Handled below.
} else if (fileExists(guess = new URL('./index.json', packageJSONUrl))) {
// Handled below.
} else if (fileExists(guess = new URL('./index.node', packageJSONUrl))) {
// Handled below.
} else {
guess = undefined;
}
if (guess) {
emitLegacyIndexDeprecation(guess, packageJSONUrl, base, packageConfig.main);
return guess;
const packageJsonUrlString = packageJSONUrl.href;

if (typeof packageJsonUrlString !== 'string') {
throw new ERR_INVALID_ARG_TYPE('packageJSONUrl', ['URL'], packageJSONUrl);
}
// Not found.
throw new ERR_MODULE_NOT_FOUND(
fileURLToPath(new URL('.', packageJSONUrl)), fileURLToPath(base));

const baseStringified = isURL(base) ? base.href : base;

const resolvedOption = FSLegacyMainResolve(packageJsonUrlString, packageConfig.main, baseStringified);

const baseUrl = resolvedOption <= legacyMainResolveExtensionsIndexes.kResolvedByMainIndexNode ? `./${packageConfig.main}` : '';
const resolvedUrl = new URL(baseUrl + legacyMainResolveExtensions[resolvedOption], packageJSONUrl);

emitLegacyIndexDeprecation(resolvedUrl, packageJSONUrl, base, packageConfig.main);

return resolvedUrl;
}

/**
Expand Down
6 changes: 6 additions & 0 deletions src/node_errors.h
Original file line number Diff line number Diff line change
Expand Up @@ -67,17 +67,22 @@ void AppendExceptionLine(Environment* env,
V(ERR_INVALID_ARG_VALUE, TypeError) \
V(ERR_OSSL_EVP_INVALID_DIGEST, Error) \
V(ERR_INVALID_ARG_TYPE, TypeError) \
V(ERR_INVALID_FILE_URL_HOST, TypeError) \
V(ERR_INVALID_FILE_URL_PATH, TypeError) \
V(ERR_INVALID_OBJECT_DEFINE_PROPERTY, TypeError) \
V(ERR_INVALID_MODULE, Error) \
V(ERR_INVALID_STATE, Error) \
V(ERR_INVALID_THIS, TypeError) \
V(ERR_INVALID_TRANSFER_OBJECT, TypeError) \
V(ERR_INVALID_URL, TypeError) \
V(ERR_INVALID_URL_SCHEME, TypeError) \
V(ERR_MEMORY_ALLOCATION_FAILED, Error) \
V(ERR_MESSAGE_TARGET_CONTEXT_UNAVAILABLE, Error) \
V(ERR_MISSING_ARGS, TypeError) \
V(ERR_MISSING_TRANSFERABLE_IN_TRANSFER_LIST, TypeError) \
V(ERR_MISSING_PASSPHRASE, TypeError) \
V(ERR_MISSING_PLATFORM_FOR_WORKER, Error) \
V(ERR_MODULE_NOT_FOUND, Error) \
V(ERR_NON_CONTEXT_AWARE_DISABLED, Error) \
V(ERR_OUT_OF_RANGE, RangeError) \
V(ERR_SCRIPT_EXECUTION_INTERRUPTED, Error) \
Expand Down Expand Up @@ -160,6 +165,7 @@ ERRORS_WITH_CODE(V)
V(ERR_INVALID_MODULE, "No such module") \
V(ERR_INVALID_THIS, "Value of \"this\" is the wrong type") \
V(ERR_INVALID_TRANSFER_OBJECT, "Found invalid object in transferList") \
V(ERR_INVALID_URL_SCHEME, "The URL must be of scheme file:") \
V(ERR_MEMORY_ALLOCATION_FAILED, "Failed to allocate memory") \
V(ERR_OSSL_EVP_INVALID_DIGEST, "Invalid digest used") \
V(ERR_MESSAGE_TARGET_CONTEXT_UNAVAILABLE, \
Expand Down
Loading

0 comments on commit f260c35

Please sign in to comment.