11import fs from 'node:fs/promises'
2- import path from 'node:path'
3- import type { Dirent } from 'node:fs'
2+ import { getPackageEntryPoints } from 'pkg-entry-points'
43import { throwIfAborted } from './common.utils.js'
54
6- type PackageJSON = {
7- exports ?: unknown
8- }
9-
10- const ACTIVE_IMPORT_CONDITIONS = new Set ( [
11- 'browser' ,
12- 'default' ,
13- 'import' ,
14- 'module' ,
15- 'production' ,
16- 'svelte' ,
17- 'webpack' ,
18- ] )
19-
20- function isRecord ( value : unknown ) : value is Record < string , unknown > {
21- return typeof value === 'object' && value !== null
22- }
23-
24- function getImportTargets ( value : unknown ) : string [ ] {
25- if ( typeof value === 'string' ) {
26- return [ value ]
27- }
28-
29- if ( Array . isArray ( value ) ) {
30- return value . flatMap ( getImportTargets )
31- }
32-
33- if ( ! isRecord ( value ) ) {
34- return [ ]
35- }
36-
37- for ( const [ condition , target ] of Object . entries ( value ) ) {
38- if ( ! ACTIVE_IMPORT_CONDITIONS . has ( condition ) ) {
39- continue
40- }
41-
42- const targets = getImportTargets ( target )
43- if ( targets . length ) {
44- return targets
45- }
46- }
5+ function withAbortSignal ( signal : AbortSignal ) {
6+ return new Proxy ( fs , {
7+ get ( target , property , receiver ) {
8+ const member = Reflect . get ( target , property , receiver )
9+ if ( typeof member !== 'function' ) {
10+ return member
11+ }
4712
48- return [ ]
13+ return async ( ...args : unknown [ ] ) => {
14+ throwIfAborted ( signal )
15+ const result = await Reflect . apply ( member , target , args )
16+ throwIfAborted ( signal )
17+ return result
18+ }
19+ } ,
20+ } ) as typeof fs
4921}
5022
5123function isBundlableTarget ( target : string ) {
24+ const pathname = target . split ( / [ ? # ] / , 1 ) [ 0 ]
5225 return (
53- target . startsWith ( './' ) &&
54- ! target . endsWith ( '.d.ts' ) &&
55- ! target . endsWith ( '.d.mts' ) &&
56- ! target . endsWith ( '.d.cts' ) &&
57- ! target . endsWith ( '.json' )
26+ ! pathname . endsWith ( '.d.ts' ) &&
27+ ! pathname . endsWith ( '.d.mts' ) &&
28+ ! pathname . endsWith ( '.d.cts' ) &&
29+ ! pathname . endsWith ( '.json' )
5830 )
5931}
6032
@@ -64,252 +36,26 @@ function toImportPoint(packageName: string, subpath: string) {
6436 : `${ packageName } /${ subpath . replace ( / ^ \. \/ / , '' ) } `
6537}
6638
67- function matchesSubpathPattern ( subpath : string , pattern : string ) {
68- const wildcardIndex = pattern . indexOf ( '*' )
69- if ( wildcardIndex === - 1 ) {
70- return subpath === pattern
71- }
72-
73- const prefix = pattern . slice ( 0 , wildcardIndex )
74- const suffix = pattern . slice ( wildcardIndex + 1 )
75- return (
76- subpath . startsWith ( prefix ) &&
77- subpath . endsWith ( suffix ) &&
78- subpath . length >= prefix . length + suffix . length
79- )
80- }
81-
82- function selectedExportKey ( subpath : string , exportKeys : string [ ] ) {
83- if ( exportKeys . includes ( subpath ) ) {
84- return subpath
85- }
86-
87- return exportKeys
88- . filter ( key => key . includes ( '*' ) && matchesSubpathPattern ( subpath , key ) )
89- . sort ( ( left , right ) => {
90- const prefixDifference = right . indexOf ( '*' ) - left . indexOf ( '*' )
91- return prefixDifference || right . length - left . length
92- } ) [ 0 ]
93- }
94-
95- function toSubpath ( packageName : string , importPoint : string ) {
96- return importPoint === packageName
97- ? '.'
98- : `./${ importPoint . slice ( packageName . length + 1 ) } `
99- }
100-
101- function escapeRegex ( value : string ) {
102- return value . replace ( / [ . * + ? ^ $ { } ( ) | [ \] \\ ] / g, '\\$&' )
103- }
104-
105- function makeTargetPatternRegex ( targetPattern : string ) {
106- const parts = targetPattern . split ( '*' )
107- let source = `^${ escapeRegex ( parts [ 0 ] ) } `
108-
109- if ( parts . length > 1 ) {
110- source += `(.+?)${ escapeRegex ( parts [ 1 ] ) } `
111- for ( const part of parts . slice ( 2 ) ) {
112- source += `\\1${ escapeRegex ( part ) } `
113- }
114- }
115-
116- return new RegExp ( `${ source } $` )
117- }
118-
119- async function collectFiles (
120- directory : string ,
121- packagePath : string ,
122- signal ?: AbortSignal ,
123- ) : Promise < string [ ] > {
124- throwIfAborted ( signal )
125-
126- let entries : Dirent [ ]
127- try {
128- entries = await fs . readdir ( directory , { withFileTypes : true } )
129- } catch ( error ) {
130- if ( ( error as NodeJS . ErrnoException ) . code === 'ENOENT' ) {
131- return [ ]
132- }
133- throw error
134- }
135-
136- const files = await Promise . all (
137- entries . map ( async entry => {
138- throwIfAborted ( signal )
139- const entryPath = path . join ( directory , entry . name )
140-
141- if ( entry . isDirectory ( ) ) {
142- return collectFiles ( entryPath , packagePath , signal )
143- }
144-
145- return entry . isFile ( )
146- ? [ path . relative ( packagePath , entryPath ) . split ( path . sep ) . join ( '/' ) ]
147- : [ ]
148- } ) ,
149- )
150-
151- return files . flat ( )
152- }
153-
154- async function targetExists ( packagePath : string , target : string ) {
155- const packageRoot = path . resolve ( packagePath )
156- const targetPath = path . resolve ( packagePath , target )
157- if (
158- targetPath !== packageRoot &&
159- ! targetPath . startsWith ( `${ packageRoot } ${ path . sep } ` )
160- ) {
161- return false
162- }
163-
164- try {
165- await fs . access ( targetPath )
166- return true
167- } catch {
168- return false
169- }
170- }
171-
172- async function expandPatternImportPoints (
173- packageName : string ,
174- packagePath : string ,
175- subpathPattern : string ,
176- targetPattern : string ,
177- signal ?: AbortSignal ,
178- ) {
179- if (
180- ! subpathPattern . includes ( '*' ) ||
181- ! targetPattern . includes ( '*' ) ||
182- ! isBundlableTarget ( targetPattern )
183- ) {
184- return [ ]
185- }
186-
187- const relativeTargetPattern = targetPattern . slice ( 2 )
188- const staticPrefix = relativeTargetPattern . slice (
189- 0 ,
190- relativeTargetPattern . indexOf ( '*' ) ,
191- )
192- const scanDirectory = path . resolve (
193- packagePath ,
194- path . posix . dirname ( `${ staticPrefix } __candidate__` ) ,
195- )
196- const packageRoot = path . resolve ( packagePath )
197-
198- if (
199- scanDirectory !== packageRoot &&
200- ! scanDirectory . startsWith ( `${ packageRoot } ${ path . sep } ` )
201- ) {
202- return [ ]
203- }
204-
205- const matcher = makeTargetPatternRegex ( relativeTargetPattern )
206- const files = await collectFiles ( scanDirectory , packageRoot , signal )
207-
208- return files . flatMap ( file => {
209- if ( ! isBundlableTarget ( `./${ file } ` ) ) {
210- return [ ]
211- }
212-
213- const match = file . match ( matcher )
214- if ( ! match ?. [ 1 ] ) {
215- return [ ]
216- }
217-
218- return [
219- toImportPoint ( packageName , subpathPattern . replaceAll ( '*' , match [ 1 ] ) ) ,
220- ]
221- } )
222- }
223-
224- async function getSubpathImportPoints (
225- packageName : string ,
226- packagePath : string ,
227- subpath : string ,
228- exportValue : unknown ,
229- signal ?: AbortSignal ,
230- ) {
231- const targets = getImportTargets ( exportValue )
232-
233- const resolvedTargets = await Promise . all (
234- targets . map ( async target => {
235- throwIfAborted ( signal )
236-
237- if ( subpath . includes ( '*' ) ) {
238- return expandPatternImportPoints (
239- packageName ,
240- packagePath ,
241- subpath ,
242- target ,
243- signal ,
244- )
245- }
246-
247- return isBundlableTarget ( target ) &&
248- ( await targetExists ( packagePath , target ) )
249- ? [ toImportPoint ( packageName , subpath ) ]
250- : [ ]
251- } ) ,
252- )
253-
254- return resolvedTargets . find ( importPoints => importPoints . length ) ?? [ ]
255- }
256-
25739export async function getImportPointsFromPackage (
25840 packageName : string ,
25941 packagePath : string ,
26042 signal ?: AbortSignal ,
26143) {
26244 throwIfAborted ( signal )
263- const packageJson = JSON . parse (
264- await fs . readFile ( path . join ( packagePath , 'package.json' ) , 'utf8' ) ,
265- ) as PackageJSON
266- throwIfAborted ( signal )
267-
268- if ( packageJson . exports === undefined ) {
269- return [ packageName ]
270- }
271-
272- const exportsField = packageJson . exports
273- const subpathEntries = isRecord ( exportsField )
274- ? Object . entries ( exportsField ) . filter ( ( [ key ] ) => key . startsWith ( '.' ) )
275- : [ ]
276-
277- const entries : Array < [ string , unknown ] > = subpathEntries . length
278- ? subpathEntries
279- : [ [ '.' , exportsField ] ]
280-
281- const exportKeys = entries . map ( ( [ subpath ] ) => subpath )
282- const resolvedEntries = await Promise . all (
283- entries . map ( async ( [ subpath , exportValue ] ) => {
284- if ( subpath !== '.' && ! subpath . startsWith ( './' ) ) {
285- return { importPoints : [ ] , subpath }
286- }
287-
288- return {
289- importPoints : await getSubpathImportPoints (
290- packageName ,
291- packagePath ,
292- subpath ,
293- exportValue ,
294- signal ,
295- ) ,
296- subpath,
297- }
298- } ) ,
299- )
300- const importPoints = new Set (
301- resolvedEntries . flatMap ( ( { importPoints : candidates , subpath } ) =>
302- candidates . filter (
303- importPoint =>
304- selectedExportKey ( toSubpath ( packageName , importPoint ) , exportKeys ) ===
305- subpath ,
306- ) ,
307- ) ,
45+ const entryPoints = await getPackageEntryPoints (
46+ packagePath ,
47+ signal ? withAbortSignal ( signal ) : fs ,
30848 )
49+ throwIfAborted ( signal )
30950
310- return [ ...importPoints ] . sort ( ( left , right ) => {
311- if ( left === packageName ) return - 1
312- if ( right === packageName ) return 1
313- return left . localeCompare ( right )
314- } )
51+ return Object . entries ( entryPoints )
52+ . filter ( ( [ , mappings ] ) =>
53+ mappings . some ( ( [ , target ] ) => isBundlableTarget ( target ) ) ,
54+ )
55+ . map ( ( [ subpath ] ) => toImportPoint ( packageName , subpath ) )
56+ . sort ( ( left , right ) => {
57+ if ( left === packageName ) return - 1
58+ if ( right === packageName ) return 1
59+ return left . localeCompare ( right )
60+ } )
31561}
0 commit comments