From 056de9e0d25e4921fe3bd767419ae095aba27fc1 Mon Sep 17 00:00:00 2001 From: Josh Faigan Date: Fri, 16 Jan 2026 15:43:21 -0500 Subject: [PATCH 1/2] add debounce for theme filewatcher to reduce update events --- .changeset/few-buses-greet.md | 5 ++++ packages/theme/src/cli/utilities/theme-fs.ts | 27 +++++++++++++++++--- 2 files changed, 29 insertions(+), 3 deletions(-) create mode 100644 .changeset/few-buses-greet.md diff --git a/.changeset/few-buses-greet.md b/.changeset/few-buses-greet.md new file mode 100644 index 0000000000..15169fee5c --- /dev/null +++ b/.changeset/few-buses-greet.md @@ -0,0 +1,5 @@ +--- +'@shopify/theme': patch +--- + +Add a 250ms debounce on our filewatcher for themes to stop potential file deletes diff --git a/packages/theme/src/cli/utilities/theme-fs.ts b/packages/theme/src/cli/utilities/theme-fs.ts index 967856e61e..63a4519216 100644 --- a/packages/theme/src/cli/utilities/theme-fs.ts +++ b/packages/theme/src/cli/utilities/theme-fs.ts @@ -23,6 +23,8 @@ import type { ThemeFSEventPayload, } from '@shopify/cli-kit/node/themes/types' +const FILE_EVENT_DEBOUNCE_TIME_IN_MS = 250 + const THEME_DIRECTORY_PATTERNS = [ 'assets/**/*.*', 'config/**/*.json', @@ -319,10 +321,29 @@ export function mountThemeFileSystem(root: string, options?: ThemeFileSystemOpti ignoreInitial: true, }) + // Debounce file events per-file + const pendingEvents = new Map() + + const queueFsEvent = (eventName: 'add' | 'change' | 'unlink', filePath: string) => { + const fileKey = getKey(filePath) + + const pending = pendingEvents.get(fileKey) + if (pending) { + clearTimeout(pending.timeout) + } + + const timeout = setTimeout(() => { + pendingEvents.delete(fileKey) + handleFsEvent(eventName, themeId, adminSession, filePath) + }, FILE_EVENT_DEBOUNCE_TIME_IN_MS) + + pendingEvents.set(fileKey, {eventName, timeout}) + } + watcher - .on('add', handleFsEvent.bind(null, 'add', themeId, adminSession)) - .on('change', handleFsEvent.bind(null, 'change', themeId, adminSession)) - .on('unlink', handleFsEvent.bind(null, 'unlink', themeId, adminSession)) + .on('add', queueFsEvent.bind(null, 'add')) + .on('change', queueFsEvent.bind(null, 'change')) + .on('unlink', queueFsEvent.bind(null, 'unlink')) }, } } From 75d8f3a55ba5b4c4a6d0d6763e9cb5fa51f484a2 Mon Sep 17 00:00:00 2001 From: Josh Faigan Date: Fri, 30 Jan 2026 11:42:38 -0500 Subject: [PATCH 2/2] update debounce to set on file and event type --- packages/theme/src/cli/utilities/theme-fs.ts | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/packages/theme/src/cli/utilities/theme-fs.ts b/packages/theme/src/cli/utilities/theme-fs.ts index 63a4519216..3302ec915b 100644 --- a/packages/theme/src/cli/utilities/theme-fs.ts +++ b/packages/theme/src/cli/utilities/theme-fs.ts @@ -321,23 +321,24 @@ export function mountThemeFileSystem(root: string, options?: ThemeFileSystemOpti ignoreInitial: true, }) - // Debounce file events per-file - const pendingEvents = new Map() + // Debounce file events per-file and per-event-type + const pendingEvents = new Map() const queueFsEvent = (eventName: 'add' | 'change' | 'unlink', filePath: string) => { const fileKey = getKey(filePath) + const eventKey = `${fileKey}:${eventName}` - const pending = pendingEvents.get(fileKey) + const pending = pendingEvents.get(eventKey) if (pending) { - clearTimeout(pending.timeout) + clearTimeout(pending) } const timeout = setTimeout(() => { - pendingEvents.delete(fileKey) + pendingEvents.delete(eventKey) handleFsEvent(eventName, themeId, adminSession, filePath) }, FILE_EVENT_DEBOUNCE_TIME_IN_MS) - pendingEvents.set(fileKey, {eventName, timeout}) + pendingEvents.set(eventKey, timeout) } watcher