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.
  • Loading branch information
H4ad committed Jun 5, 2023
1 parent 4bb06db commit 9301722
Show file tree
Hide file tree
Showing 5 changed files with 377 additions and 44 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);
}
115 changes: 71 additions & 44 deletions lib/internal/modules/esm/resolve.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,14 +36,16 @@ 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: URLLegacyMainResolve } = internalBinding('fs');
const {
ERR_INPUT_TYPE_NOT_ALLOWED,
ERR_INVALID_ARG_TYPE,
ERR_INVALID_MODULE_SPECIFIER,
ERR_INVALID_PACKAGE_CONFIG,
ERR_INVALID_PACKAGE_TARGET,
ERR_INVALID_URL,
ERR_MANIFEST_DEPENDENCY_MISSING,
ERR_MODULE_NOT_FOUND,
ERR_PACKAGE_IMPORT_NOT_DEFINED,
Expand Down Expand Up @@ -132,13 +134,58 @@ 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 mainResolveExtensions = [
'',
'.js',
'.json',
'.node',
'/index.js',
'/index.json',
'/index.node',
'./index.js',
'./index.json',
'./index.node',
];

const mainResolveReturnType = {
kInvalidUrl: -2,
kModuleNotFound: -1,
// 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,
/**
* Get the URL for the guess resolved by URLLegacyMainResolve
*
* @param {number} resolvedOption
* @param {string} baseUrl
* @param {URL} packageJSONUrl
* @returns {string}
*/
getGuessByResolvedOption: function(resolvedOption, baseUrl, packageJSONUrl) {
return new URL(baseUrl + mainResolveExtensions[resolvedOption], packageJSONUrl);
},
/**
* When resolved option is equal or less than 6, means we found a valid file
* with the prefix that came from packageConfig.main
*
* @param {number} resolvedOption
* @param {PackageConfig} packageConfig
* @returns {string}
*/
getBaseUrlByResolvedOption: function (resolvedOption, packageConfig) {
return resolvedOption <= mainResolveReturnType.kResolvedByMainNode ? `./${packageConfig.main}` : '';
},
};

/**
* Legacy CommonJS main resolution:
Expand All @@ -153,44 +200,23 @@ 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;
const resolvedOption = URLLegacyMainResolve(packageJSONUrl.href, packageConfig.main);

if (resolvedOption < mainResolveReturnType.kResolvedByMain) {
if (resolvedOption === mainResolveReturnType.kInvalidUrl) {
throw new ERR_INVALID_URL(packageConfig.main || './index.js');
} else {
throw new ERR_MODULE_NOT_FOUND(
fileURLToPath(new URL('.', packageJSONUrl)), fileURLToPath(base));
}
// 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;
}
// Not found.
throw new ERR_MODULE_NOT_FOUND(
fileURLToPath(new URL('.', packageJSONUrl)), fileURLToPath(base));

const baseUrl = mainResolveReturnType.getBaseUrlByResolvedOption(resolvedOption, packageConfig);
const guess = mainResolveReturnType.getGuessByResolvedOption(resolvedOption, baseUrl, packageJSONUrl);

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

return guess;
}

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

// cycle
Expand Down
4 changes: 4 additions & 0 deletions src/node_errors.h
Original file line number Diff line number Diff line change
Expand Up @@ -68,11 +68,14 @@ 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_SCHEME, TypeError) \
V(ERR_MEMORY_ALLOCATION_FAILED, Error) \
V(ERR_MESSAGE_TARGET_CONTEXT_UNAVAILABLE, Error) \
V(ERR_MISSING_ARGS, TypeError) \
Expand Down Expand Up @@ -163,6 +166,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
Loading

0 comments on commit 9301722

Please sign in to comment.