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.

PR-URL: nodejs#48325
Reviewed-By: Yagiz Nizipli <yagiz@nizipli.com>
Reviewed-By: Geoffrey Booth <webadmin@geoffreybooth.com>
Reviewed-By: Rafael Gonzaga <rafael.nunu@hotmail.com>
  • Loading branch information
H4ad committed Jul 3, 2023
1 parent 329813d commit a2a8e31
Show file tree
Hide file tree
Showing 10 changed files with 715 additions and 45 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);
}
91 changes: 46 additions & 45 deletions lib/internal/modules/esm/resolve.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,9 @@ const preserveSymlinksMain = getOptionValue('--preserve-symlinks-main');
const experimentalNetworkImports =
getOptionValue('--experimental-network-imports');
const typeFlag = getOptionValue('--input-type');
const { URL, pathToFileURL, fileURLToPath, isURL, toPathIfFileURL } = require('internal/url');
const { URL, pathToFileURL, fileURLToPath, isURL } = require('internal/url');
const { canParse: URLCanParse } = internalBinding('url');
const { legacyMainResolve: FSLegacyMainResolve } = internalBinding('fs');
const {
ERR_INPUT_TYPE_NOT_ALLOWED,
ERR_INVALID_ARG_TYPE,
Expand Down Expand Up @@ -133,13 +134,34 @@ function emitLegacyIndexDeprecation(url, packageJSONUrl, base, main) {

const realpathCache = new SafeMap();

/**
* @param {string | URL} url
* @returns {boolean}
*/
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:
Expand All @@ -154,44 +176,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)));
else if (fileExists(guess = new URL(`./${packageConfig.main}.json`,
packageJSONUrl)));
else if (fileExists(guess = new URL(`./${packageConfig.main}.node`,
packageJSONUrl)));
else if (fileExists(guess = new URL(`./${packageConfig.main}/index.js`,
packageJSONUrl)));
else if (fileExists(guess = new URL(`./${packageConfig.main}/index.json`,
packageJSONUrl)));
else if (fileExists(guess = new URL(`./${packageConfig.main}/index.node`,
packageJSONUrl)));
else guess = undefined;
if (guess) {
emitLegacyIndexDeprecation(guess, packageJSONUrl, base,
packageConfig.main);
return guess;
}
// Fallthrough.
}
if (fileExists(guess = new URL('./index.js', packageJSONUrl)));
// So fs.
else if (fileExists(guess = new URL('./index.json', packageJSONUrl)));
else if (fileExists(guess = new URL('./index.node', packageJSONUrl)));
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;
}

const encodedSepRegEx = /%2F|%5C/i;
Expand Down Expand Up @@ -1078,6 +1078,7 @@ module.exports = {
packageExportsResolve,
packageImportsResolve,
throwIfInvalidParentURL,
legacyMainResolve,
};

// cycle
Expand Down
5 changes: 5 additions & 0 deletions src/node_errors.h
Original file line number Diff line number Diff line change
Expand Up @@ -69,18 +69,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 @@ -165,6 +169,7 @@ ERRORS_WITH_CODE(V)
V(ERR_INVALID_STATE, "Invalid state") \
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

0 comments on commit a2a8e31

Please sign in to comment.