From 9ff1d0616627fa83b3d95bad8a38ba697222186b Mon Sep 17 00:00:00 2001 From: Geoffrey Booth Date: Sat, 23 Jul 2022 17:16:27 -0700 Subject: [PATCH 1/2] esm: move package config helpers --- lib/internal/modules/esm/package_config.js | 128 +++++++++++++++++++++ lib/internal/modules/esm/resolve.js | 118 ++----------------- test/parallel/test-bootstrap-modules.js | 1 + 3 files changed, 136 insertions(+), 111 deletions(-) create mode 100644 lib/internal/modules/esm/package_config.js diff --git a/lib/internal/modules/esm/package_config.js b/lib/internal/modules/esm/package_config.js new file mode 100644 index 00000000000000..f26b6ffc7a3487 --- /dev/null +++ b/lib/internal/modules/esm/package_config.js @@ -0,0 +1,128 @@ +'use strict'; + +const { + JSONParse, + SafeMap, + StringPrototypeEndsWith, +} = primordials; +const { URL, fileURLToPath } = require('internal/url'); +const { + ERR_INVALID_PACKAGE_CONFIG, +} = require('internal/errors').codes; + +const packageJsonReader = require('internal/modules/package_json_reader'); + + +/** + * @typedef {string | string[] | Record} Exports + * @typedef {'module' | 'commonjs'} PackageType + * @typedef {{ + * pjsonPath: string, + * exports?: ExportConfig, + * name?: string, + * main?: string, + * type?: PackageType, + * }} PackageConfig + */ + +const packageJSONCache = new SafeMap(); /* string -> PackageConfig */ + + +/** + * @param {string} path + * @param {string} specifier + * @param {string | URL | undefined} base + * @returns {PackageConfig} + */ + function getPackageConfig(path, specifier, base) { + const existing = packageJSONCache.get(path); + if (existing !== undefined) { + return existing; + } + const source = packageJsonReader.read(path).string; + if (source === undefined) { + const packageConfig = { + pjsonPath: path, + exists: false, + main: undefined, + name: undefined, + type: 'none', + exports: undefined, + imports: undefined, + }; + packageJSONCache.set(path, packageConfig); + return packageConfig; + } + + let packageJSON; + try { + packageJSON = JSONParse(source); + } catch (error) { + throw new ERR_INVALID_PACKAGE_CONFIG( + path, + (base ? `"${specifier}" from ` : '') + fileURLToPath(base || specifier), + error.message + ); + } + + let { imports, main, name, type } = packageJSON; + const { exports } = packageJSON; + if (typeof imports !== 'object' || imports === null) { imports = undefined }; + if (typeof main !== 'string') { main = undefined }; + if (typeof name !== 'string') { name = undefined }; + // Ignore unknown types for forwards compatibility + if (type !== 'module' && type !== 'commonjs') { type = 'none' }; + + const packageConfig = { + pjsonPath: path, + exists: true, + main, + name, + type, + exports, + imports, + }; + packageJSONCache.set(path, packageConfig); + return packageConfig; +} + + +/** + * @param {URL | string} resolved + * @returns {PackageConfig} + */ +function getPackageScopeConfig(resolved) { + let packageJSONUrl = new URL('./package.json', resolved); + while (true) { + const packageJSONPath = packageJSONUrl.pathname; + if (StringPrototypeEndsWith(packageJSONPath, 'node_modules/package.json')) { break; } + const packageConfig = getPackageConfig(fileURLToPath(packageJSONUrl), + resolved); + if (packageConfig.exists) { return packageConfig; } + + const lastPackageJSONUrl = packageJSONUrl; + packageJSONUrl = new URL('../package.json', packageJSONUrl); + + // Terminates at root where ../package.json equals ../../package.json + // (can't just check "/package.json" for Windows support). + if (packageJSONUrl.pathname === lastPackageJSONUrl.pathname) { break; } + } + const packageJSONPath = fileURLToPath(packageJSONUrl); + const packageConfig = { + pjsonPath: packageJSONPath, + exists: false, + main: undefined, + name: undefined, + type: 'none', + exports: undefined, + imports: undefined, + }; + packageJSONCache.set(packageJSONPath, packageConfig); + return packageConfig; +} + + +module.exports = { + getPackageConfig, + getPackageScopeConfig, +}; diff --git a/lib/internal/modules/esm/resolve.js b/lib/internal/modules/esm/resolve.js index 435a6f279e9fd1..90ef9bc1de1c3f 100644 --- a/lib/internal/modules/esm/resolve.js +++ b/lib/internal/modules/esm/resolve.js @@ -58,9 +58,11 @@ const { ERR_NETWORK_IMPORT_DISALLOWED, ERR_UNSUPPORTED_ESM_URL_SCHEME, } = require('internal/errors').codes; -const { Module: CJSModule } = require('internal/modules/cjs/loader'); +const { Module: CJSModule } = require('internal/modules/cjs/loader'); const packageJsonReader = require('internal/modules/package_json_reader'); +const { getPackageConfig, getPackageScopeConfig } = require('internal/modules/esm/package_config'); + const userConditions = getOptionValue('--conditions'); const noAddons = getOptionValue('--no-addons'); const addonConditions = noAddons ? [] : ['node-addons']; @@ -74,18 +76,6 @@ const DEFAULT_CONDITIONS = ObjectFreeze([ const DEFAULT_CONDITIONS_SET = new SafeSet(DEFAULT_CONDITIONS); -/** - * @typedef {string | string[] | Record} Exports - * @typedef {'module' | 'commonjs'} PackageType - * @typedef {{ - * pjsonPath: string, - * exports?: ExportConfig, - * name?: string, - * main?: string, - * type?: PackageType, - * }} PackageConfig - */ - const emittedPackageWarnings = new SafeSet(); function emitTrailingSlashPatternDeprecation(match, pjsonUrl, base) { @@ -154,7 +144,6 @@ function getConditionsSet(conditions) { } const realpathCache = new SafeMap(); -const packageJSONCache = new SafeMap(); /* string -> PackageConfig */ /** * @param {string | URL} path @@ -163,99 +152,6 @@ const packageJSONCache = new SafeMap(); /* string -> PackageConfig */ const tryStatSync = (path) => statSync(path, { throwIfNoEntry: false }) ?? new Stats(); -/** - * @param {string} path - * @param {string} specifier - * @param {string | URL | undefined} base - * @returns {PackageConfig} - */ -function getPackageConfig(path, specifier, base) { - const existing = packageJSONCache.get(path); - if (existing !== undefined) { - return existing; - } - const source = packageJsonReader.read(path).string; - if (source === undefined) { - const packageConfig = { - pjsonPath: path, - exists: false, - main: undefined, - name: undefined, - type: 'none', - exports: undefined, - imports: undefined, - }; - packageJSONCache.set(path, packageConfig); - return packageConfig; - } - - let packageJSON; - try { - packageJSON = JSONParse(source); - } catch (error) { - throw new ERR_INVALID_PACKAGE_CONFIG( - path, - (base ? `"${specifier}" from ` : '') + fileURLToPath(base || specifier), - error.message - ); - } - - let { imports, main, name, type } = packageJSON; - const { exports } = packageJSON; - if (typeof imports !== 'object' || imports === null) imports = undefined; - if (typeof main !== 'string') main = undefined; - if (typeof name !== 'string') name = undefined; - // Ignore unknown types for forwards compatibility - if (type !== 'module' && type !== 'commonjs') type = 'none'; - - const packageConfig = { - pjsonPath: path, - exists: true, - main, - name, - type, - exports, - imports, - }; - packageJSONCache.set(path, packageConfig); - return packageConfig; -} - -/** - * @param {URL | string} resolved - * @returns {PackageConfig} - */ -function getPackageScopeConfig(resolved) { - let packageJSONUrl = new URL('./package.json', resolved); - while (true) { - const packageJSONPath = packageJSONUrl.pathname; - if (StringPrototypeEndsWith(packageJSONPath, 'node_modules/package.json')) - break; - const packageConfig = getPackageConfig(fileURLToPath(packageJSONUrl), - resolved); - if (packageConfig.exists) return packageConfig; - - const lastPackageJSONUrl = packageJSONUrl; - packageJSONUrl = new URL('../package.json', packageJSONUrl); - - // Terminates at root where ../package.json equals ../../package.json - // (can't just check "/package.json" for Windows support). - if (packageJSONUrl.pathname === lastPackageJSONUrl.pathname) break; - } - const packageJSONPath = fileURLToPath(packageJSONUrl); - const packageConfig = { - pjsonPath: packageJSONPath, - exists: false, - main: undefined, - name: undefined, - type: 'none', - exports: undefined, - imports: undefined, - }; - packageJSONCache.set(packageJSONPath, packageConfig); - return packageConfig; -} - /** * @param {string | URL} url * @returns {boolean} @@ -272,7 +168,7 @@ function fileExists(url) { * 4. TRY(pkg_url/index.js, pkg_url/index.json, pkg_url/index.node) * 5. NOT_FOUND * @param {URL} packageJSONUrl - * @param {PackageConfig} packageConfig + * @param {import('internal/modules/esm/package_config.js').PackageConfig} packageConfig * @param {string | URL | undefined} base * @returns {URL} */ @@ -609,7 +505,7 @@ function resolvePackageTarget(packageJSONUrl, target, subpath, packageSubpath, /** * - * @param {Exports} exports + * @param {import('internal/modules/esm/package_config.js').Exports} exports * @param {URL} packageJSONUrl * @param {string | URL | undefined} base * @returns {boolean} @@ -640,7 +536,7 @@ function isConditionalExportsMainSugar(exports, packageJSONUrl, base) { /** * @param {URL} packageJSONUrl * @param {string} packageSubpath - * @param {PackageConfig} packageConfig + * @param {import('internal/modules/esm/package_config.js').PackageConfig} packageConfig * @param {string | URL | undefined} base * @param {Set} conditions * @returns {URL} @@ -799,7 +695,7 @@ function packageImportsResolve(name, base, conditions) { /** * @param {URL} url - * @returns {PackageType} + * @returns {import('internal/modules/esm/package_config.js').PackageType} */ function getPackageType(url) { const packageConfig = getPackageScopeConfig(url); diff --git a/test/parallel/test-bootstrap-modules.js b/test/parallel/test-bootstrap-modules.js index bd7221529ca8fc..374fb3eed623a1 100644 --- a/test/parallel/test-bootstrap-modules.js +++ b/test/parallel/test-bootstrap-modules.js @@ -84,6 +84,7 @@ const expectedModules = new Set([ 'NativeModule internal/modules/esm/loader', 'NativeModule internal/modules/esm/module_job', 'NativeModule internal/modules/esm/module_map', + 'NativeModule internal/modules/esm/package_config', 'NativeModule internal/modules/esm/resolve', 'NativeModule internal/modules/esm/translators', 'NativeModule internal/modules/package_json_reader', From c39d5801ddd14c7d594b4aa2e11ca3b0343901f5 Mon Sep 17 00:00:00 2001 From: Geoffrey Booth Date: Sun, 24 Jul 2022 18:11:17 -0700 Subject: [PATCH 2/2] fixup! esm: move package config helpers --- lib/internal/modules/esm/package_config.js | 36 +++++++++++++++------- lib/internal/modules/esm/resolve.js | 9 ++++-- 2 files changed, 32 insertions(+), 13 deletions(-) diff --git a/lib/internal/modules/esm/package_config.js b/lib/internal/modules/esm/package_config.js index f26b6ffc7a3487..89e90d0d997cd2 100644 --- a/lib/internal/modules/esm/package_config.js +++ b/lib/internal/modules/esm/package_config.js @@ -25,7 +25,8 @@ const packageJsonReader = require('internal/modules/package_json_reader'); * }} PackageConfig */ -const packageJSONCache = new SafeMap(); /* string -> PackageConfig */ +/** @type {Map} */ +const packageJSONCache = new SafeMap(); /** @@ -34,7 +35,7 @@ const packageJSONCache = new SafeMap(); /* string -> PackageConfig */ * @param {string | URL | undefined} base * @returns {PackageConfig} */ - function getPackageConfig(path, specifier, base) { +function getPackageConfig(path, specifier, base) { const existing = packageJSONCache.get(path); if (existing !== undefined) { return existing; @@ -67,11 +68,19 @@ const packageJSONCache = new SafeMap(); /* string -> PackageConfig */ let { imports, main, name, type } = packageJSON; const { exports } = packageJSON; - if (typeof imports !== 'object' || imports === null) { imports = undefined }; - if (typeof main !== 'string') { main = undefined }; - if (typeof name !== 'string') { name = undefined }; + if (typeof imports !== 'object' || imports === null) { + imports = undefined; + } + if (typeof main !== 'string') { + main = undefined; + } + if (typeof name !== 'string') { + name = undefined; + } // Ignore unknown types for forwards compatibility - if (type !== 'module' && type !== 'commonjs') { type = 'none' }; + if (type !== 'module' && type !== 'commonjs') { + type = 'none'; + } const packageConfig = { pjsonPath: path, @@ -95,17 +104,22 @@ function getPackageScopeConfig(resolved) { let packageJSONUrl = new URL('./package.json', resolved); while (true) { const packageJSONPath = packageJSONUrl.pathname; - if (StringPrototypeEndsWith(packageJSONPath, 'node_modules/package.json')) { break; } - const packageConfig = getPackageConfig(fileURLToPath(packageJSONUrl), - resolved); - if (packageConfig.exists) { return packageConfig; } + if (StringPrototypeEndsWith(packageJSONPath, 'node_modules/package.json')) { + break; + } + const packageConfig = getPackageConfig(fileURLToPath(packageJSONUrl), resolved); + if (packageConfig.exists) { + return packageConfig; + } const lastPackageJSONUrl = packageJSONUrl; packageJSONUrl = new URL('../package.json', packageJSONUrl); // Terminates at root where ../package.json equals ../../package.json // (can't just check "/package.json" for Windows support). - if (packageJSONUrl.pathname === lastPackageJSONUrl.pathname) { break; } + if (packageJSONUrl.pathname === lastPackageJSONUrl.pathname) { + break; + } } const packageJSONPath = fileURLToPath(packageJSONUrl); const packageConfig = { diff --git a/lib/internal/modules/esm/resolve.js b/lib/internal/modules/esm/resolve.js index 90ef9bc1de1c3f..64a566d6edcc47 100644 --- a/lib/internal/modules/esm/resolve.js +++ b/lib/internal/modules/esm/resolve.js @@ -63,6 +63,11 @@ const { Module: CJSModule } = require('internal/modules/cjs/loader'); const packageJsonReader = require('internal/modules/package_json_reader'); const { getPackageConfig, getPackageScopeConfig } = require('internal/modules/esm/package_config'); +/** + * @typedef {import('internal/modules/esm/package_config.js').PackageConfig} PackageConfig + */ + + const userConditions = getOptionValue('--conditions'); const noAddons = getOptionValue('--no-addons'); const addonConditions = noAddons ? [] : ['node-addons']; @@ -168,7 +173,7 @@ function fileExists(url) { * 4. TRY(pkg_url/index.js, pkg_url/index.json, pkg_url/index.node) * 5. NOT_FOUND * @param {URL} packageJSONUrl - * @param {import('internal/modules/esm/package_config.js').PackageConfig} packageConfig + * @param {PackageConfig} packageConfig * @param {string | URL | undefined} base * @returns {URL} */ @@ -536,7 +541,7 @@ function isConditionalExportsMainSugar(exports, packageJSONUrl, base) { /** * @param {URL} packageJSONUrl * @param {string} packageSubpath - * @param {import('internal/modules/esm/package_config.js').PackageConfig} packageConfig + * @param {PackageConfig} packageConfig * @param {string | URL | undefined} base * @param {Set} conditions * @returns {URL}