Skip to content

Commit

Permalink
fix: array pattern implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
unional committed Jan 4, 2023
1 parent ca097d8 commit dc108a5
Show file tree
Hide file tree
Showing 5 changed files with 93 additions and 29 deletions.
30 changes: 30 additions & 0 deletions .changeset/breezy-plants-search.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
---
'resolve.imports': patch
---

Fix array pattern handling.

The array pattern actually does not return an array of resolved paths.
It returns the first match with condition support.

i.e.:

```ts
//=> "./a.js"
{
"imports": {
"#a": ["./a.js", "./b.js"]
}
}

// `conditions: ["node"] => "./node.js"`
// `conditions: undefined => "./browser.js"`
{
"imports": {
"#a": [
{ "node": "./node.js" },
"./browser.js"
]
}
}
```
24 changes: 23 additions & 1 deletion packages/resolve.imports/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,28 @@ resolve(manifest, '#internal/a.js')
```ts
import { resolve } from 'resolve.imports';

const manifest = {
content: {
imports: {
'#internal/*.js': ['./src/internal/*.js', './src/internal2/*.js'],
'#utils/*.js': [{ node: './src/utils/*.js' }, './src/utils2/*.js']
}
}
}

//=> './src/internal/foo.js'
resolve(manifest, '#internal/a.js')

//=> './src/utils/foo.js'
resolve(manifest, '#utils/a.js', { conditions: ['node'] })

//=> './src/utils2/foo.js'
resolve(manifest, '#utils/a.js')
```

```ts
import { resolve } from 'resolve.imports';

const manifest = {
content: {
imports: {
Expand All @@ -118,7 +140,7 @@ const manifest = {
}
}

//=> ['./src/internal/foo.js', './src/internal2/foo.js']
//=> './src/internal/foo.js'
resolve(manifest, '#internal/a.js')
```

Expand Down
30 changes: 18 additions & 12 deletions packages/resolve.imports/ts/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export type ImportsFieldManifest = {
}
}

export type ImportMap = string | string[] | { [key: string]: ImportMap }
export type ImportMap = string | Array<ImportMap> | { [key: string]: ImportMap }

export type ResolveOptions = {
/**
Expand All @@ -46,7 +46,7 @@ export function resolve(manifest: ImportsFieldManifest, specifier: string, optio
const conditions = new Set(options?.conditions ?? [])
const matched = manifest.content.imports[specifier]
if (matched) {
return noRecursive(lookupReplacer(matched, conditions))
return noRecursive(resolvePackagePattern(matched, conditions))
}

const expansionKeys = getExpensionKeys(Object.keys(manifest.content.imports))
Expand All @@ -55,11 +55,9 @@ export function resolve(manifest: ImportsFieldManifest, specifier: string, optio

const [prefix, suffix] = keyParts
if (specifier.startsWith(prefix)) {
const replacer = lookupReplacer(manifest.content.imports[key], conditions)
const replacer = resolvePackagePattern(manifest.content.imports[key], conditions)

if (replacer) return noRecursive(
Array.isArray(replacer) ? replacer.map(replacePattern) : replacePattern(replacer)
)
if (replacer) return noRecursive(replacePattern(replacer))
}

function replacePattern(replacer: string) {
Expand All @@ -72,18 +70,26 @@ export function resolve(manifest: ImportsFieldManifest, specifier: string, optio
throw new ERR_PACKAGE_IMPORT_NOT_DEFINED(specifier, manifest.path, manifest.base)
}

function lookupReplacer(map: ImportMap, conditions: Set<string>): string | string[] | undefined {
if (typeof map === 'string' || Array.isArray(map)) return map
function resolvePackagePattern(map: ImportMap, conditions: Set<string>): string | undefined {
if (typeof map === 'string') return map

if (Array.isArray(map)) {
for (const item of map) {
const result = resolvePackagePattern(item, conditions)
if (result) return result
}
return undefined
}

for (const key of Object.keys(map)) {
if (conditions.has(key)) return lookupReplacer(map[key], conditions)
if (conditions.has(key)) return resolvePackagePattern(map[key], conditions)
}
return map.default as string | undefined
}

function noRecursive(value: string | string[] | undefined): string | string[] | undefined {
if (Array.isArray(value)) return value.map(noRecursive) as string[]
return value?.startsWith('#') ? undefined : value
function noRecursive(value: string | undefined): string | undefined {
assert(!value?.startsWith('#'), 'recursive imports are not allowed')
return value
}

function getExpensionKeys(keys: string[]) {
Expand Down
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -44,18 +44,14 @@ describe('subpath imports', () => {
expect(r).toBe('./browser.js')
})

it('returns the value if it is string[]', () => {
// the actual resolution (check which file exists and thus accept),
// is up to the engine, we just return the array.
const r = resolve(
manifest({
imports: {
'#ansi-styles': ['./a.js', './b.js']
}
}),
'#ansi-styles'
)
expect(r).toEqual(['./a.js', './b.js'])
it('supports conditional imports in array form', () => {
const m = manifest({
imports: {
'#ansi-styles': [{ "node": './a.js' }, './b.js']
}
})
expect(resolve(m, '#ansi-styles')).toEqual('./b.js')
expect(resolve(m, '#ansi-styles', { conditions: ['blah', 'node'] })).toEqual('./a.js')
})

it('returns first matched condition', () => {
Expand Down Expand Up @@ -160,7 +156,7 @@ describe(`subpath patterns`, () => {
expect(r).toBe('./src/internal/foo.js')
})

it('maps to string[] pattern', () => {
it('gets the first value from array pattern', () => {
const r = resolve(
manifest({
imports: {
Expand All @@ -169,7 +165,17 @@ describe(`subpath patterns`, () => {
}),
'#internal/foo.js'
)
expect(r).toEqual(['./src/internal/foo.js', './src/internal2/foo.js'])
expect(r).toEqual('./src/internal/foo.js')
})

it('supports conditional imports in array form', () => {
const m = manifest({
imports: {
'#a/*': [{ "node": './a/*.js' }, './b/*.js']
}
})
expect(resolve(m, '#a/foo')).toEqual('./b/foo.js')
expect(resolve(m, '#a/foo', { conditions: ['blah', 'node'] })).toEqual('./a/foo.js')
})

it('maps based on condition', () => {
Expand Down Expand Up @@ -288,6 +294,6 @@ it('does not support recursive references', () => {
'#b': './b.js',
}
})
expect(resolve(pkg, '#a')).toBeUndefined()
expect(resolve(pkg, '#internal/foo.js')).toBeUndefined()
expect(() => resolve(pkg, '#a')).toThrow(`recursive imports are not allowed`)
expect(() => resolve(pkg, '#internal/foo.js')).toThrow(`recursive imports are not allowed`)
})

0 comments on commit dc108a5

Please sign in to comment.