Skip to content

Commit efdcc40

Browse files
committed
feat(lib)!: [resolveModule] support extension rewrite map
- `ChangeExtFn` -> `GetNewExtension` Signed-off-by: Lexus Drumgold <unicornware@flexdevelopment.llc>
1 parent 38c36b4 commit efdcc40

14 files changed

Lines changed: 257 additions & 133 deletions

README.md

Lines changed: 91 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
- [Resolve like Node.js](#resolve-like-nodejs)
2424
- [Resolve with custom conditions](#resolve-with-custom-conditions)
2525
- [Resolve a directory index](#resolve-a-directory-index)
26+
- [Resolve path aliases](#resolve-path-aliases)
2627
- [Rewrite an extension](#rewrite-an-extension)
2728
- [Use a custom file system](#use-a-custom-file-system)
2829
- [Use Cases](#use-cases)
@@ -67,16 +68,17 @@
6768
- [`Awaitable<T>`](#awaitablet)
6869
- [`BufferEncodingMap`](#bufferencodingmap)
6970
- [`BufferEncoding`](#bufferencoding)
70-
- [`ChangeExtFn<[Ext]>`](#changeextfnext)
7171
- [`ConditionMap`](#conditionmap)
7272
- [`Condition`](#condition)
7373
- [`Dot`](#dot)
7474
- [`EmptyArray`](#emptyarray)
7575
- [`EmptyObject`](#emptyobject)
7676
- [`EmptyString`](#emptystring)
7777
- [`Ext`](#ext)
78+
- [`ExtensionRewrites`](#extensionrewrites)
7879
- [`FileContent`](#filecontent)
7980
- [`FileSystem`](#filesystem)
81+
- [`GetNewExtension<[T]>`](#getnewextensiont)
8082
- [`GetSourceContext`](#getsourcecontext)
8183
- [`GetSourceHandler`](#getsourcehandler)
8284
- [`GetSourceHandlers`](#getsourcehandlers)
@@ -215,6 +217,39 @@ const resolved: URL = resolveModule('./src/lib', import.meta.url, {
215217
})
216218
```
217219

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+
}
234+
235+
/**
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+
218253
### Rewrite an extension
219254

220255
```ts
@@ -987,7 +1022,7 @@ Resolve an aliased `specifier`.
9871022
- `specifier` (`string`)
9881023
the specifier using an alias
9891024
- `options` ([`ResolveAliasOptions`](#resolvealiasoptions) | `null` | `undefined`)
990-
alias resolution options
1025+
options for alias resolution
9911026

9921027
#### Returns
9931028

@@ -1021,7 +1056,7 @@ Adds support for:
10211056
- `parent` ([`ModuleId`](#moduleid))
10221057
the url of the parent module
10231058
- `options` ([`ResolveModuleOptions`](#resolvemoduleoptions))
1024-
module resolution options
1059+
options for module resolution
10251060

10261061
#### Returns
10271062

@@ -1144,34 +1179,6 @@ They will be added to this union automatically.
11441179
type BufferEncoding = BufferEncodingMap[keyof BufferEncodingMap]
11451180
```
11461181

1147-
### `ChangeExtFn<[Ext]>`
1148-
1149-
Get a new file extension for `url` (`type`).
1150-
1151-
Returning an empty string (`''`), `null`, or `undefined` will remove the current file extension.
1152-
1153-
```ts
1154-
type ChangeExtFn<
1155-
Ext extends string | null | undefined = string | null | undefined
1156-
> = (this: void, url: URL, specifier: string) => Ext
1157-
```
1158-
1159-
#### Type Parameters
1160-
1161-
- `Ext` (`string` | `null` | `undefined`, optional)
1162-
the new file extension
1163-
1164-
#### Parameters
1165-
1166-
- `url` (`URL`)
1167-
the resolved module URL
1168-
- `specifier` (`string`)
1169-
the module specifier being resolved
1170-
1171-
#### Returns
1172-
1173-
(`Ext`) The new file extension
1174-
11751182
### `ConditionMap`
11761183

11771184
Registry of export/import conditions (`interface`).
@@ -1237,6 +1244,20 @@ A file extension (`type`).
12371244
type Ext = `${Dot}${string}`
12381245
```
12391246

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+
12401261
### `FileContent`
12411262

12421263
Union of values that can occur where file content is expected (`type`).
@@ -1258,6 +1279,38 @@ The file system API (`interface`).
12581279
- `stat` ([`Stat`](#stat))
12591280
get information about a directory or file
12601281

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+
12611314
### `GetSourceContext`
12621315

12631316
Source code retrieval context (`interface`).
@@ -1557,7 +1610,7 @@ Options for path alias resolution (`interface`).
15571610

15581611
### `ResolveModuleOptions`
15591612

1560-
Options for path alias resolution (`interface`).
1613+
Options for module resolution (`interface`).
15611614

15621615
#### Properties
15631616

@@ -1571,9 +1624,12 @@ Options for path alias resolution (`interface`).
15711624
- `cwd?` ([`ModuleId`](#moduleid) | `null` | `undefined`)
15721625
the url of the directory to resolve path `aliases` from
15731626
- **default**: [`cwd()`](#cwd)
1574-
- `ext?` ([`ChangeExtFn`](#changeextfnext) | `string` | `null` | `undefined`)
1575-
a replacement file extension or a function that returns a file extension.
1576-
> \:point\_right: **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
15771633
- `extensions?` ([`List<string>`](#listt) | `null` | `undefined`)
15781634
the module extensions to probe for
15791635
- **default**: [`defaultExtensions`](#defaultextensions)

package.json

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -72,9 +72,7 @@
7272
"default": "./dist/interfaces/*.d.mts"
7373
},
7474
"#internal/fs": {
75-
"types": {
76-
"mlly": "./src/internal/fs.d.mts"
77-
},
75+
"types": "./src/internal/fs.d.mts",
7876
"browser": {
7977
"mlly": "./src/internal/fs.browser.mts",
8078
"default": "./dist/internal/fs.browser.mjs"

src/interfaces/__tests__/resolve-module-options.spec-d.mts

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,10 @@
55

66
import type TestSubject from '#interfaces/resolve-module-options'
77
import type {
8-
ChangeExtFn,
98
Condition,
9+
ExtensionRewrites,
1010
FileSystem,
11+
GetNewExtension,
1112
List,
1213
MainField
1314
} from '@flex-development/mlly'
@@ -24,10 +25,12 @@ describe('unit-d:interfaces/ResolveModuleOptions', () => {
2425
.toEqualTypeOf<Nilable<List<Condition>>>()
2526
})
2627

27-
it('should match [ext?: ChangeExtFn | string | null | undefined]', () => {
28-
expectTypeOf<TestSubject>()
29-
.toHaveProperty('ext')
30-
.toEqualTypeOf<Nilable<ChangeExtFn | string>>()
28+
it('should match [ext?: ExtensionRewrites | GetNewExtension | string | false | null | undefined]', () => {
29+
// Arrange
30+
type Expect = Nilable<ExtensionRewrites | GetNewExtension | string | false>
31+
32+
// Expect
33+
expectTypeOf<TestSubject>().toHaveProperty('ext').toEqualTypeOf<Expect>()
3134
})
3235

3336
it('should match [extensions?: List<string> | null | undefined]', () => {

src/interfaces/resolve-module-options.mts

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,17 @@
55

66
import type {
77
Aliases,
8-
ChangeExtFn,
98
Condition,
9+
ExtensionRewrites,
1010
FileSystem,
11+
GetNewExtension,
1112
List,
1213
MainField,
1314
ModuleId
1415
} from '@flex-development/mlly'
1516

1617
/**
17-
* Module resolution options.
18+
* Options for module resolution.
1819
*/
1920
interface ResolveModuleOptions {
2021
/**
@@ -49,14 +50,17 @@ interface ResolveModuleOptions {
4950
cwd?: ModuleId | null | undefined
5051

5152
/**
52-
* A replacement file extension or a function that returns a file extension.
53+
* A replacement file extension, a record of replacement file extensions,
54+
* or a function that returns a replacement file extension.
5355
*
54-
* > 👉 **Note**: An empty string (`''`) or `null` will
55-
* > remove a file extension.
56+
* > 👉 **Note**: Replacement file extensions are normalized and do not
57+
* > need to begin with a dot character (`'.'`); an empty string (`''`),
58+
* > `false`, or `null` will remove an extension.
5659
*
57-
* @see {@linkcode ChangeExtFn}
60+
* @see {@linkcode ExtensionRewrites}
61+
* @see {@linkcode GetNewExtension}
5862
*/
59-
ext?: ChangeExtFn | string | null | undefined
63+
ext?: ExtensionRewrites | GetNewExtension | string | false | null | undefined
6064

6165
/**
6266
* The module extensions to probe for.

src/lib/__snapshots__/node/resolve-module.snap

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ exports[`unit:lib/resolveModule > fs (default) > should return resolved URL ("mo
2626

2727
exports[`unit:lib/resolveModule > fs (default) > should return resolved URL ("node:test") 1`] = `"node:test"`;
2828

29-
exports[`unit:lib/resolveModule > fs (default) > should return resolved URL ("subpath-exports/lib/a.js") 1`] = `file://\${process.cwd()}/__fixtures__/node_modules/subpath-exports/lib/a.js`;
29+
exports[`unit:lib/resolveModule > fs (default) > should return resolved URL ("subpath-exports/lib/a.js") 1`] = `file://\${process.cwd()}/__fixtures__/node_modules/subpath-exports/lib/a`;
3030

3131
exports[`unit:lib/resolveModule > fs (default) > should return resolved URL ("unist") 1`] = `file://\${process.cwd()}/node_modules/@types/unist/index.d.ts`;
3232

@@ -56,6 +56,6 @@ exports[`unit:lib/resolveModule > fs (only async) > should return resolved URL (
5656

5757
exports[`unit:lib/resolveModule > fs (only async) > should return resolved URL ("node:test") 1`] = `"node:test"`;
5858

59-
exports[`unit:lib/resolveModule > fs (only async) > should return resolved URL ("subpath-exports/lib/a.js") 1`] = `file://\${process.cwd()}/__fixtures__/node_modules/subpath-exports/lib/a.js`;
59+
exports[`unit:lib/resolveModule > fs (only async) > should return resolved URL ("subpath-exports/lib/a.js") 1`] = `file://\${process.cwd()}/__fixtures__/node_modules/subpath-exports/lib/a`;
6060

6161
exports[`unit:lib/resolveModule > fs (only async) > should return resolved URL ("unist") 1`] = `file://\${process.cwd()}/node_modules/@types/unist/index.d.ts`;

src/lib/__tests__/resolve-module.spec.mts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ describe('unit:lib/resolveModule', () => {
5252
[pkg.name + '/package.json', import.meta.url],
5353
[pkgKronk.name, import.meta.url],
5454
[pkgPathe.name + '/dot', import.meta.url],
55-
[subpathExports.name + '/lib/a.js', parent]
55+
[subpathExports.name + '/lib/a.js', parent, { ext: { '.js': false } }]
5656
])('should return resolved URL (%j)', async (
5757
specifier,
5858
parent,

src/lib/resolve-alias.mts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ export default resolveAlias
2424
* @param {string} specifier
2525
* The specifier using an alias
2626
* @param {ResolveAliasOptions | null | undefined} [options]
27-
* Alias resolution options
27+
* Options for alias resolution
2828
* @return {string | null}
2929
* The specifier of the aliased module
3030
*/

0 commit comments

Comments
 (0)