@flex-development/mlly 1.0.0-beta.4 → 1.0.0-beta.6

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.
Files changed (121) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/README.md +252 -113
  3. package/dist/index.d.mts +1908 -5
  4. package/dist/index.mjs +1 -5
  5. package/dist/internal/chain-or-call.d.mts +51 -0
  6. package/dist/internal/chain-or-call.mjs +1 -34
  7. package/dist/internal/chars.d.mts +16 -0
  8. package/dist/internal/chars.mjs +1 -17
  9. package/dist/internal/check-invalid-segments.d.mts +18 -0
  10. package/dist/internal/check-invalid-segments.mjs +1 -49
  11. package/dist/internal/constant.d.mts +21 -0
  12. package/dist/internal/constant.mjs +1 -23
  13. package/dist/internal/fs.browser.d.mts +12 -0
  14. package/dist/internal/fs.browser.mjs +1 -42
  15. package/dist/internal/fs.node.d.mts +12 -0
  16. package/dist/internal/fs.node.mjs +1 -16
  17. package/dist/internal/identity.d.mts +21 -0
  18. package/dist/internal/identity.mjs +1 -23
  19. package/dist/internal/invalid-package-target.d.mts +31 -0
  20. package/dist/internal/invalid-package-target.mjs +1 -36
  21. package/dist/internal/invalid-subpath.d.mts +32 -0
  22. package/dist/internal/invalid-subpath.mjs +1 -37
  23. package/dist/internal/is-promise.d.mts +21 -0
  24. package/dist/internal/is-promise.mjs +1 -26
  25. package/dist/internal/process.browser.d.mts +9 -0
  26. package/dist/internal/process.browser.mjs +1 -8
  27. package/dist/lib/can-parse-url.mjs +1 -31
  28. package/dist/lib/cwd.mjs +1 -17
  29. package/dist/lib/default-conditions.mjs +1 -14
  30. package/dist/lib/default-extensions.mjs +1 -29
  31. package/dist/lib/default-main-fields.mjs +1 -13
  32. package/dist/lib/extension-format-map.mjs +1 -31
  33. package/dist/lib/formats.mjs +1 -22
  34. package/dist/lib/get-source.mjs +1 -153
  35. package/dist/lib/index.mjs +1 -41
  36. package/dist/lib/is-absolute-specifier.mjs +1 -34
  37. package/dist/lib/is-array-index.mjs +1 -28
  38. package/dist/lib/is-bare-specifier.mjs +1 -33
  39. package/dist/lib/is-directory.mjs +1 -54
  40. package/dist/lib/is-file.mjs +1 -54
  41. package/dist/lib/is-imports-subpath.mjs +1 -28
  42. package/dist/lib/is-module-id.mjs +1 -24
  43. package/dist/lib/is-relative-specifier.mjs +1 -36
  44. package/dist/lib/lookup-package-scope.mjs +1 -102
  45. package/dist/lib/pattern-key-compare.mjs +1 -71
  46. package/dist/lib/pattern-match.mjs +1 -85
  47. package/dist/lib/read-package-json.mjs +1 -114
  48. package/dist/lib/resolve-alias.mjs +1 -102
  49. package/dist/lib/resolve-module.mjs +1 -186
  50. package/dist/lib/resolver.mjs +1 -1015
  51. package/dist/lib/root.mjs +1 -12
  52. package/dist/lib/to-relative-specifier.mjs +1 -63
  53. package/dist/lib/to-url.mjs +1 -34
  54. package/package.json +29 -13
  55. package/dist/interfaces/aliases.d.mts +0 -21
  56. package/dist/interfaces/buffer-encoding-map.d.mts +0 -29
  57. package/dist/interfaces/condition-map.d.mts +0 -24
  58. package/dist/interfaces/file-system.d.mts +0 -29
  59. package/dist/interfaces/get-source-context.d.mts +0 -44
  60. package/dist/interfaces/get-source-options.d.mts +0 -56
  61. package/dist/interfaces/index.d.mts +0 -22
  62. package/dist/interfaces/is-directory.d.mts +0 -17
  63. package/dist/interfaces/is-file.d.mts +0 -17
  64. package/dist/interfaces/main-field-map.d.mts +0 -22
  65. package/dist/interfaces/module-format-map.d.mts +0 -26
  66. package/dist/interfaces/pattern-key-comparison-map.d.mts +0 -31
  67. package/dist/interfaces/protocol-map.d.mts +0 -44
  68. package/dist/interfaces/read-file.d.mts +0 -42
  69. package/dist/interfaces/realpath.d.mts +0 -29
  70. package/dist/interfaces/resolve-alias-options.d.mts +0 -43
  71. package/dist/interfaces/resolve-module-options.d.mts +0 -79
  72. package/dist/interfaces/stat.d.mts +0 -27
  73. package/dist/interfaces/stats.d.mts +0 -23
  74. package/dist/lib/can-parse-url.d.mts +0 -23
  75. package/dist/lib/cwd.d.mts +0 -14
  76. package/dist/lib/default-conditions.d.mts +0 -15
  77. package/dist/lib/default-extensions.d.mts +0 -14
  78. package/dist/lib/default-main-fields.d.mts +0 -14
  79. package/dist/lib/extension-format-map.d.mts +0 -16
  80. package/dist/lib/formats.d.mts +0 -21
  81. package/dist/lib/get-source.d.mts +0 -46
  82. package/dist/lib/index.d.mts +0 -41
  83. package/dist/lib/is-absolute-specifier.d.mts +0 -23
  84. package/dist/lib/is-array-index.d.mts +0 -19
  85. package/dist/lib/is-bare-specifier.d.mts +0 -23
  86. package/dist/lib/is-directory.d.mts +0 -27
  87. package/dist/lib/is-file.d.mts +0 -27
  88. package/dist/lib/is-imports-subpath.d.mts +0 -26
  89. package/dist/lib/is-module-id.d.mts +0 -23
  90. package/dist/lib/is-relative-specifier.d.mts +0 -23
  91. package/dist/lib/lookup-package-scope.d.mts +0 -49
  92. package/dist/lib/pattern-key-compare.d.mts +0 -28
  93. package/dist/lib/pattern-match.d.mts +0 -22
  94. package/dist/lib/read-package-json.d.mts +0 -63
  95. package/dist/lib/resolve-alias.d.mts +0 -21
  96. package/dist/lib/resolve-module.d.mts +0 -43
  97. package/dist/lib/resolver.d.mts +0 -346
  98. package/dist/lib/root.d.mts +0 -11
  99. package/dist/lib/to-relative-specifier.d.mts +0 -27
  100. package/dist/lib/to-url.d.mts +0 -26
  101. package/dist/types/awaitable.d.mts +0 -12
  102. package/dist/types/buffer-encoding.d.mts +0 -13
  103. package/dist/types/change-ext-fn.d.mts +0 -29
  104. package/dist/types/condition.d.mts +0 -13
  105. package/dist/types/dot.d.mts +0 -9
  106. package/dist/types/empty-array.d.mts +0 -9
  107. package/dist/types/empty-object.d.mts +0 -19
  108. package/dist/types/empty-string.d.mts +0 -9
  109. package/dist/types/ext.d.mts +0 -12
  110. package/dist/types/file-content.d.mts +0 -11
  111. package/dist/types/get-source-handler.d.mts +0 -23
  112. package/dist/types/get-source-handlers.d.mts +0 -15
  113. package/dist/types/index.d.mts +0 -24
  114. package/dist/types/list.d.mts +0 -12
  115. package/dist/types/main-field.d.mts +0 -13
  116. package/dist/types/module-format.d.mts +0 -13
  117. package/dist/types/module-id.d.mts +0 -12
  118. package/dist/types/numeric.d.mts +0 -9
  119. package/dist/types/pattern-key-comparison.d.mts +0 -14
  120. package/dist/types/pattern-match.d.mts +0 -10
  121. package/dist/types/protocol.d.mts +0 -13
package/README.md CHANGED
@@ -12,13 +12,22 @@
12
12
  [![vitest](https://img.shields.io/badge/-vitest-6e9f18?style=flat\&logo=vitest\&logoColor=ffffff)](https://vitest.dev)
13
13
  [![yarn](https://img.shields.io/badge/-yarn-2c8ebb?style=flat\&logo=yarn\&logoColor=ffffff)](https://yarnpkg.com)
14
14
 
15
- [ECMAScript module][node-esm] utilities.
15
+ [ESM][node-esm] utilities for modern tooling — spec-compliant, developer-friendly, and fully typed.
16
16
 
17
17
  ## Contents
18
18
 
19
19
  - [What is this?](#what-is-this)
20
+ - [Why this package?](#why-this-package)
20
21
  - [Install](#install)
21
- - [Use](#use)
22
+ - [Quick Start](#quick-start)
23
+ - [Resolve like Node.js](#resolve-like-nodejs)
24
+ - [Resolve with custom conditions](#resolve-with-custom-conditions)
25
+ - [Resolve a directory index](#resolve-a-directory-index)
26
+ - [Resolve path aliases](#resolve-path-aliases)
27
+ - [Rewrite an extension](#rewrite-an-extension)
28
+ - [Use a custom file system](#use-a-custom-file-system)
29
+ - [Use Cases](#use-cases)
30
+ - [Design Goals](#design-goals)
22
31
  - [API](#api)
23
32
  - [`canParseUrl(input[, base])`](#canparseurlinput-base)
24
33
  - [`cwd()`](#cwd)
@@ -59,7 +68,6 @@
59
68
  - [`Awaitable<T>`](#awaitablet)
60
69
  - [`BufferEncodingMap`](#bufferencodingmap)
61
70
  - [`BufferEncoding`](#bufferencoding)
62
- - [`ChangeExtFn<[Ext]>`](#changeextfnext)
63
71
  - [`ConditionMap`](#conditionmap)
64
72
  - [`Condition`](#condition)
65
73
  - [`Dot`](#dot)
@@ -67,8 +75,10 @@
67
75
  - [`EmptyObject`](#emptyobject)
68
76
  - [`EmptyString`](#emptystring)
69
77
  - [`Ext`](#ext)
78
+ - [`ExtensionRewrites`](#extensionrewrites)
70
79
  - [`FileContent`](#filecontent)
71
80
  - [`FileSystem`](#filesystem)
81
+ - [`GetNewExtension<[T]>`](#getnewextensiont)
72
82
  - [`GetSourceContext`](#getsourcecontext)
73
83
  - [`GetSourceHandler`](#getsourcehandler)
74
84
  - [`GetSourceHandlers`](#getsourcehandlers)
@@ -94,13 +104,44 @@
94
104
  - [`Stat`](#stat)
95
105
  - [`Stats`](#stats)
96
106
  - Additional Documentation
97
- - [Resolution Algorithm](./docs/resolution-algorithm.md)
107
+ - [Resolution Algorithm][resolution-algorithm]
108
+ - [Sponsors](#sponsors)
98
109
  - [Contribute](#contribute)
99
110
 
100
111
  ## What is this?
101
112
 
102
- `mlly` is a set of [ECMAScript module][node-esm] (ESM) utilities.\
103
- It exposes several tools to bridge the gap between developer experience and the current state of ECMAScript modules.
113
+ `@flex-development/mlly` is a set of [ECMAScript module][node-esm] (ESM) utilities
114
+ for building modern, Node.js-compatible tooling. It implements Node's [resolution algorithms][resolution-algorithm]
115
+ and provides additional helpers for resolving modules in real-world projects.
116
+
117
+ ### Features
118
+
119
+ - Comply with Node.js [resolution algorithms][resolution-algorithm]
120
+ - Resolve `exports` and `imports` maps (including subpaths + patterns)
121
+ - Support condition-based resolution (`development`, `production`, custom conditions, etc.)
122
+ - Support configurable `main` fields for legacy / fallback entrypoints
123
+ - Enhance DX/UX with high-level [`resolveModule`](#resolvemoduletspecifier-parent-options) ergonomics
124
+ on top of spec-compliant resolution
125
+ - Extensionless and directory index resolution
126
+ - Path alias resolution (TypeScript-style mappings)
127
+ - Rewrite file extensions (useful for TS/MTS/CTS, build outputs, or dual publishing)
128
+ - Scopeless `@types/*` resolution (`unist` → `@types/unist`)
129
+ - Utilities for specifier classification and conversion
130
+ - Work with custom file system adapters (sync/async, in-memory, browser)
131
+
132
+ ## Why this package?
133
+
134
+ Node's ESM support is powerful, but writing tooling around it can be painful.
135
+
136
+ If you're building a CLI, bundler, runtime tool, or plugin system, you often need to:
137
+
138
+ - resolve modules the same way Node does
139
+ - support `exports` / `imports` correctly
140
+ - work with custom resolution conditions
141
+ - handle extensionless paths, directory indexes, and TypeScript configurations
142
+
143
+ `@flex-development/mlly` provides these building blocks in a spec-aligned way,
144
+ while also bridging gaps in developer experience.
104
145
 
105
146
  ## Install
106
147
 
@@ -133,67 +174,139 @@ In browsers with [`esm.sh`][esmsh]:
133
174
  </script>
134
175
  ```
135
176
 
136
- ## Use
177
+ ## Quick Start
178
+
179
+ ### Resolve like Node.js
137
180
 
138
181
  ```ts
139
- import {
140
- lookupPackageScope,
141
- readPackageJson,
142
- resolveModule,
143
- type FileSystem
144
- } from '@flex-development/mlly'
145
- import pkg from '@flex-development/mlly/package.json' with { type: 'json' }
146
- import type { PackageJson } from '@flex-development/pkg-types'
147
- import nfs from 'node:fs'
182
+ import { moduleResolve } from '@flex-development/mlly'
148
183
 
149
184
  /**
150
- * A file system API with both asynchronous and synchronous methods.
185
+ * The resolved URL.
151
186
  *
152
- * @const {FileSystem} fs
187
+ * @const {URL} resolved
153
188
  */
154
- const fs: FileSystem = {
155
- readFile: nfs.promises.readFile,
156
- realpath: nfs.promises.realpath,
157
- stat: nfs.statSync
158
- }
189
+ const resolved: URL = moduleResolve('typescript', import.meta.url)
190
+ ```
191
+
192
+ ### Resolve with custom conditions
193
+
194
+ ```ts
195
+ import { moduleResolve } from '@flex-development/mlly'
159
196
 
160
197
  /**
161
- * The URL of the package directory.
198
+ * The resolved URL.
162
199
  *
163
- * @const {URL | null} scope
200
+ * @const {URL} resolved
164
201
  */
165
- const scope: URL | null = lookupPackageScope(import.meta.url, null, fs)
202
+ const resolved: URL = moduleResolve('devlop', import.meta.url, ['development'])
203
+ ```
166
204
 
167
- console.dir(scope) // file:///Users/lex/Projects/flex-development/mlly/
205
+ ### Resolve a directory index
206
+
207
+ ```ts
208
+ import { resolveModule } from '@flex-development/mlly'
168
209
 
169
210
  /**
170
- * The package manifest.
211
+ * The resolved URL.
171
212
  *
172
- * @const {PackageJson | null} manifest
213
+ * @const {URL} resolved
173
214
  */
174
- const manifest: PackageJson | null = await readPackageJson(
175
- scope,
176
- null,
177
- import.meta.url,
178
- fs
179
- )
215
+ const resolved: URL = resolveModule('./src/lib', import.meta.url, {
216
+ extensions: ['.mts']
217
+ })
218
+ ```
180
219
 
181
- console.dir(manifest?.name === pkg.name) // true
182
- console.dir(manifest) // `pkg`
220
+ ### Resolve path aliases
221
+
222
+ ```ts
223
+ import { resolveModule, type Aliases } from '@flex-development/mlly'
224
+
225
+ /**
226
+ * The path mappings dictionary.
227
+ *
228
+ * @const {Aliases} aliases
229
+ */
230
+ const aliases: Aliases = {
231
+ '@/internal': './src/internal',
232
+ '@/internal/*': './src/internal/*'
233
+ }
183
234
 
184
235
  /**
185
- * A fully resolved URL.
236
+ * The resolved directory URL.
237
+ *
238
+ * @const {URL} directory
239
+ */
240
+ const directory: URL = resolveModule('@/internal', import.meta.url, { aliases })
241
+
242
+ /**
243
+ * The resolved file URL.
244
+ *
245
+ * @const {URL} file
246
+ */
247
+ const file: URL = resolveModule('@/internal/fs', import.meta.url, { aliases })
248
+
249
+ console.dir(directory)
250
+ console.dir(file)
251
+ ```
252
+
253
+ ### Rewrite an extension
254
+
255
+ ```ts
256
+ import { resolveModule } from '@flex-development/mlly'
257
+
258
+ /**
259
+ * The resolved URL.
186
260
  *
187
261
  * @const {URL} resolved
188
262
  */
189
- const resolved = resolveModule(pkg.name, import.meta.url, {
190
- conditions: new Set(['mlly']),
191
- ext: null
263
+ const resolved: URL = resolveModule('./src/index.ts', import.meta.url, {
264
+ ext: { '.cts': '.cjs', '.mts': '.mjs', '.ts': '.js' }
192
265
  })
266
+ ```
193
267
 
194
- console.dir(resolved) // file:///Users/lex/Projects/flex-development/mlly/src/
268
+ ### Use a custom file system
269
+
270
+ ```ts
271
+ import vfs from '#internal/vfs' // a virtual, async file system
272
+ import { resolveModule } from '@flex-development/mlly'
273
+
274
+ /**
275
+ * The resolved URL.
276
+ *
277
+ * @const {URL} resolved
278
+ */
279
+ const resolved: URL = await resolveModule('react', import.meta.url, { fs: vfs })
195
280
  ```
196
281
 
282
+ <br />
283
+
284
+ > \:eyes: See the [API reference](#api) for lower-level building blocks and resolution primitives.
285
+
286
+ ## Use Cases
287
+
288
+ `mlly` is designed for developers who need Node-compatible ESM resolution behavior.
289
+
290
+ Common use cases include:
291
+
292
+ - CLI tools that load user config files and/or plugins
293
+ - Plugin systems that resolve modules relative to a project root
294
+ - Bundlers and transpilers that need to interpret `exports`, `imports`, and `main` fields
295
+ - Test runners and code execution tools
296
+ - Monorepo tooling that resolves workspace dependencies
297
+ - Runtime loaders and developer tools that operate on virtual filesystems
298
+ - Framework tooling that needs resolution behavior matching Node.js
299
+
300
+ ## Design Goals
301
+
302
+ This package is built around a few core goals:
303
+
304
+ - Spec-compliant behavior where it matters (resolution rules, `exports`/`imports`)
305
+ - Developer-friendly ergonomics for real-world projects and workflows
306
+ - TypeScript-first APIs with strong typing and predictable return values
307
+ - Composable building blocks (low-level primitives + high-level helpers)
308
+ - Testability and deterministic behavior across environments
309
+
197
310
  ## API
198
311
 
199
312
  `mlly` exports the identifiers listed below.
@@ -204,7 +317,7 @@ There is no default export.
204
317
 
205
318
  Check if `input` can be parsed to a `URL`.
206
319
 
207
- > 👉 **Note**: If `input` is relative, `base` is required.
320
+ > \:point\_right: **Note**: If `input` is relative, `base` is required.
208
321
  > If `input` is absolute, `base` is ignored.
209
322
 
210
323
  #### Parameters
@@ -270,7 +383,7 @@ const enum formats {
270
383
 
271
384
  Get the source code for a module.
272
385
 
273
- > 👉 **Note**: Returns a promise if the [handler](#getsourcehandler) for `id` is async.
386
+ > \:point\_right: **Note**: Returns a promise if the [handler](#getsourcehandler) for `id` is async.
274
387
 
275
388
  #### Type Parameters
276
389
 
@@ -296,7 +409,7 @@ Get the source code for a module.
296
409
 
297
410
  Check if `value` is an *absolute specifier*.
298
411
 
299
- > 👉 **Note**: Only checks specifier syntax.\
412
+ > \:point\_right: **Note**: Only checks specifier syntax.\
300
413
  > Does **not** guarantee the specifier references an existing module.
301
414
 
302
415
  #### Parameters
@@ -325,7 +438,7 @@ Check if `value` is a valid array index.
325
438
 
326
439
  Check if `value` is a *bare specifier*.
327
440
 
328
- > 👉 **Note**: Only checks specifier syntax.\
441
+ > \:point\_right: **Note**: Only checks specifier syntax.\
329
442
  > Does **not** guarantee the specifier references an existing module.
330
443
 
331
444
  #### Parameters
@@ -341,7 +454,7 @@ Check if `value` is a *bare specifier*.
341
454
 
342
455
  Check if a directory exists.
343
456
 
344
- > 👉 **Note**: Returns a promise if `fs.stat` is async.
457
+ > \:point\_right: **Note**: Returns a promise if `fs.stat` is async.
345
458
 
346
459
  #### Type Parameters
347
460
 
@@ -363,7 +476,7 @@ Check if a directory exists.
363
476
 
364
477
  Check if a file exists.
365
478
 
366
- > 👉 **Note**: Returns a promise if `fs.stat` is async.
479
+ > \:point\_right: **Note**: Returns a promise if `fs.stat` is async.
367
480
 
368
481
  #### Type Parameters
369
482
 
@@ -385,7 +498,7 @@ Check if a file exists.
385
498
 
386
499
  Check if `value` is an [`imports`][subpath-imports] subpath.
387
500
 
388
- > 👉 **Note**: Only checks specifier syntax.\
501
+ > \:point\_right: **Note**: Only checks specifier syntax.\
389
502
  > Does **not** guarantee the specifier references an existing module.
390
503
 
391
504
  #### Parameters
@@ -414,7 +527,7 @@ Check if `value` is a module id.
414
527
 
415
528
  Check if `value` is a *relative specifier*.
416
529
 
417
- > 👉 **Note**: Only checks specifier syntax.\
530
+ > \:point\_right: **Note**: Only checks specifier syntax.\
418
531
  > Does **not** guarantee the specifier references an existing module.
419
532
 
420
533
  #### Parameters
@@ -436,7 +549,7 @@ Resolve a [`main`][main]-like package entry point.
436
549
 
437
550
  Implements the [`LEGACY_MAIN_RESOLVE`][algorithm-legacy-main-resolve] resolution algorithm.
438
551
 
439
- > 👉 **Note**: Returns a promise if `fs.stat` is async.
552
+ > \:point\_right: **Note**: Returns a promise if `fs.stat` is async.
440
553
 
441
554
  #### Type Parameters
442
555
 
@@ -471,7 +584,7 @@ Get the package scope URL for a module `url`.
471
584
 
472
585
  Implements the [`LOOKUP_PACKAGE_SCOPE`][algorithm-lookup-package-scope] algorithm.
473
586
 
474
- > 👉 **Note**: Returns a promise if `fs.stat` is async.
587
+ > \:point\_right: **Note**: Returns a promise if `fs.stat` is async.
475
588
 
476
589
  #### Overloads
477
590
 
@@ -522,8 +635,8 @@ Resolve a module `specifier`.
522
635
 
523
636
  Implements the [`ESM_RESOLVE`][algorithm-esm-resolve] algorithm.
524
637
 
525
- > 👉 **Note**: Returns a promise if `fs.realpath` or `fs.stat` is async, or one the following methods returns a promise:
526
- > [`packageImportsResolve`](#packageimportsresolvetspecifier-parent-conditions-mainfields-fs),
638
+ > \:point\_right: **Note**: Returns a promise if `fs.realpath` or `fs.stat` is async, or one the following methods
639
+ > returns a promise: [`packageImportsResolve`](#packageimportsresolvetspecifier-parent-conditions-mainfields-fs),
527
640
  > [`packageResolve`](#packageresolvetspecifier-parent-conditions-mainfields-fs).
528
641
 
529
642
  #### Type Parameters
@@ -562,7 +675,7 @@ Resolve a package export.
562
675
 
563
676
  Implements the [`PACKAGE_EXPORTS_RESOLVE`][algorithm-package-exports-resolve] algorithm.
564
677
 
565
- > 👉 **Note**: Never returns a promisee.
678
+ > \:point\_right: **Note**: Never returns a promisee.
566
679
 
567
680
  #### Type Parameters
568
681
 
@@ -599,7 +712,7 @@ Resolve a package export or import.
599
712
 
600
713
  Implements the [`PACKAGE_IMPORTS_EXPORTS_RESOLVE`][algorithm-package-imports-exports-resolve] algorithm.
601
714
 
602
- > 👉 **Note**: Returns a promise if
715
+ > \:point\_right: **Note**: Returns a promise if
603
716
  > [`packageTargetResolve`](#packagetargetresolvetpackageurl-target-subpath-patternmatch-isimports-conditions-mainfields-parent-fs),
604
717
  > returns a promise.
605
718
 
@@ -643,7 +756,7 @@ Resolve a package import.
643
756
 
644
757
  Implements the [`PACKAGE_IMPORTS_RESOLVE`][algorithm-package-imports-resolve] algorithm.
645
758
 
646
- > 👉 **Note**: Returns a promise if [`lookupPackageScope`](#lookuppackagescopeturl-end-fs),
759
+ > \:point\_right: **Note**: Returns a promise if [`lookupPackageScope`](#lookuppackagescopeturl-end-fs),
647
760
  > [`packageImportsExportsResolve`](#packageimportsexportsresolvetmatchkey-matchobject-packageurl-isimports-conditions-mainfields-parent-fs),
648
761
  > or [`readPackageJson`](#readpackagejsontid-specifier-parent-fs) returns a promise.
649
762
 
@@ -690,7 +803,7 @@ Implements the [`PACKAGE_RESOLVE`][algorithm-package-resolve] algorithm.
690
803
  > package name, or a specific feature module within a package prefixed by the package name.
691
804
  > Including the file extension is only necessary for packages without an [`exports`][exports] field.
692
805
 
693
- > 👉 **Note**: Returns a promise if `fs.stat` is async or one of the following methods returns a promise:
806
+ > \:point\_right: **Note**: Returns a promise if `fs.stat` is async or one of the following methods returns a promise:
694
807
  > [`legacyMainResolve`](#legacymainresolvetpackageurl-manifest-mainfields-parent-fs),
695
808
  > [`packageExportsResolve`](#packageexportsresolvetpackageurl-subpath-exports-conditions-parent-fs),
696
809
  > [`packageSelfResolve`](#packageselfresolvetname-subpath-parent-conditions-fs), or
@@ -735,7 +848,7 @@ Resolve the self-import of a package.
735
848
 
736
849
  Implements the [`PACKAGE_SELF_RESOLVE`][algorithm-package-self-resolve] algorithm.
737
850
 
738
- > 👉 **Note**: Returns a promise if [`lookupPackageScope`](#lookuppackagescopeturl-end-fs),
851
+ > \:point\_right: **Note**: Returns a promise if [`lookupPackageScope`](#lookuppackagescopeturl-end-fs),
739
852
  > [`packageExportsResolve`](#packageexportsresolvetpackageurl-subpath-exports-conditions-parent-fs),
740
853
  > or [`readPackageJson`](#readpackagejsontid-specifier-parent-fs) returns a promise.
741
854
 
@@ -772,7 +885,7 @@ Resolve a package target.
772
885
 
773
886
  Implements the [`PACKAGE_TARGET_RESOLVE`][algorithm-package-target-resolve] algorithm.
774
887
 
775
- > 👉 **Note**: Returns a promise if `target` is internal to the package and
888
+ > \:point\_right: **Note**: Returns a promise if `target` is internal to the package and
776
889
  > [`packageResolve`](#packageresolvetspecifier-parent-conditions-mainfields-fs) returns a promise.
777
890
 
778
891
  #### Type Parameters
@@ -851,7 +964,7 @@ Read a `package.json` file.
851
964
 
852
965
  Implements the [`READ_PACKAGE_JSON`][algorithm-read-package-json] algorithm.
853
966
 
854
- > 👉 **Note**: Returns a promise if `fs.readFile` or `fs.stat` is async.
967
+ > \:point\_right: **Note**: Returns a promise if `fs.readFile` or `fs.stat` is async.
855
968
 
856
969
  #### Overloads
857
970
 
@@ -886,7 +999,7 @@ function readPackageJson<T extends Awaitable<PackageJson | null>>(
886
999
  — the url of the package directory, the `package.json` file, or a module in the same directory as a `package.json`
887
1000
  - `specifier` (`string` | `null` | `undefined`)
888
1001
  — the module specifier that initiated the reading of the `package.json` file
889
- > 👉 **note**: should be a `file:` url if `parent` is not a url
1002
+ > \:point\_right: **note**: should be a `file:` url if `parent` is not a url
890
1003
  - `parent` ([`ModuleId`](#moduleid) | `null` | `undefined`)
891
1004
  — the url of the parent module
892
1005
  - `fs` ([`FileSystem`](#filesystem) | `null` | `undefined`)
@@ -909,7 +1022,7 @@ Resolve an aliased `specifier`.
909
1022
  - `specifier` (`string`)
910
1023
  — the specifier using an alias
911
1024
  - `options` ([`ResolveAliasOptions`](#resolvealiasoptions) | `null` | `undefined`)
912
- — alias resolution options
1025
+ options for alias resolution
913
1026
 
914
1027
  #### Returns
915
1028
 
@@ -923,13 +1036,12 @@ Implements the [`ESM_RESOLVE`][algorithm-esm-resolve] algorithm, mostly \:wink:.
923
1036
 
924
1037
  Adds support for:
925
1038
 
926
- - Changing file extensions
927
- - Directory index resolution
928
- - Extensionless file resolution
1039
+ - Extensionless and directory index resolution
929
1040
  - Path alias resolution
1041
+ - Rewrite file extensions
930
1042
  - Scopeless `@types/*` resolution (i.e. `unist` -> `@types/unist`)
931
1043
 
932
- > 👉 **Note**: Returns a promise if
1044
+ > \:point\_right: **Note**: Returns a promise if
933
1045
  > [`moduleResolve`](#moduleresolvetspecifier-parent-conditions-mainfields-preservesymlinks-fs) returns a promise.
934
1046
 
935
1047
  #### Type Parameters
@@ -944,7 +1056,7 @@ Adds support for:
944
1056
  - `parent` ([`ModuleId`](#moduleid))
945
1057
  — the url of the parent module
946
1058
  - `options` ([`ResolveModuleOptions`](#resolvemoduleoptions))
947
- — module resolution options
1059
+ options for module resolution
948
1060
 
949
1061
  #### Returns
950
1062
 
@@ -973,7 +1085,7 @@ The URL of the file system root.
973
1085
 
974
1086
  Turn `url` into a *relative specifier*.
975
1087
 
976
- > 👉 **Note**: The relative specifier will only have a file extension if `specifier` also has an extension.
1088
+ > \:point\_right: **Note**: The relative specifier will only have a file extension if `specifier` also has an extension.
977
1089
 
978
1090
  #### Parameters
979
1091
 
@@ -990,7 +1102,7 @@ Turn `url` into a *relative specifier*.
990
1102
 
991
1103
  Convert `id` to a `URL`.
992
1104
 
993
- > 👉 **Note**: If `id` cannot be parsed as a `URL` and is also not a [builtin module][builtin-module],
1105
+ > \:point\_right: **Note**: If `id` cannot be parsed as a `URL` and is also not a [builtin module][builtin-module],
994
1106
  > it will be assumed to be a path and converted to a [`file:` URL][file-url].
995
1107
 
996
1108
  #### Parameters
@@ -1040,7 +1152,7 @@ type Awaitable<T> = PromiseLike<T> | T
1040
1152
  #### Type Parameters
1041
1153
 
1042
1154
  - `T` (`any`)
1043
- - the value
1155
+ the value
1044
1156
 
1045
1157
  ### `BufferEncodingMap`
1046
1158
 
@@ -1067,34 +1179,6 @@ They will be added to this union automatically.
1067
1179
  type BufferEncoding = BufferEncodingMap[keyof BufferEncodingMap]
1068
1180
  ```
1069
1181
 
1070
- ### `ChangeExtFn<[Ext]>`
1071
-
1072
- Get a new file extension for `url` (`type`).
1073
-
1074
- Returning an empty string (`''`), `null`, or `undefined` will remove the current file extension.
1075
-
1076
- ```ts
1077
- type ChangeExtFn<
1078
- Ext extends string | null | undefined = string | null | undefined
1079
- > = (this: void, url: URL, specifier: string) => Ext
1080
- ```
1081
-
1082
- #### Type Parameters
1083
-
1084
- - `Ext` (`string` | `null` | `undefined`, optional)
1085
- — the new file extension
1086
-
1087
- #### Parameters
1088
-
1089
- - `url` (`URL`)
1090
- — the resolved module URL
1091
- - `specifier` (`string`)
1092
- — the module specifier being resolved
1093
-
1094
- #### Returns
1095
-
1096
- (`Ext`) The new file extension
1097
-
1098
1182
  ### `ConditionMap`
1099
1183
 
1100
1184
  Registry of export/import conditions (`interface`).
@@ -1160,6 +1244,20 @@ A file extension (`type`).
1160
1244
  type Ext = `${Dot}${string}`
1161
1245
  ```
1162
1246
 
1247
+ ### `ExtensionRewrites`
1248
+
1249
+ Record, where each key is the file extension of a module specifier
1250
+ and each value is a replacement file extension (`type`).
1251
+
1252
+ > 👉 **Note**: Replacement file extensions are normalized and do not need to begin with a dot character (`'.'`);
1253
+ > falsy values will remove an extension.
1254
+
1255
+ ```ts
1256
+ type ExtensionRewrites = {
1257
+ [K in EmptyString | Ext]?: string | false | null | undefined
1258
+ }
1259
+ ```
1260
+
1163
1261
  ### `FileContent`
1164
1262
 
1165
1263
  Union of values that can occur where file content is expected (`type`).
@@ -1181,6 +1279,38 @@ The file system API (`interface`).
1181
1279
  - `stat` ([`Stat`](#stat))
1182
1280
  — get information about a directory or file
1183
1281
 
1282
+ ### `GetNewExtension<[T]>`
1283
+
1284
+ Get a new file extension for a `url` (`type`).
1285
+
1286
+ Returning an empty string (`''`), `false`, `null`, or `undefined` will remove the current file extension.
1287
+
1288
+ ```ts
1289
+ type GetNewExtension<
1290
+ T extends string | false | null | undefined =
1291
+ | string
1292
+ | false
1293
+ | null
1294
+ | undefined
1295
+ > = (this: void, url: URL, specifier: string) => T
1296
+ ```
1297
+
1298
+ #### Type Parameters
1299
+
1300
+ - `T` (`string` | `false` | `null` | `undefined`, optional)
1301
+ — the new file extension
1302
+
1303
+ #### Parameters
1304
+
1305
+ - `url` (`URL`)
1306
+ — the resolved module URL
1307
+ - `specifier` (`string`)
1308
+ — the module specifier being resolved
1309
+
1310
+ #### Returns
1311
+
1312
+ (`T`) The new file extension
1313
+
1184
1314
  ### `GetSourceContext`
1185
1315
 
1186
1316
  Source code retrieval context (`interface`).
@@ -1240,7 +1370,7 @@ Options for retrieving source code (`interface`).
1240
1370
 
1241
1371
  - `encoding?` ([`BufferEncoding`](#bufferencoding) | `null` | `undefined`)
1242
1372
  — the encoding of the result
1243
- > 👉 **note**: used when the `file:` handler is called
1373
+ > \:point\_right: **note**: used when the `file:` handler is called
1244
1374
  - `format?` ([`ModuleFormat`](#moduleformat) | `null` | `undefined`)
1245
1375
  — the module format hint
1246
1376
  - `fs?` ([`FileSystem`](#filesystem) | `null` | `undefined`)
@@ -1282,7 +1412,7 @@ type List<T = unknown> = ReadonlySet<T> | readonly T[]
1282
1412
  #### Type Parameters
1283
1413
 
1284
1414
  - `T` (`any`, optional)
1285
- — list item type
1415
+ the list item type
1286
1416
 
1287
1417
  ### `MainFieldMap`
1288
1418
 
@@ -1443,7 +1573,7 @@ Read the entire contents of a file (`interface`).
1443
1573
 
1444
1574
  Compute a canonical pathname by resolving `.`, `..`, and symbolic links (`interface`).
1445
1575
 
1446
- > 👉 **Note**: A canonical pathname is not necessarily unique.
1576
+ > \:point\_right: **Note**: A canonical pathname is not necessarily unique.
1447
1577
  > Hard links and bind mounts can expose an entity through many pathnames.
1448
1578
 
1449
1579
  #### Type Parameters
@@ -1471,7 +1601,7 @@ Options for path alias resolution (`interface`).
1471
1601
  if `true`, the resolved specifier will be a [`file:` URL][file-url]
1472
1602
  - `aliases?` ([`Aliases`](#aliases) | `null` | `undefined`)
1473
1603
  — the path mappings dictionary
1474
- > 👉 **note**: paths should be relative to `cwd`
1604
+ > \:point\_right: **note**: paths should be relative to `cwd`
1475
1605
  - `cwd?` ([`ModuleId`](#moduleid) | `null` | `undefined`)
1476
1606
  — the url of the directory to resolve non-absolute modules from
1477
1607
  - **default**: [`cwd()`](#cwd)
@@ -1480,33 +1610,36 @@ Options for path alias resolution (`interface`).
1480
1610
 
1481
1611
  ### `ResolveModuleOptions`
1482
1612
 
1483
- Options for path alias resolution (`interface`).
1613
+ Options for module resolution (`interface`).
1484
1614
 
1485
1615
  #### Properties
1486
1616
 
1487
1617
  - `aliases?` ([`Aliases`](#aliases) | `null` | `undefined`)
1488
1618
  — the path mappings dictionary
1489
- > 👉 **note**: paths should be relative to `cwd`
1619
+ > \:point\_right: **note**: paths should be relative to `cwd`
1490
1620
  - `conditions?` ([`List<Condition>`](#condition) | `null` | `undefined`)
1491
1621
  — the list of export/import conditions
1492
1622
  - **default**: [`defaultConditions`](#defaultconditions)
1493
- > 👉 **note**: should be sorted by priority
1623
+ > \:point\_right: **note**: should be sorted by priority
1494
1624
  - `cwd?` ([`ModuleId`](#moduleid) | `null` | `undefined`)
1495
1625
  — the url of the directory to resolve path `aliases` from
1496
1626
  - **default**: [`cwd()`](#cwd)
1497
- - `ext?` ([`ChangeExtFn`](#changeextfnext) | `string` | `null` | `undefined`)
1498
- a replacement file extension or a function that returns a file extension.
1499
- > 👉 **note**: an empty string (`''`) or `null` will remove a file extension
1627
+ <!--lint disable-->
1628
+ - `ext?` ([`ExtensionRewrites`](#extensionrewrites) | [`GetNewExtension`](#getnewextensiont) | `false` | `string` | `null` | `undefined`)
1629
+ a replacement file extension, a record of replacement file extensions, or a function that returns a replacement file extension
1630
+ <!--lint enable-->
1631
+ > \:point\_right: **note**: replacement file extensions are normalized and do not need to begin
1632
+ > with a dot character (`'.'`); an empty string (`''`), `false`, or `null` will remove an extension
1500
1633
  - `extensions?` ([`List<string>`](#listt) | `null` | `undefined`)
1501
1634
  — the module extensions to probe for
1502
1635
  - **default**: [`defaultExtensions`](#defaultextensions)
1503
- > 👉 **note**: should be sorted by priority
1636
+ > \:point\_right: **note**: should be sorted by priority
1504
1637
  - `fs?` ([`FileSystem`](#filesystem) | `null` | `undefined`)
1505
1638
  — the file system api
1506
1639
  - `mainFields?` ([`List<MainField>`](#mainfield) | `null` | `undefined`)
1507
1640
  — the list of legacy `main` fields
1508
1641
  - **default**: [`defaultMainFields`](#defaultmainfields)
1509
- > 👉 **note**: should be sorted by priority
1642
+ > \:point\_right: **note**: should be sorted by priority
1510
1643
  - `preserveSymlinks?` (`boolean` | `null` | `undefined`)
1511
1644
  — whether to keep symlinks instead of resolving them
1512
1645
 
@@ -1539,12 +1672,16 @@ An object describing a directory or file (`interface`).
1539
1672
  - `isFile` ([`IsFile`](#isfile))
1540
1673
  — check if the stats object describes a file
1541
1674
 
1675
+ ## Sponsors
1676
+
1677
+ If you find this package helpful, consider sponsoring to support maintenance, tests, and long-term stability.
1678
+
1542
1679
  ## Contribute
1543
1680
 
1544
1681
  See [`CONTRIBUTING.md`](CONTRIBUTING.md).
1545
1682
 
1546
- This project has a [code of conduct](./CODE_OF_CONDUCT.md). By interacting with this repository, organization, or
1547
- community you agree to abide by its terms.
1683
+ This project has a [code of conduct](./CODE_OF_CONDUCT.md).
1684
+ By interacting with this repository, organization, or community you agree to abide by its terms.
1548
1685
 
1549
1686
  [algorithm-esm-resolve]: ./docs/resolution-algorithm.md#esm_resolvespecifier-parent-conditions-mainfields-preservesymlinks-extensionformatmap
1550
1687
 
@@ -1604,6 +1741,8 @@ community you agree to abide by its terms.
1604
1741
 
1605
1742
  [pkg-package-json]: https://github.com/flex-development/pkg-types/blob/main/src/package-json.ts
1606
1743
 
1744
+ [resolution-algorithm]: ./docs/resolution-algorithm.md
1745
+
1607
1746
  [subpath-imports]: https://nodejs.org/api/packages.html#subpath-imports
1608
1747
 
1609
1748
  [typescript]: https://www.typescriptlang.org