Skip to content

Commit 219f93b

Browse files
committed
Improve watch mode support
1 parent 6aeb1a0 commit 219f93b

File tree

1 file changed

+89
-90
lines changed

1 file changed

+89
-90
lines changed

src/index.ts

Lines changed: 89 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import path from 'node:path'
2-
import fs from 'node:fs'
2+
import fs from 'node:fs/promises'
33
import { builtinModules } from 'node:module'
44
import 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

112110
const defaults: Config = {
113111
builtins: true,
@@ -130,116 +128,117 @@ const isString = (str: unknown): str is string =>
130128
*/
131129
function 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

Comments
 (0)