@jsenv/node-esm-resolution 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,4 +1,4 @@
1
1
  TODO:
2
2
 
3
- - add more unit tests
3
+ - add more tests
4
4
  - improve error messages
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jsenv/node-esm-resolution",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",
@@ -8,8 +8,7 @@
8
8
  "directory": "packages/node-esm-resolution"
9
9
  },
10
10
  "publishConfig": {
11
- "access": "public",
12
- "registry": "https://registry.npmjs.org"
11
+ "access": "public"
13
12
  },
14
13
  "type": "module",
15
14
  "exports": {
@@ -25,6 +24,6 @@
25
24
  "/main.js"
26
25
  ],
27
26
  "scripts": {
28
- "test": "node --conditions=development ./test/test.mjs"
27
+ "test": "node --conditions=development ./tests/test.mjs"
29
28
  }
30
- }
29
+ }
@@ -7,7 +7,7 @@ export const readCustomConditionsFromProcessArgs = () => {
7
7
  packageConditions.push(packageCondition)
8
8
  }
9
9
  if (arg.includes("--conditions=")) {
10
- const packageCondition = arg.slice(0, "--conditions=".length)
10
+ const packageCondition = arg.slice("--conditions=".length)
11
11
  packageConditions.push(packageCondition)
12
12
  }
13
13
  })
@@ -29,11 +29,11 @@ export const determineModuleSystem = (
29
29
  return "url"
30
30
  }
31
31
  if (ambiguousExtensions.includes(extension)) {
32
- const packageUrl = defaultLookupPackageScope(url)
33
- if (!packageUrl) {
32
+ const packageDirectoryUrl = defaultLookupPackageScope(url)
33
+ if (!packageDirectoryUrl) {
34
34
  return "commonjs"
35
35
  }
36
- const packageJson = defaultReadPackageJson(packageUrl)
36
+ const packageJson = defaultReadPackageJson(packageDirectoryUrl)
37
37
  if (packageJson.type === "module") {
38
38
  return "module"
39
39
  }
package/src/errors.js CHANGED
@@ -1,11 +1,11 @@
1
1
  // https://github.com/nodejs/node/blob/0367b5c35ea0f98b323175a4aaa8e651af7a91e7/tools/node_modules/eslint/node_modules/%40babel/core/lib/vendor/import-meta-resolve.js#L2473
2
2
  import { fileURLToPath } from "node:url"
3
3
 
4
- export const createInvalidModuleSpecifierError = ({
5
- specifier,
6
- parentUrl,
4
+ export const createInvalidModuleSpecifierError = (
7
5
  reason,
8
- }) => {
6
+ specifier,
7
+ { parentUrl },
8
+ ) => {
9
9
  const error = new Error(
10
10
  `Invalid module "${specifier}" ${reason} imported from ${fileURLToPath(
11
11
  parentUrl,
@@ -15,24 +15,21 @@ export const createInvalidModuleSpecifierError = ({
15
15
  return error
16
16
  }
17
17
 
18
- export const createInvalidPackageTargetError = ({
19
- parentUrl,
20
- packageUrl,
21
- target,
22
- key,
23
- isImport,
18
+ export const createInvalidPackageTargetError = (
24
19
  reason,
25
- }) => {
20
+ target,
21
+ { parentUrl, packageDirectoryUrl, key, isImport },
22
+ ) => {
26
23
  let message
27
24
  if (key === ".") {
28
25
  message = `Invalid "exports" main target defined in ${fileURLToPath(
29
- packageUrl,
26
+ packageDirectoryUrl,
30
27
  )}package.json imported from ${fileURLToPath(parentUrl)}; ${reason}`
31
28
  } else {
32
29
  message = `Invalid "${
33
30
  isImport ? "imports" : "exports"
34
31
  }" target ${JSON.stringify(target)} defined for "${key}" in ${fileURLToPath(
35
- packageUrl,
32
+ packageDirectoryUrl,
36
33
  )}package.json imported from ${fileURLToPath(parentUrl)}; ${reason}`
37
34
  }
38
35
  const error = new Error(message)
@@ -40,19 +37,18 @@ export const createInvalidPackageTargetError = ({
40
37
  return error
41
38
  }
42
39
 
43
- export const createPackagePathNotExportedError = ({
40
+ export const createPackagePathNotExportedError = (
44
41
  subpath,
45
- parentUrl,
46
- packageUrl,
47
- }) => {
42
+ { parentUrl, packageDirectoryUrl },
43
+ ) => {
48
44
  let message
49
45
  if (subpath === ".") {
50
46
  message = `No "exports" main defined in ${fileURLToPath(
51
- packageUrl,
47
+ packageDirectoryUrl,
52
48
  )}package.json imported from ${fileURLToPath(parentUrl)}`
53
49
  } else {
54
50
  message = `Package subpath "${subpath}" is not defined by "exports" in ${fileURLToPath(
55
- packageUrl,
51
+ packageDirectoryUrl,
56
52
  )}package.json imported from ${fileURLToPath(parentUrl)}`
57
53
  }
58
54
  const error = new Error(message)
@@ -60,7 +56,7 @@ export const createPackagePathNotExportedError = ({
60
56
  return error
61
57
  }
62
58
 
63
- export const createModuleNotFoundError = ({ specifier, parentUrl }) => {
59
+ export const createModuleNotFoundError = (specifier, { parentUrl }) => {
64
60
  const error = new Error(
65
61
  `Cannot find "${specifier}" imported from ${fileURLToPath(parentUrl)}`,
66
62
  )
@@ -68,14 +64,13 @@ export const createModuleNotFoundError = ({ specifier, parentUrl }) => {
68
64
  return error
69
65
  }
70
66
 
71
- export const createPackageImportNotDefinedError = ({
67
+ export const createPackageImportNotDefinedError = (
72
68
  specifier,
73
- packageUrl,
74
- parentUrl,
75
- }) => {
69
+ { parentUrl, packageDirectoryUrl },
70
+ ) => {
76
71
  const error = new Error(
77
72
  `Package import specifier "${specifier}" is not defined in ${fileURLToPath(
78
- packageUrl,
73
+ packageDirectoryUrl,
79
74
  )}package.json imported from ${fileURLToPath(parentUrl)}`,
80
75
  )
81
76
  error.code = "PACKAGE_IMPORT_NOT_DEFINED"
@@ -9,7 +9,8 @@
9
9
  * maintain symlink as facade url when it's outside project directory
10
10
  * or use the real path when inside
11
11
  */
12
- import { existsSync, readFileSync } from "node:fs"
12
+ import { pathToFileURL } from "node:url"
13
+ import { existsSync, readFileSync, realpathSync } from "node:fs"
13
14
 
14
15
  import { isSpecifierForNodeBuiltin } from "./node_builtin_specifiers.js"
15
16
  import { defaultLookupPackageScope } from "./default_lookup_package_scope.js"
@@ -25,40 +26,38 @@ import {
25
26
  import { readCustomConditionsFromProcessArgs } from "./custom_conditions.js"
26
27
 
27
28
  export const applyNodeEsmResolution = ({
28
- conditions = [...readCustomConditionsFromProcessArgs(), "node", "import"],
29
- parentUrl,
30
29
  specifier,
30
+ parentUrl,
31
+ conditions = [...readCustomConditionsFromProcessArgs(), "node", "import"],
31
32
  lookupPackageScope = defaultLookupPackageScope,
32
33
  readPackageJson = defaultReadPackageJson,
34
+ preservesSymlink = false,
33
35
  }) => {
34
- const resolution = applyPackageSpecifierResolution({
35
- conditions,
36
+ const resolution = applyPackageSpecifierResolution(specifier, {
36
37
  parentUrl: String(parentUrl),
37
- specifier,
38
+ conditions,
38
39
  lookupPackageScope,
39
40
  readPackageJson,
41
+ preservesSymlink,
40
42
  })
41
43
  const { url } = resolution
42
44
  if (url.startsWith("file:")) {
43
45
  if (url.includes("%2F") || url.includes("%5C")) {
44
- throw createInvalidModuleSpecifierError({
46
+ throw createInvalidModuleSpecifierError(
47
+ `must not include encoded "/" or "\\" characters`,
45
48
  specifier,
46
- parentUrl,
47
- reason: `must not include encoded "/" or "\\" characters`,
48
- })
49
+ {
50
+ parentUrl,
51
+ },
52
+ )
49
53
  }
50
54
  return resolution
51
55
  }
52
56
  return resolution
53
57
  }
54
58
 
55
- const applyPackageSpecifierResolution = ({
56
- conditions,
57
- parentUrl,
58
- specifier,
59
- lookupPackageScope,
60
- readPackageJson,
61
- }) => {
59
+ const applyPackageSpecifierResolution = (specifier, resolutionContext) => {
60
+ const { parentUrl } = resolutionContext
62
61
  // relative specifier
63
62
  if (
64
63
  specifier[0] === "/" ||
@@ -66,13 +65,10 @@ const applyPackageSpecifierResolution = ({
66
65
  specifier.startsWith("../")
67
66
  ) {
68
67
  if (specifier[0] !== "/") {
69
- const browserFieldResolution = applyBrowserFieldResolution({
70
- conditions,
71
- parentUrl,
68
+ const browserFieldResolution = applyBrowserFieldResolution(
72
69
  specifier,
73
- lookupPackageScope,
74
- readPackageJson,
75
- })
70
+ resolutionContext,
71
+ )
76
72
  if (browserFieldResolution) {
77
73
  return browserFieldResolution
78
74
  }
@@ -83,13 +79,7 @@ const applyPackageSpecifierResolution = ({
83
79
  }
84
80
  }
85
81
  if (specifier[0] === "#") {
86
- return applyPackageImportsResolution({
87
- conditions,
88
- parentUrl,
89
- specifier,
90
- lookupPackageScope,
91
- readPackageJson,
92
- })
82
+ return applyPackageImportsResolution(specifier, resolutionContext)
93
83
  }
94
84
  try {
95
85
  const urlObject = new URL(specifier)
@@ -105,42 +95,29 @@ const applyPackageSpecifierResolution = ({
105
95
  }
106
96
  } catch (e) {
107
97
  // bare specifier
108
- const browserFieldResolution = applyBrowserFieldResolution({
109
- conditions,
110
- parentUrl,
111
- packageSpecifier: specifier,
112
- lookupPackageScope,
113
- readPackageJson,
114
- })
98
+ const browserFieldResolution = applyBrowserFieldResolution(
99
+ specifier,
100
+ resolutionContext,
101
+ )
115
102
  if (browserFieldResolution) {
116
103
  return browserFieldResolution
117
104
  }
118
- return applyPackageResolve({
119
- conditions,
120
- parentUrl,
121
- packageSpecifier: specifier,
122
- lookupPackageScope,
123
- readPackageJson,
124
- })
105
+ return applyPackageResolve(specifier, resolutionContext)
125
106
  }
126
107
  }
127
108
 
128
- const applyBrowserFieldResolution = ({
129
- conditions,
130
- parentUrl,
131
- packageSpecifier,
132
- lookupPackageScope,
133
- readPackageJson,
134
- }) => {
109
+ const applyBrowserFieldResolution = (specifier, resolutionContext) => {
110
+ const { parentUrl, conditions, lookupPackageScope, readPackageJson } =
111
+ resolutionContext
135
112
  const browserCondition = conditions.includes("browser")
136
113
  if (!browserCondition) {
137
114
  return null
138
115
  }
139
- const packageUrl = lookupPackageScope(parentUrl)
140
- if (!packageUrl) {
116
+ const packageDirectoryUrl = lookupPackageScope(parentUrl)
117
+ if (!packageDirectoryUrl) {
141
118
  return null
142
119
  }
143
- const packageJson = readPackageJson(packageUrl)
120
+ const packageJson = readPackageJson(packageDirectoryUrl)
144
121
  if (!packageJson) {
145
122
  return null
146
123
  }
@@ -152,30 +129,28 @@ const applyBrowserFieldResolution = ({
152
129
  return null
153
130
  }
154
131
  let url
155
- if (packageSpecifier.startsWith(".")) {
156
- const packageSpecifierUrl = new URL(packageSpecifier, parentUrl).href
157
- const packageSpecifierRelativeUrl = packageSpecifierUrl.slice(
158
- packageUrl.length,
159
- )
160
- const packageSpecifierRelativeNotation = `./${packageSpecifierRelativeUrl}`
161
- const browserMapping = browser[packageSpecifierRelativeNotation]
132
+ if (specifier.startsWith(".")) {
133
+ const specifierUrl = new URL(specifier, parentUrl).href
134
+ const specifierRelativeUrl = specifierUrl.slice(packageDirectoryUrl.length)
135
+ const secifierRelativeNotation = `./${specifierRelativeUrl}`
136
+ const browserMapping = browser[secifierRelativeNotation]
162
137
  if (typeof browserMapping === "string") {
163
- url = new URL(browserMapping, packageUrl).href
138
+ url = new URL(browserMapping, packageDirectoryUrl).href
164
139
  } else if (browserMapping === false) {
165
- url = `file:///@ignore/${packageSpecifierUrl.slice("file:///")}`
140
+ url = `file:///@ignore/${specifierUrl.slice("file:///")}`
166
141
  }
167
142
  } else {
168
- const browserMapping = browser[packageSpecifier]
143
+ const browserMapping = browser[specifier]
169
144
  if (typeof browserMapping === "string") {
170
- url = new URL(browserMapping, packageUrl).href
145
+ url = new URL(browserMapping, packageDirectoryUrl).href
171
146
  } else if (browserMapping === false) {
172
- url = `file:///@ignore/${packageSpecifier}`
147
+ url = `file:///@ignore/${specifier}`
173
148
  }
174
149
  }
175
150
  if (url) {
176
151
  return {
177
- type: "browser",
178
- packageUrl,
152
+ type: "field:browser",
153
+ packageDirectoryUrl,
179
154
  packageJson,
180
155
  url,
181
156
  }
@@ -183,62 +158,43 @@ const applyBrowserFieldResolution = ({
183
158
  return null
184
159
  }
185
160
 
186
- const applyPackageImportsResolution = ({
187
- conditions,
188
- parentUrl,
189
- specifier,
190
- lookupPackageScope,
191
- readPackageJson,
192
- }) => {
193
- if (!specifier.startsWith("#")) {
194
- throw createInvalidModuleSpecifierError({
195
- specifier,
196
- parentUrl,
197
- reason: "internal imports must start with #",
198
- })
199
- }
200
- if (specifier === "#" || specifier.startsWith("#/")) {
201
- throw createInvalidModuleSpecifierError({
202
- specifier,
203
- parentUrl,
204
- reason: "not a valid internal imports specifier name",
205
- })
161
+ const applyPackageImportsResolution = (
162
+ internalSpecifier,
163
+ resolutionContext,
164
+ ) => {
165
+ const { parentUrl, lookupPackageScope, readPackageJson } = resolutionContext
166
+ if (internalSpecifier === "#" || internalSpecifier.startsWith("#/")) {
167
+ throw createInvalidModuleSpecifierError(
168
+ "not a valid internal imports specifier name",
169
+ internalSpecifier,
170
+ resolutionContext,
171
+ )
206
172
  }
207
- const packageUrl = lookupPackageScope(parentUrl)
208
- if (packageUrl !== null) {
209
- const packageJson = readPackageJson(packageUrl)
173
+ const packageDirectoryUrl = lookupPackageScope(parentUrl)
174
+ if (packageDirectoryUrl !== null) {
175
+ const packageJson = readPackageJson(packageDirectoryUrl)
210
176
  const { imports } = packageJson
211
177
  if (imports !== null && typeof imports === "object") {
212
- const resolved = applyPackageImportsExportsResolution({
213
- conditions,
214
- parentUrl,
215
- packageUrl,
178
+ const resolved = applyPackageImportsExportsResolution(internalSpecifier, {
179
+ ...resolutionContext,
180
+ packageDirectoryUrl,
216
181
  packageJson,
217
- matchObject: imports,
218
- matchKey: specifier,
219
- isImports: true,
220
- lookupPackageScope,
221
- readPackageJson,
182
+ isImport: true,
222
183
  })
223
184
  if (resolved) {
224
185
  return resolved
225
186
  }
226
187
  }
227
188
  }
228
- throw createPackageImportNotDefinedError({
229
- specifier,
230
- packageUrl,
231
- parentUrl,
189
+ throw createPackageImportNotDefinedError(internalSpecifier, {
190
+ ...resolutionContext,
191
+ packageDirectoryUrl,
232
192
  })
233
193
  }
234
194
 
235
- const applyPackageResolve = ({
236
- conditions,
237
- parentUrl,
238
- packageSpecifier,
239
- lookupPackageScope,
240
- readPackageJson,
241
- }) => {
195
+ const applyPackageResolve = (packageSpecifier, resolutionContext) => {
196
+ const { parentUrl, conditions, readPackageJson, preservesSymlink } =
197
+ resolutionContext
242
198
  if (packageSpecifier === "") {
243
199
  throw new Error("invalid module specifier")
244
200
  }
@@ -258,78 +214,64 @@ const applyPackageResolve = ({
258
214
  packageName.includes("\\") ||
259
215
  packageName.includes("%")
260
216
  ) {
261
- throw createInvalidModuleSpecifierError({
262
- specifier: packageName,
263
- parentUrl,
264
- reason: `is not a valid package name`,
265
- })
217
+ throw createInvalidModuleSpecifierError(
218
+ `is not a valid package name`,
219
+ packageName,
220
+ resolutionContext,
221
+ )
266
222
  }
267
223
  if (packageSubpath.endsWith("/")) {
268
224
  throw new Error("invalid module specifier")
269
225
  }
270
- const selfResolution = applyPackageSelfResolution({
271
- conditions,
272
- parentUrl,
226
+ const selfResolution = applyPackageSelfResolution(packageSubpath, {
227
+ ...resolutionContext,
273
228
  packageName,
274
- packageSubpath,
275
- lookupPackageScope,
276
- readPackageJson,
277
229
  })
278
230
  if (selfResolution) {
279
231
  return selfResolution
280
232
  }
281
233
  let currentUrl = parentUrl
282
234
  while (currentUrl !== "file:///") {
283
- const packageUrl = new URL(`node_modules/${packageName}/`, currentUrl).href
284
- if (!existsSync(new URL(packageUrl))) {
235
+ const packageDirectoryFacadeUrl = new URL(
236
+ `node_modules/${packageName}/`,
237
+ currentUrl,
238
+ ).href
239
+ if (!existsSync(new URL(packageDirectoryFacadeUrl))) {
285
240
  currentUrl = getParentUrl(currentUrl)
286
241
  continue
287
242
  }
288
- const packageJson = readPackageJson(packageUrl)
243
+ const packageDirectoryUrl = preservesSymlink
244
+ ? packageDirectoryFacadeUrl
245
+ : resolvePackageSymlink(packageDirectoryFacadeUrl)
246
+ const packageJson = readPackageJson(packageDirectoryUrl)
289
247
  if (packageJson !== null) {
290
248
  const { exports } = packageJson
291
249
  if (exports !== null && exports !== undefined) {
292
- return applyPackageExportsResolution({
293
- conditions,
294
- parentUrl,
295
- packageUrl,
250
+ return applyPackageExportsResolution(packageSubpath, {
251
+ ...resolutionContext,
252
+ packageDirectoryUrl,
296
253
  packageJson,
297
- packageSubpath,
298
254
  exports,
299
- lookupPackageScope,
300
- readPackageJson,
301
255
  })
302
256
  }
303
257
  }
304
- return applyLegacySubpathResolution({
305
- conditions,
306
- parentUrl,
307
- packageUrl,
258
+ return applyLegacySubpathResolution(packageSubpath, {
259
+ ...resolutionContext,
260
+ packageDirectoryUrl,
308
261
  packageJson,
309
- packageSubpath,
310
- lookupPackageScope,
311
- readPackageJson,
312
262
  })
313
263
  }
314
- throw createModuleNotFoundError({
315
- specifier: packageName,
316
- parentUrl,
317
- })
264
+ throw createModuleNotFoundError(packageName, resolutionContext)
318
265
  }
319
266
 
320
- const applyPackageSelfResolution = ({
321
- conditions,
322
- parentUrl,
323
- packageName,
324
- packageSubpath,
325
- lookupPackageScope,
326
- readPackageJson,
327
- }) => {
328
- const packageUrl = lookupPackageScope(parentUrl)
329
- if (!packageUrl) {
267
+ const applyPackageSelfResolution = (packageSubpath, resolutionContext) => {
268
+ const { parentUrl, packageName, lookupPackageScope, readPackageJson } =
269
+ resolutionContext
270
+ const packageDirectoryUrl = lookupPackageScope(parentUrl)
271
+ if (!packageDirectoryUrl) {
330
272
  return undefined
331
273
  }
332
- const packageJson = readPackageJson(packageUrl)
274
+ const packageJson = readPackageJson(packageDirectoryUrl)
333
275
  if (!packageJson) {
334
276
  return undefined
335
277
  }
@@ -338,118 +280,65 @@ const applyPackageSelfResolution = ({
338
280
  }
339
281
  const { exports } = packageJson
340
282
  if (!exports) {
341
- const subpathResolution = applyLegacySubpathResolution({
342
- conditions,
343
- parentUrl,
344
- packageUrl,
283
+ const subpathResolution = applyLegacySubpathResolution(packageSubpath, {
284
+ ...resolutionContext,
285
+ packageDirectoryUrl,
345
286
  packageJson,
346
- packageSubpath,
347
- lookupPackageScope,
348
- readPackageJson,
349
287
  })
350
288
  if (subpathResolution && subpathResolution.type !== "subpath") {
351
289
  return subpathResolution
352
290
  }
353
291
  return undefined
354
292
  }
355
- return applyPackageExportsResolution({
356
- conditions,
357
- parentUrl,
358
- packageUrl,
293
+ return applyPackageExportsResolution(packageSubpath, {
294
+ ...resolutionContext,
295
+ packageDirectoryUrl,
359
296
  packageJson,
360
- packageSubpath,
361
- exports,
362
- lookupPackageScope,
363
- readPackageJson,
364
297
  })
365
298
  }
366
299
 
367
300
  // https://github.com/nodejs/node/blob/0367b5c35ea0f98b323175a4aaa8e651af7a91e7/lib/internal/modules/esm/resolve.js#L642
368
- const applyPackageExportsResolution = ({
369
- conditions,
370
- parentUrl,
371
- packageUrl,
372
- packageJson,
373
- packageSubpath,
374
- exports,
375
- lookupPackageScope,
376
- readPackageJson,
377
- }) => {
378
- const exportsInfo = readExports({ exports, packageUrl })
301
+ const applyPackageExportsResolution = (packageSubpath, resolutionContext) => {
379
302
  if (packageSubpath === ".") {
380
- const mainExport = applyMainExportResolution({ exports, exportsInfo })
303
+ const mainExport = applyMainExportResolution(resolutionContext)
381
304
  if (!mainExport) {
382
- throw createPackagePathNotExportedError({
383
- subpath: packageSubpath,
384
- parentUrl,
385
- packageUrl,
386
- })
305
+ throw createPackagePathNotExportedError(packageSubpath, resolutionContext)
387
306
  }
388
- const resolved = applyPackageTargetResolution({
389
- conditions,
390
- parentUrl,
391
- packageUrl,
392
- packageJson,
307
+ const resolved = applyPackageTargetResolution(mainExport, {
308
+ ...resolutionContext,
393
309
  key: ".",
394
- target: mainExport,
395
- lookupPackageScope,
396
- readPackageJson,
397
310
  })
398
311
  if (resolved) {
399
312
  return resolved
400
313
  }
401
- throw createPackagePathNotExportedError({
402
- subpath: packageSubpath,
403
- parentUrl,
404
- packageUrl,
405
- })
314
+ throw createPackagePathNotExportedError(packageSubpath, resolutionContext)
406
315
  }
407
- if (exportsInfo.type === "object" && exportsInfo.allKeysAreRelative) {
408
- const resolved = applyPackageImportsExportsResolution({
409
- conditions,
410
- parentUrl,
411
- packageUrl,
412
- packageJson,
413
- matchObject: exports,
414
- matchKey: packageSubpath,
415
- isImports: false,
416
- lookupPackageScope,
417
- readPackageJson,
316
+ const packageExportsInfo = readExports(resolutionContext)
317
+ if (
318
+ packageExportsInfo.type === "object" &&
319
+ packageExportsInfo.allKeysAreRelative
320
+ ) {
321
+ const resolved = applyPackageImportsExportsResolution(packageSubpath, {
322
+ ...resolutionContext,
323
+ isImport: false,
418
324
  })
419
325
  if (resolved) {
420
326
  return resolved
421
327
  }
422
328
  }
423
- throw createPackagePathNotExportedError({
424
- subpath: packageSubpath,
425
- parentUrl,
426
- packageUrl,
427
- })
329
+ throw createPackagePathNotExportedError(packageSubpath, resolutionContext)
428
330
  }
429
331
 
430
- const applyPackageImportsExportsResolution = ({
431
- conditions,
432
- parentUrl,
433
- packageUrl,
434
- packageJson,
435
- matchObject,
436
- matchKey,
437
- isImports,
438
- lookupPackageScope,
439
- readPackageJson,
440
- }) => {
332
+ const applyPackageImportsExportsResolution = (matchKey, resolutionContext) => {
333
+ const { packageJson, isImport } = resolutionContext
334
+ const matchObject = isImport ? packageJson.imports : packageJson.exports
335
+
441
336
  if (!matchKey.includes("*") && matchObject.hasOwnProperty(matchKey)) {
442
337
  const target = matchObject[matchKey]
443
- return applyPackageTargetResolution({
444
- conditions,
445
- parentUrl,
446
- packageUrl,
447
- packageJson,
338
+ return applyPackageTargetResolution(target, {
339
+ ...resolutionContext,
448
340
  key: matchKey,
449
- target,
450
- internal: isImports,
451
- lookupPackageScope,
452
- readPackageJson,
341
+ isImport,
453
342
  })
454
343
  }
455
344
  const expansionKeys = Object.keys(matchObject)
@@ -468,80 +357,64 @@ const applyPackageImportsExportsResolution = ({
468
357
  patternBase.length,
469
358
  matchKey.length - patternTrailer.length,
470
359
  )
471
- return applyPackageTargetResolution({
472
- conditions,
473
- parentUrl,
474
- packageUrl,
475
- packageJson,
360
+ return applyPackageTargetResolution(target, {
361
+ ...resolutionContext,
476
362
  key: matchKey,
477
- target,
478
363
  subpath,
479
364
  pattern: true,
480
- internal: isImports,
481
- lookupPackageScope,
482
- readPackageJson,
365
+ isImport,
483
366
  })
484
367
  }
485
368
  return null
486
369
  }
487
370
 
488
- const applyPackageTargetResolution = ({
489
- conditions,
490
- parentUrl,
491
- packageUrl,
492
- packageJson,
493
- key,
494
- target,
495
- subpath = "",
496
- pattern = false,
497
- internal = false,
498
- lookupPackageScope,
499
- readPackageJson,
500
- }) => {
371
+ const applyPackageTargetResolution = (target, resolutionContext) => {
372
+ const {
373
+ conditions,
374
+ packageDirectoryUrl,
375
+ packageJson,
376
+ key,
377
+ subpath = "",
378
+ pattern = false,
379
+ isImport = false,
380
+ } = resolutionContext
381
+
501
382
  if (typeof target === "string") {
502
383
  if (pattern === false && subpath !== "" && !target.endsWith("/")) {
503
384
  throw new Error("invalid module specifier")
504
385
  }
505
386
  if (target.startsWith("./")) {
506
- const targetUrl = new URL(target, packageUrl).href
507
- if (!targetUrl.startsWith(packageUrl)) {
508
- throw createInvalidPackageTargetError({
509
- parentUrl,
510
- packageUrl,
387
+ const targetUrl = new URL(target, packageDirectoryUrl).href
388
+ if (!targetUrl.startsWith(packageDirectoryUrl)) {
389
+ throw createInvalidPackageTargetError(
390
+ `target must be inside package`,
511
391
  target,
512
- key,
513
- isImport: internal,
514
- reason: `target must be inside package`,
515
- })
392
+ resolutionContext,
393
+ )
516
394
  }
517
395
  return {
518
- type: internal ? "imports_subpath" : "exports_subpath",
519
- packageUrl,
396
+ type: isImport ? "field:imports" : "field:exports",
397
+ packageDirectoryUrl,
520
398
  packageJson,
521
399
  url: pattern
522
400
  ? targetUrl.replaceAll("*", subpath)
523
401
  : new URL(subpath, targetUrl).href,
524
402
  }
525
403
  }
526
- if (!internal || target.startsWith("../") || isValidUrl(target)) {
527
- throw createInvalidPackageTargetError({
528
- parentUrl,
529
- packageUrl,
404
+ if (!isImport || target.startsWith("../") || isValidUrl(target)) {
405
+ throw createInvalidPackageTargetError(
406
+ `target must starst with "./"`,
530
407
  target,
531
- key,
532
- isImport: internal,
533
- reason: `target must starst with "./"`,
534
- })
408
+ resolutionContext,
409
+ )
535
410
  }
536
- return applyPackageResolve({
537
- conditions,
538
- parentUrl: packageUrl,
539
- packageSpecifier: pattern
540
- ? target.replaceAll("*", subpath)
541
- : `${target}${subpath}`,
542
- lookupPackageScope,
543
- readPackageJson,
544
- })
411
+ return applyPackageResolve(
412
+ pattern ? target.replaceAll("*", subpath) : `${target}${subpath}`,
413
+ {
414
+ ...resolutionContext,
415
+ parentUrl: packageDirectoryUrl,
416
+ },
417
+ )
545
418
  }
546
419
  if (Array.isArray(target)) {
547
420
  if (target.length === 0) {
@@ -553,18 +426,12 @@ const applyPackageTargetResolution = ({
553
426
  const targetValue = target[i]
554
427
  i++
555
428
  try {
556
- const resolved = applyPackageTargetResolution({
557
- conditions,
558
- parentUrl,
559
- packageUrl,
560
- packageJson,
429
+ const resolved = applyPackageTargetResolution(targetValue, {
430
+ ...resolutionContext,
561
431
  key: `${key}[${i}]`,
562
- target: targetValue,
563
432
  subpath,
564
433
  pattern,
565
- internal,
566
- lookupPackageScope,
567
- readPackageJson,
434
+ isImport,
568
435
  })
569
436
  if (resolved) {
570
437
  return resolved
@@ -593,18 +460,12 @@ const applyPackageTargetResolution = ({
593
460
  }
594
461
  if (key === "default" || conditions.includes(key)) {
595
462
  const targetValue = target[key]
596
- const resolved = applyPackageTargetResolution({
597
- conditions,
598
- parentUrl,
599
- packageUrl,
600
- packageJson,
463
+ const resolved = applyPackageTargetResolution(targetValue, {
464
+ ...resolutionContext,
601
465
  key,
602
- target: targetValue,
603
466
  subpath,
604
467
  pattern,
605
- internal,
606
- lookupPackageScope,
607
- readPackageJson,
468
+ isImport,
608
469
  })
609
470
  if (resolved) {
610
471
  return resolved
@@ -613,27 +474,25 @@ const applyPackageTargetResolution = ({
613
474
  }
614
475
  return null
615
476
  }
616
- throw createInvalidPackageTargetError({
617
- parentUrl,
618
- packageUrl,
477
+ throw createInvalidPackageTargetError(
478
+ `target must be a string, array, object or null`,
619
479
  target,
620
- key,
621
- isImport: internal,
622
- reason: `target must be a string, array, object or null`,
623
- })
480
+ resolutionContext,
481
+ )
624
482
  }
625
483
 
626
- const readExports = ({ exports, packageUrl }) => {
627
- if (Array.isArray(exports)) {
484
+ const readExports = ({ packageDirectoryUrl, packageJson }) => {
485
+ const packageExports = packageJson.exports
486
+ if (Array.isArray(packageExports)) {
628
487
  return {
629
488
  type: "array",
630
489
  }
631
490
  }
632
- if (exports === null) {
491
+ if (packageExports === null) {
633
492
  return {}
634
493
  }
635
- if (typeof exports === "object") {
636
- const keys = Object.keys(exports)
494
+ if (typeof packageExports === "object") {
495
+ const keys = Object.keys(packageExports)
637
496
  const relativeKeys = []
638
497
  const conditionalKeys = []
639
498
  keys.forEach((availableKey) => {
@@ -649,8 +508,8 @@ const readExports = ({ exports, packageUrl }) => {
649
508
  `Invalid package configuration: cannot mix relative and conditional keys in package.exports
650
509
  --- unexpected keys ---
651
510
  ${conditionalKeys.map((key) => `"${key}"`).join("\n")}
652
- --- package.json ---
653
- ${packageUrl}`,
511
+ --- package directory url ---
512
+ ${packageDirectoryUrl}`,
654
513
  )
655
514
  }
656
515
  return {
@@ -659,7 +518,7 @@ ${packageUrl}`,
659
518
  allKeysAreRelative: relativeKeys.length === keys.length,
660
519
  }
661
520
  }
662
- if (typeof exports === "string") {
521
+ if (typeof packageExports === "string") {
663
522
  return { type: "string" }
664
523
  }
665
524
  return {}
@@ -704,101 +563,100 @@ const parsePackageSpecifier = (packageSpecifier) => {
704
563
  }
705
564
  }
706
565
 
707
- const applyMainExportResolution = ({ exports, exportsInfo }) => {
708
- if (exportsInfo.type === "array" || exportsInfo.type === "string") {
709
- return exports
566
+ const applyMainExportResolution = (resolutionContext) => {
567
+ const { packageJson } = resolutionContext
568
+ const packageExportsInfo = readExports(resolutionContext)
569
+ if (
570
+ packageExportsInfo.type === "array" ||
571
+ packageExportsInfo.type === "string"
572
+ ) {
573
+ return packageJson.exports
710
574
  }
711
- if (exportsInfo.type === "object") {
712
- if (exportsInfo.hasRelativeKey) {
713
- return exports["."]
575
+ if (packageExportsInfo.type === "object") {
576
+ if (packageExportsInfo.hasRelativeKey) {
577
+ return packageJson.exports["."]
714
578
  }
715
- return exports
579
+ return packageJson.exports
716
580
  }
717
581
  return undefined
718
582
  }
719
583
 
720
- const applyLegacySubpathResolution = ({
721
- conditions,
722
- parentUrl,
723
- packageUrl,
724
- packageJson,
725
- packageSubpath,
726
- lookupPackageScope,
727
- readPackageJson,
728
- }) => {
584
+ const applyLegacySubpathResolution = (packageSubpath, resolutionContext) => {
585
+ const { packageDirectoryUrl, packageJson } = resolutionContext
586
+
729
587
  if (packageSubpath === ".") {
730
- return applyLegacyMainResolution({
731
- conditions,
732
- packageUrl,
733
- packageJson,
734
- })
588
+ return applyLegacyMainResolution(packageSubpath, resolutionContext)
735
589
  }
736
- const browserFieldResolution = applyBrowserFieldResolution({
737
- conditions,
738
- parentUrl,
739
- specifier: packageSubpath,
740
- lookupPackageScope,
741
- readPackageJson,
742
- })
590
+ const browserFieldResolution = applyBrowserFieldResolution(
591
+ packageSubpath,
592
+ resolutionContext,
593
+ )
743
594
  if (browserFieldResolution) {
744
595
  return browserFieldResolution
745
596
  }
746
597
  return {
747
598
  type: "subpath",
748
- packageUrl,
599
+ packageDirectoryUrl,
749
600
  packageJson,
750
- url: new URL(packageSubpath, packageUrl).href,
601
+ url: new URL(packageSubpath, packageDirectoryUrl).href,
751
602
  }
752
603
  }
753
604
 
754
- const applyLegacyMainResolution = ({ conditions, packageUrl, packageJson }) => {
605
+ const applyLegacyMainResolution = (packageSubpath, resolutionContext) => {
606
+ const { conditions, packageDirectoryUrl, packageJson } = resolutionContext
755
607
  for (const condition of conditions) {
756
608
  const conditionResolver = mainLegacyResolvers[condition]
757
609
  if (!conditionResolver) {
758
610
  continue
759
611
  }
760
- const resolved = conditionResolver(packageJson, packageUrl)
612
+ const resolved = conditionResolver(resolutionContext)
761
613
  if (resolved) {
762
614
  return {
763
615
  type: resolved.type,
764
- packageUrl,
616
+ packageDirectoryUrl,
765
617
  packageJson,
766
- url: new URL(resolved.path, packageUrl).href,
618
+ url: new URL(resolved.path, packageDirectoryUrl).href,
767
619
  }
768
620
  }
769
621
  }
770
622
  return {
771
- type: "default",
772
- packageUrl,
623
+ type: "field:main", // the absence of "main" field
624
+ packageDirectoryUrl,
773
625
  packageJson,
774
- url: new URL("index.js", packageUrl).href,
626
+ url: new URL("index.js", packageDirectoryUrl).href,
775
627
  }
776
628
  }
777
629
  const mainLegacyResolvers = {
778
- import: (packageJson) => {
630
+ import: ({ packageJson }) => {
779
631
  if (typeof packageJson.module === "string") {
780
- return { type: "module", path: packageJson.module }
632
+ return { type: "field:module", path: packageJson.module }
781
633
  }
782
634
  if (typeof packageJson.jsnext === "string") {
783
- return { type: "jsnext", path: packageJson.jsnext }
635
+ return { type: "field:jsnext", path: packageJson.jsnext }
784
636
  }
785
637
  if (typeof packageJson.main === "string") {
786
- return { type: "main", path: packageJson.main }
638
+ return { type: "field:main", path: packageJson.main }
787
639
  }
788
640
  return null
789
641
  },
790
- browser: (packageJson, packageUrl) => {
791
- const browserMain =
792
- typeof packageJson.browser === "string"
793
- ? packageJson.browser
794
- : typeof packageJson.browser === "object" &&
795
- packageJson.browser !== null
796
- ? packageJson.browser["."]
797
- : ""
642
+ browser: ({ packageDirectoryUrl, packageJson }) => {
643
+ const browserMain = (() => {
644
+ if (typeof packageJson.browser === "string") {
645
+ return packageJson.browser
646
+ }
647
+ if (
648
+ typeof packageJson.browser === "object" &&
649
+ packageJson.browser !== null
650
+ ) {
651
+ return packageJson.browser["."]
652
+ }
653
+ return ""
654
+ })()
655
+
798
656
  if (!browserMain) {
799
657
  if (typeof packageJson.module === "string") {
800
658
  return {
801
- type: "module",
659
+ type: "field:module",
802
660
  path: packageJson.module,
803
661
  }
804
662
  }
@@ -809,11 +667,11 @@ const mainLegacyResolvers = {
809
667
  packageJson.module === browserMain
810
668
  ) {
811
669
  return {
812
- type: "browser",
670
+ type: "field:browser",
813
671
  path: browserMain,
814
672
  }
815
673
  }
816
- const browserMainUrlObject = new URL(browserMain, packageUrl)
674
+ const browserMainUrlObject = new URL(browserMain, packageDirectoryUrl)
817
675
  const content = readFileSync(browserMainUrlObject, "utf-8")
818
676
  if (
819
677
  (/typeof exports\s*==/.test(content) &&
@@ -821,19 +679,19 @@ const mainLegacyResolvers = {
821
679
  /module\.exports\s*=/.test(content)
822
680
  ) {
823
681
  return {
824
- type: "module",
682
+ type: "field:module",
825
683
  path: packageJson.module,
826
684
  }
827
685
  }
828
686
  return {
829
- type: "browser",
687
+ type: "field:browser",
830
688
  path: browserMain,
831
689
  }
832
690
  },
833
- node: (packageJson) => {
691
+ node: ({ packageJson }) => {
834
692
  if (typeof packageJson.main === "string") {
835
693
  return {
836
- type: "main",
694
+ type: "field:main",
837
695
  path: packageJson.main,
838
696
  }
839
697
  }
@@ -842,10 +700,10 @@ const mainLegacyResolvers = {
842
700
  }
843
701
 
844
702
  const comparePatternKeys = (keyA, keyB) => {
845
- if (!keyA.endsWith("/") && !keyA.contains("*")) {
703
+ if (!keyA.endsWith("/") && !keyA.includes("*")) {
846
704
  throw new Error("Invalid package configuration")
847
705
  }
848
- if (!keyB.endsWith("/") && !keyB.contains("*")) {
706
+ if (!keyB.endsWith("/") && !keyB.includes("*")) {
849
707
  throw new Error("Invalid package configuration")
850
708
  }
851
709
  const aStarIndex = keyA.indexOf("*")
@@ -872,3 +730,9 @@ const comparePatternKeys = (keyA, keyB) => {
872
730
  }
873
731
  return 0
874
732
  }
733
+
734
+ const resolvePackageSymlink = (packageDirectoryUrl) => {
735
+ const packageDirectoryPath = realpathSync(new URL(packageDirectoryUrl))
736
+ const packageDirectoryResolvedUrl = pathToFileURL(packageDirectoryPath).href
737
+ return `${packageDirectoryResolvedUrl}/`
738
+ }
package/src/url_utils.js CHANGED
@@ -10,24 +10,21 @@ export const getParentUrl = (url) => {
10
10
  if (url.startsWith("file://")) {
11
11
  // With node.js new URL('../', 'file:///C:/').href
12
12
  // returns "file:///C:/" instead of "file:///"
13
- const ressource = url.slice("file://".length)
14
- const slashLastIndex = ressource.lastIndexOf("/")
13
+ const resource = url.slice("file://".length)
14
+ const slashLastIndex = resource.lastIndexOf("/")
15
15
  if (slashLastIndex === -1) {
16
16
  return url
17
17
  }
18
- const lastCharIndex = ressource.length - 1
18
+ const lastCharIndex = resource.length - 1
19
19
  if (slashLastIndex === lastCharIndex) {
20
- const slashBeforeLastIndex = ressource.lastIndexOf(
21
- "/",
22
- slashLastIndex - 1,
23
- )
20
+ const slashBeforeLastIndex = resource.lastIndexOf("/", slashLastIndex - 1)
24
21
  if (slashBeforeLastIndex === -1) {
25
22
  return url
26
23
  }
27
- return `file://${ressource.slice(0, slashBeforeLastIndex + 1)}`
24
+ return `file://${resource.slice(0, slashBeforeLastIndex + 1)}`
28
25
  }
29
26
 
30
- return `file://${ressource.slice(0, slashLastIndex + 1)}`
27
+ return `file://${resource.slice(0, slashLastIndex + 1)}`
31
28
  }
32
29
  return new URL(url.endsWith("/") ? "../" : "./", url).href
33
30
  }