Skip to content

Commit e072c3a

Browse files
GeoffreyBoothruyadorno
authored andcommitted
esm: move package config helpers
PR-URL: #43967 Reviewed-By: Jacob Smith <jacob@frende.me> Reviewed-By: Michaël Zasso <targos@protonmail.com> Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com>
1 parent 8c2d19b commit e072c3a

File tree

3 files changed

+153
-109
lines changed

3 files changed

+153
-109
lines changed
+142
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
'use strict';
2+
3+
const {
4+
JSONParse,
5+
SafeMap,
6+
StringPrototypeEndsWith,
7+
} = primordials;
8+
const { URL, fileURLToPath } = require('internal/url');
9+
const {
10+
ERR_INVALID_PACKAGE_CONFIG,
11+
} = require('internal/errors').codes;
12+
13+
const packageJsonReader = require('internal/modules/package_json_reader');
14+
15+
16+
/**
17+
* @typedef {string | string[] | Record<string, unknown>} Exports
18+
* @typedef {'module' | 'commonjs'} PackageType
19+
* @typedef {{
20+
* pjsonPath: string,
21+
* exports?: ExportConfig,
22+
* name?: string,
23+
* main?: string,
24+
* type?: PackageType,
25+
* }} PackageConfig
26+
*/
27+
28+
/** @type {Map<string, PackageConfig>} */
29+
const packageJSONCache = new SafeMap();
30+
31+
32+
/**
33+
* @param {string} path
34+
* @param {string} specifier
35+
* @param {string | URL | undefined} base
36+
* @returns {PackageConfig}
37+
*/
38+
function getPackageConfig(path, specifier, base) {
39+
const existing = packageJSONCache.get(path);
40+
if (existing !== undefined) {
41+
return existing;
42+
}
43+
const source = packageJsonReader.read(path).string;
44+
if (source === undefined) {
45+
const packageConfig = {
46+
pjsonPath: path,
47+
exists: false,
48+
main: undefined,
49+
name: undefined,
50+
type: 'none',
51+
exports: undefined,
52+
imports: undefined,
53+
};
54+
packageJSONCache.set(path, packageConfig);
55+
return packageConfig;
56+
}
57+
58+
let packageJSON;
59+
try {
60+
packageJSON = JSONParse(source);
61+
} catch (error) {
62+
throw new ERR_INVALID_PACKAGE_CONFIG(
63+
path,
64+
(base ? `"${specifier}" from ` : '') + fileURLToPath(base || specifier),
65+
error.message
66+
);
67+
}
68+
69+
let { imports, main, name, type } = packageJSON;
70+
const { exports } = packageJSON;
71+
if (typeof imports !== 'object' || imports === null) {
72+
imports = undefined;
73+
}
74+
if (typeof main !== 'string') {
75+
main = undefined;
76+
}
77+
if (typeof name !== 'string') {
78+
name = undefined;
79+
}
80+
// Ignore unknown types for forwards compatibility
81+
if (type !== 'module' && type !== 'commonjs') {
82+
type = 'none';
83+
}
84+
85+
const packageConfig = {
86+
pjsonPath: path,
87+
exists: true,
88+
main,
89+
name,
90+
type,
91+
exports,
92+
imports,
93+
};
94+
packageJSONCache.set(path, packageConfig);
95+
return packageConfig;
96+
}
97+
98+
99+
/**
100+
* @param {URL | string} resolved
101+
* @returns {PackageConfig}
102+
*/
103+
function getPackageScopeConfig(resolved) {
104+
let packageJSONUrl = new URL('./package.json', resolved);
105+
while (true) {
106+
const packageJSONPath = packageJSONUrl.pathname;
107+
if (StringPrototypeEndsWith(packageJSONPath, 'node_modules/package.json')) {
108+
break;
109+
}
110+
const packageConfig = getPackageConfig(fileURLToPath(packageJSONUrl), resolved);
111+
if (packageConfig.exists) {
112+
return packageConfig;
113+
}
114+
115+
const lastPackageJSONUrl = packageJSONUrl;
116+
packageJSONUrl = new URL('../package.json', packageJSONUrl);
117+
118+
// Terminates at root where ../package.json equals ../../package.json
119+
// (can't just check "/package.json" for Windows support).
120+
if (packageJSONUrl.pathname === lastPackageJSONUrl.pathname) {
121+
break;
122+
}
123+
}
124+
const packageJSONPath = fileURLToPath(packageJSONUrl);
125+
const packageConfig = {
126+
pjsonPath: packageJSONPath,
127+
exists: false,
128+
main: undefined,
129+
name: undefined,
130+
type: 'none',
131+
exports: undefined,
132+
imports: undefined,
133+
};
134+
packageJSONCache.set(packageJSONPath, packageConfig);
135+
return packageConfig;
136+
}
137+
138+
139+
module.exports = {
140+
getPackageConfig,
141+
getPackageScopeConfig,
142+
};

lib/internal/modules/esm/resolve.js

+10-109
Original file line numberDiff line numberDiff line change
@@ -58,9 +58,16 @@ const {
5858
ERR_NETWORK_IMPORT_DISALLOWED,
5959
ERR_UNSUPPORTED_ESM_URL_SCHEME,
6060
} = require('internal/errors').codes;
61-
const { Module: CJSModule } = require('internal/modules/cjs/loader');
6261

62+
const { Module: CJSModule } = require('internal/modules/cjs/loader');
6363
const packageJsonReader = require('internal/modules/package_json_reader');
64+
const { getPackageConfig, getPackageScopeConfig } = require('internal/modules/esm/package_config');
65+
66+
/**
67+
* @typedef {import('internal/modules/esm/package_config.js').PackageConfig} PackageConfig
68+
*/
69+
70+
6471
const userConditions = getOptionValue('--conditions');
6572
const noAddons = getOptionValue('--no-addons');
6673
const addonConditions = noAddons ? [] : ['node-addons'];
@@ -74,18 +81,6 @@ const DEFAULT_CONDITIONS = ObjectFreeze([
7481

7582
const DEFAULT_CONDITIONS_SET = new SafeSet(DEFAULT_CONDITIONS);
7683

77-
/**
78-
* @typedef {string | string[] | Record<string, unknown>} Exports
79-
* @typedef {'module' | 'commonjs'} PackageType
80-
* @typedef {{
81-
* pjsonPath: string,
82-
* exports?: ExportConfig,
83-
* name?: string,
84-
* main?: string,
85-
* type?: PackageType,
86-
* }} PackageConfig
87-
*/
88-
8984
const emittedPackageWarnings = new SafeSet();
9085

9186
function emitTrailingSlashPatternDeprecation(match, pjsonUrl, base) {
@@ -154,7 +149,6 @@ function getConditionsSet(conditions) {
154149
}
155150

156151
const realpathCache = new SafeMap();
157-
const packageJSONCache = new SafeMap(); /* string -> PackageConfig */
158152

159153
/**
160154
* @param {string | URL} path
@@ -163,99 +157,6 @@ const packageJSONCache = new SafeMap(); /* string -> PackageConfig */
163157
const tryStatSync =
164158
(path) => statSync(path, { throwIfNoEntry: false }) ?? new Stats();
165159

166-
/**
167-
* @param {string} path
168-
* @param {string} specifier
169-
* @param {string | URL | undefined} base
170-
* @returns {PackageConfig}
171-
*/
172-
function getPackageConfig(path, specifier, base) {
173-
const existing = packageJSONCache.get(path);
174-
if (existing !== undefined) {
175-
return existing;
176-
}
177-
const source = packageJsonReader.read(path).string;
178-
if (source === undefined) {
179-
const packageConfig = {
180-
pjsonPath: path,
181-
exists: false,
182-
main: undefined,
183-
name: undefined,
184-
type: 'none',
185-
exports: undefined,
186-
imports: undefined,
187-
};
188-
packageJSONCache.set(path, packageConfig);
189-
return packageConfig;
190-
}
191-
192-
let packageJSON;
193-
try {
194-
packageJSON = JSONParse(source);
195-
} catch (error) {
196-
throw new ERR_INVALID_PACKAGE_CONFIG(
197-
path,
198-
(base ? `"${specifier}" from ` : '') + fileURLToPath(base || specifier),
199-
error.message
200-
);
201-
}
202-
203-
let { imports, main, name, type } = packageJSON;
204-
const { exports } = packageJSON;
205-
if (typeof imports !== 'object' || imports === null) imports = undefined;
206-
if (typeof main !== 'string') main = undefined;
207-
if (typeof name !== 'string') name = undefined;
208-
// Ignore unknown types for forwards compatibility
209-
if (type !== 'module' && type !== 'commonjs') type = 'none';
210-
211-
const packageConfig = {
212-
pjsonPath: path,
213-
exists: true,
214-
main,
215-
name,
216-
type,
217-
exports,
218-
imports,
219-
};
220-
packageJSONCache.set(path, packageConfig);
221-
return packageConfig;
222-
}
223-
224-
/**
225-
* @param {URL | string} resolved
226-
* @returns {PackageConfig}
227-
*/
228-
function getPackageScopeConfig(resolved) {
229-
let packageJSONUrl = new URL('./package.json', resolved);
230-
while (true) {
231-
const packageJSONPath = packageJSONUrl.pathname;
232-
if (StringPrototypeEndsWith(packageJSONPath, 'node_modules/package.json'))
233-
break;
234-
const packageConfig = getPackageConfig(fileURLToPath(packageJSONUrl),
235-
resolved);
236-
if (packageConfig.exists) return packageConfig;
237-
238-
const lastPackageJSONUrl = packageJSONUrl;
239-
packageJSONUrl = new URL('../package.json', packageJSONUrl);
240-
241-
// Terminates at root where ../package.json equals ../../package.json
242-
// (can't just check "/package.json" for Windows support).
243-
if (packageJSONUrl.pathname === lastPackageJSONUrl.pathname) break;
244-
}
245-
const packageJSONPath = fileURLToPath(packageJSONUrl);
246-
const packageConfig = {
247-
pjsonPath: packageJSONPath,
248-
exists: false,
249-
main: undefined,
250-
name: undefined,
251-
type: 'none',
252-
exports: undefined,
253-
imports: undefined,
254-
};
255-
packageJSONCache.set(packageJSONPath, packageConfig);
256-
return packageConfig;
257-
}
258-
259160
/**
260161
* @param {string | URL} url
261162
* @returns {boolean}
@@ -609,7 +510,7 @@ function resolvePackageTarget(packageJSONUrl, target, subpath, packageSubpath,
609510

610511
/**
611512
*
612-
* @param {Exports} exports
513+
* @param {import('internal/modules/esm/package_config.js').Exports} exports
613514
* @param {URL} packageJSONUrl
614515
* @param {string | URL | undefined} base
615516
* @returns {boolean}
@@ -799,7 +700,7 @@ function packageImportsResolve(name, base, conditions) {
799700

800701
/**
801702
* @param {URL} url
802-
* @returns {PackageType}
703+
* @returns {import('internal/modules/esm/package_config.js').PackageType}
803704
*/
804705
function getPackageType(url) {
805706
const packageConfig = getPackageScopeConfig(url);

test/parallel/test-bootstrap-modules.js

+1
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ const expectedModules = new Set([
8585
'NativeModule internal/modules/esm/loader',
8686
'NativeModule internal/modules/esm/module_job',
8787
'NativeModule internal/modules/esm/module_map',
88+
'NativeModule internal/modules/esm/package_config',
8889
'NativeModule internal/modules/esm/resolve',
8990
'NativeModule internal/modules/esm/translators',
9091
'NativeModule internal/modules/package_json_reader',

0 commit comments

Comments
 (0)