Skip to content

Commit a1969f7

Browse files
committed
fix(watch): re-glob entry files on create/delete events
When entry uses glob patterns like `src/**/*.ts`, new files added during watch mode now trigger a restart by re-resolving the entry globs on file create/delete events. Closes #709
1 parent 6d24405 commit a1969f7

File tree

5 files changed

+31
-2
lines changed

5 files changed

+31
-2
lines changed

dts.snapshot.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,7 @@
136136
"PublintOptions": "interface PublintOptions extends Omit<Options, 'pack' | 'pkgDir'> {}",
137137
"ReportOptions": "interface ReportOptions {\n gzip?: boolean\n brotli?: boolean\n maxCompressSize?: number\n}",
138138
"ReportPlugin": "declare function ReportPlugin(_: ResolvedConfig, _: boolean, _: boolean): Plugin",
139-
"ResolvedConfig": "type ResolvedConfig = Overwrite<MarkPartial<Omit<UserConfig, 'workspace' | 'fromVite' | 'publicDir' | 'bundle' | 'removeNodeProtocol' | 'external' | 'noExternal' | 'inlineOnly' | 'skipNodeModulesBundle' | 'logLevel' | 'failOnWarn' | 'customLogger' | 'envFile' | 'envPrefix'>, 'globalName' | 'inputOptions' | 'outputOptions' | 'minify' | 'define' | 'alias' | 'onSuccess' | 'outExtensions' | 'hooks' | 'copy' | 'loader' | 'name' | 'banner' | 'footer' | 'checks'>, { entry: Record<string, string>; nameLabel: string | undefined; format: NormalizedFormat; target?: string[]; clean: string[]; pkg?: PackageJsonWithPath; nodeProtocol: 'strip' | boolean; logger: Logger; ignoreWatch: Array<string | RegExp>; deps: ResolvedDepsConfig; css: Required<CssOptions>; dts: false | DtsOptions; report: false | ReportOptions; tsconfig: false | string; exports: false | ExportsOptions; devtools: false | DevtoolsOptions; publint: false | PublintOptions; attw: false | AttwOptions; unused: false | UnusedOptions; exe: false | ExeOptions }>",
139+
"ResolvedConfig": "type ResolvedConfig = Overwrite<MarkPartial<Omit<UserConfig, 'workspace' | 'fromVite' | 'publicDir' | 'bundle' | 'removeNodeProtocol' | 'external' | 'noExternal' | 'inlineOnly' | 'skipNodeModulesBundle' | 'logLevel' | 'failOnWarn' | 'customLogger' | 'envFile' | 'envPrefix'>, 'globalName' | 'inputOptions' | 'outputOptions' | 'minify' | 'define' | 'alias' | 'onSuccess' | 'outExtensions' | 'hooks' | 'copy' | 'loader' | 'name' | 'banner' | 'footer' | 'checks'>, { entry: Record<string, string>; rawEntry?: TsdownInputOption; nameLabel: string | undefined; format: NormalizedFormat; target?: string[]; clean: string[]; pkg?: PackageJsonWithPath; nodeProtocol: 'strip' | boolean; logger: Logger; ignoreWatch: Array<string | RegExp>; deps: ResolvedDepsConfig; css: Required<CssOptions>; dts: false | DtsOptions; report: false | ReportOptions; tsconfig: false | string; exports: false | ExportsOptions; devtools: false | DevtoolsOptions; publint: false | PublintOptions; attw: false | AttwOptions; unused: false | UnusedOptions; exe: false | ExeOptions }>",
140140
"ResolvedDepsConfig": "interface ResolvedDepsConfig {\n neverBundle?: ExternalOption\n alwaysBundle?: NoExternalFn\n onlyAllowBundle?: Array<string | RegExp> | false\n skipNodeModulesBundle: boolean\n}",
141141
"RolldownChunk": "type RolldownChunk = (OutputChunk | OutputAsset) & { outDir: string }",
142142
"RolldownContext": "interface RolldownContext {\n buildOptions: BuildOptions\n}",

src/build.ts

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import { warnLegacyCJS } from './features/cjs.ts'
1515
import { cleanChunks, cleanOutDir } from './features/clean.ts'
1616
import { copy } from './features/copy.ts'
1717
import { startDevtoolsUI } from './features/devtools.ts'
18+
import { isGlobEntry, toObjectEntry } from './features/entry.ts'
1819
import { buildExe } from './features/exe.ts'
1920
import { createHooks, executeOnSuccess } from './features/hooks.ts'
2021
import { bundleDone, initBundleByPkg } from './features/pkg/index.ts'
@@ -187,7 +188,7 @@ async function buildSingle(
187188
const changedFile: string[] = []
188189
let hasError = false
189190

190-
watcher.on('change', (id, event) => {
191+
watcher.on('change', async (id, event) => {
191192
if (event.event === 'update') {
192193
changedFile.push(id)
193194
// Cancel pending postBuild immediately on file change,
@@ -201,6 +202,19 @@ async function buildSingle(
201202
globalLogger.info(`Reload config: ${id}, restarting...`)
202203
restart()
203204
}
205+
if (
206+
(event.event === 'create' || event.event === 'delete') &&
207+
config.rawEntry &&
208+
isGlobEntry(config.rawEntry)
209+
) {
210+
const newEntry = await toObjectEntry(config.rawEntry, config.cwd)
211+
const currentKeys = Object.keys(config.entry).toSorted().join('\0')
212+
const newKeys = Object.keys(newEntry).toSorted().join('\0')
213+
if (currentKeys !== newKeys) {
214+
globalLogger.info('Entry files changed, restarting...')
215+
restart()
216+
}
217+
}
204218
})
205219

206220
watcher.on('event', async (event) => {

src/config/options.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,7 @@ export async function resolveUserConfig(
134134
outDir = path.resolve(cwd, outDir)
135135
clean = resolveClean(clean, outDir, cwd)
136136

137+
const rawEntry = entry
137138
const resolvedEntry = await resolveEntry(logger, entry, cwd, color, nameLabel)
138139

139140
target = resolveTarget(logger, target, color, pkg, nameLabel)
@@ -279,6 +280,7 @@ export async function resolveUserConfig(
279280
platform,
280281
plugins,
281282
publint,
283+
rawEntry,
282284
report,
283285
shims,
284286
sourcemap,

src/config/types.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -653,6 +653,8 @@ export type ResolvedConfig = Overwrite<
653653
{
654654
/** Resolved entry map (after glob expansion) */
655655
entry: Record<string, string>
656+
/** Original entry config before glob resolution (for watch mode re-globbing) */
657+
rawEntry?: TsdownInputOption
656658
nameLabel: string | undefined
657659
format: NormalizedFormat
658660
target?: string[]

src/features/entry.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,17 @@ export function toObjectEntry(
5353
return resolveArrayEntry(entry, cwd)
5454
}
5555

56+
export function isGlobEntry(entry: TsdownInputOption | undefined): boolean {
57+
if (!entry) return false
58+
if (typeof entry === 'string') return isDynamicPattern(entry)
59+
if (Array.isArray(entry)) {
60+
return entry.some((e) =>
61+
typeof e === 'string' ? isDynamicPattern(e) : isGlobEntry(e),
62+
)
63+
}
64+
return Object.keys(entry).some((key) => key.includes('*'))
65+
}
66+
5667
async function resolveObjectEntry(
5768
entries: Record<string, string | string[]>,
5869
cwd: string,

0 commit comments

Comments
 (0)