11import path from 'node:path'
2- import fs from 'node:fs'
2+ import fs from 'node:fs/promises '
33import { builtinModules } from 'node:module'
44import type { Plugin } from 'rollup'
55
@@ -63,14 +63,14 @@ export interface ExternalsOptions {
6363 /**
6464 * Force include these deps in the list of externals, regardless of other settings.
6565 *
66- * Defaults to `[]`
66+ * Defaults to `[]` (force include nothing)
6767 */
6868 include ?: MaybeArray < MaybeFalsy < string | RegExp > >
6969
7070 /**
7171 * Force exclude these deps from the list of externals, regardless of other settings.
7272 *
73- * Defaults to `[]`
73+ * Defaults to `[]` (force exclude nothing)
7474 */
7575 exclude ?: MaybeArray < MaybeFalsy < string | RegExp > >
7676}
@@ -105,9 +105,7 @@ const workspaceRootFiles = new Set([
105105] )
106106
107107// Our defaults
108- type Config = Required < ExternalsOptions > & {
109- invalid ?: boolean
110- }
108+ type Config = Required < ExternalsOptions >
111109
112110const defaults : Config = {
113111 builtins : true ,
@@ -130,116 +128,117 @@ const isString = (str: unknown): str is string =>
130128 */
131129function externals ( options : ExternalsOptions = { } ) : Plugin {
132130
133- // This will store all eventual errors/warnings until we can display them.
134- const messages : string [ ] = [ ]
135-
136131 // Consolidate options
137132 const config : Config = Object . assign ( Object . create ( null ) , defaults , options )
138133
139134 // Map the include and exclude options to arrays of regexes.
135+ const warnings : string [ ] = [ ]
140136 const [ include , exclude ] = ( [ 'include' , 'exclude' ] as const ) . map ( option =>
141137 ( [ ] as ( string | RegExp | null | undefined | false ) [ ] )
142138 . concat ( config [ option ] )
143139 . reduce ( ( result , entry , index ) => {
144- if ( entry instanceof RegExp )
145- result . push ( entry )
146- else if ( typeof entry === 'string' )
147- result . push ( new RegExp ( '^' + entry . replace ( / [ . * + ? ^ $ { } ( ) | [ \] \\ ] / g, '\\$&' ) + '$' ) )
148- else if ( entry ) {
149- messages . push ( `Ignoring wrong entry type #${ index } in '${ option } ' option: ${ JSON . stringify ( entry ) } ` )
140+ if ( entry ) {
141+ if ( entry instanceof RegExp )
142+ result . push ( entry )
143+ else if ( typeof entry === 'string' )
144+ result . push ( new RegExp ( '^' + entry . replace ( / [ . * + ? ^ $ { } ( ) | [ \] \\ ] / g, '\\$&' ) + '$' ) )
145+ else {
146+ warnings . push ( `Ignoring wrong entry type #${ index } in '${ option } ' option: ${ JSON . stringify ( entry ) } ` )
147+ }
150148 }
151149 return result
152150 } , [ ] as RegExp [ ] )
153151 )
154152
155- // Prepare npm dependencies list.
156- if ( config . deps || config . devDeps || config . peerDeps || config . optDeps ) {
157-
158- const packagePaths : string [ ] = Array . isArray ( config . packagePath )
159- ? config . packagePath . filter ( isString )
160- : isString ( config . packagePath )
161- ? [ config . packagePath ]
162- : [ ]
163-
164- // Populate packagePaths if not given by getting all package.json files
165- // from cwd up to the root of the monorepo, the root of the git repo,
166- // or the root of the volume, whichever comes first.
167- if ( packagePaths . length === 0 ) {
168- for (
169- let current = process . cwd ( ) , previous : string | null = null ;
170- previous !== current ;
171- previous = current , current = path . dirname ( current )
172- ) {
173- const entries = fs . readdirSync ( current , { withFileTypes : true } )
174-
175- if ( entries . some ( entry => entry . name === 'package.json' && entry . isFile ( ) ) )
176- packagePaths . push ( path . join ( current , 'package.json' ) )
177-
178- // Break early if there is a pnpm/lerna workspace config file.
179- if ( entries . some ( entry =>
180- ( workspaceRootFiles . has ( entry . name ) && entry . isFile ( ) ) ||
181- ( entry . name === '.git' && entry . isDirectory ( ) )
182- ) ) {
183- break
184- }
185- }
186- }
187-
188- // Gather dependencies names.
189- const dependencies : Record < string , string > = { }
190- for ( const packagePath of packagePaths ) {
191- let pkg : PackageJson | null = null
192- try {
193- pkg = JSON . parse ( fs . readFileSync ( packagePath ) . toString ( ) ) as PackageJson
194- Object . assign ( dependencies ,
195- config . deps && pkg . dependencies ,
196- config . devDeps && pkg . devDependencies ,
197- config . peerDeps && pkg . peerDependencies ,
198- config . optDeps && pkg . optionalDependencies
199- )
200-
201- // Break early if this is a npm/yarn workspace root.
202- if ( 'workspaces' in pkg || 'packages' in pkg )
203- break
204- }
205- catch {
206- messages . push ( pkg
207- ? `File ${ JSON . stringify ( packagePath ) } does not look like a valid package.json file.`
208- : `Cannot read file ${ JSON . stringify ( packagePath ) } `
209- )
210-
211- config . invalid = true
212- break
213- }
214- }
215-
216- const names = Object . keys ( dependencies )
217- if ( names . length > 0 )
218- include . push ( new RegExp ( '^(?:' + names . join ( '|' ) + ')(?:/.+)?$' ) )
219- }
220-
221153 const isIncluded = ( id : string ) => include . some ( rx => rx . test ( id ) )
222154 const isExcluded = ( id : string ) => exclude . some ( rx => rx . test ( id ) )
223155
224156 return {
225157 name : 'node-externals' ,
226158
227159 async buildStart ( ) {
228- // Bail out if there was an error.
229- if ( config . invalid )
230- this . error ( messages [ 0 ] )
231-
232- // Otherwise issue any warnings we may have collected earlier.
233- while ( messages . length > 0 ) {
234- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
235- this . warn ( messages . shift ( ) ! )
160+ // Issue any warnings we may have collected earlier.
161+ while ( warnings . length > 0 ) {
162+ this . warn ( warnings . shift ( ) ! ) // eslint-disable-line @typescript-eslint/no-non-null-assertion
163+ }
164+
165+ // Prepare npm dependencies list.
166+ const packageFiles : string [ ] = Array . isArray ( config . packagePath )
167+ ? config . packagePath . filter ( isString )
168+ : isString ( config . packagePath )
169+ ? [ config . packagePath ]
170+ : [ ]
171+
172+ // Populate packagePaths if not given by getting all package.json files
173+ // from cwd up to the root of the git repo, the root of the monorepo,
174+ // or the root of the volume, whichever comes first.
175+ if ( packageFiles . length === 0 ) {
176+ for (
177+ let current = process . cwd ( ) , previous : string | undefined ;
178+ previous !== current ;
179+ previous = current , current = path . dirname ( current )
180+ ) {
181+ const entries = await fs . readdir ( current , { withFileTypes : true } )
182+
183+ if ( entries . some ( entry => entry . name === 'package.json' && entry . isFile ( ) ) )
184+ packageFiles . push ( path . join ( current , 'package.json' ) )
185+
186+ // Break early if this is a git repo root or a pnpm/lerna workspace root.
187+ if ( entries . some ( entry =>
188+ ( entry . name === '.git' && entry . isDirectory ( ) ) ||
189+ ( workspaceRootFiles . has ( entry . name ) && entry . isFile ( ) )
190+ ) ) {
191+ break
192+ }
193+ }
236194 }
195+
196+ // Gather dependencies names.
197+ const dependencies : Record < string , string > = { }
198+ for ( const packageFile of packageFiles ) {
199+ try {
200+ const json = ( await fs . readFile ( packageFile ) ) . toString ( )
201+ try {
202+ const pkg = JSON . parse ( json ) as PackageJson
203+ Object . assign ( dependencies ,
204+ config . deps ? pkg . dependencies : undefined ,
205+ config . devDeps ? pkg . devDependencies : undefined ,
206+ config . peerDeps ? pkg . peerDependencies : undefined ,
207+ config . optDeps ? pkg . optionalDependencies : undefined
208+ )
209+
210+ // Watch the file.
211+ if ( this . meta . watchMode )
212+ this . addWatchFile ( packageFile )
213+
214+ // Break early if this is a npm/yarn workspace root.
215+ if ( 'workspaces' in pkg || 'packages' in pkg )
216+ break
217+ }
218+ catch {
219+ this . error ( {
220+ message : `File ${ JSON . stringify ( packageFile ) } does not look like a valid package.json file.` ,
221+ stack : undefined
222+ } )
223+ }
224+ }
225+ catch {
226+ this . error ( {
227+ message : `Cannot read file ${ JSON . stringify ( packageFile ) } ` ,
228+ stack : undefined
229+ } )
230+ }
231+ }
232+
233+ const names = Object . keys ( dependencies )
234+ if ( names . length > 0 )
235+ include . push ( new RegExp ( '^(?:' + names . join ( '|' ) + ')(?:/.+)?$' ) )
237236 } ,
238237
239238 async resolveId ( id ) {
240239
241240 // Let Rollup handle already resolved ids, relative imports and virtual modules.
242- if ( path . isAbsolute ( id ) || / ^ (?: \0 | \. { 1 , 2 } [ \\ / ] ) / . test ( id ) )
241+ if ( / ^ (?: \0 | \. { 1 , 2 } [ \\ / ] ) / . test ( id ) || path . isAbsolute ( id ) )
243242 return null
244243
245244 // Handle node builtins.
0 commit comments