From 8af4a0f5ea733588ce9141d0b41d1c70b323b942 Mon Sep 17 00:00:00 2001 From: Divyansh Singh <40380293+brc-dd@users.noreply.github.com> Date: Thu, 30 Jul 2026 03:39:32 +0530 Subject: [PATCH] refactor(data): memoize loader promise instead of manual deferred Store the promise of the extracted loadData() directly in the per-id map rather than resolving a hand-made pending promise at the end of the load hook. This also propagates loader errors to the other build's pending consumer, which previously awaited forever. Co-Authored-By: Claude Fable 5 --- src/node/plugins/staticDataPlugin.ts | 100 +++++++++++++-------------- 1 file changed, 47 insertions(+), 53 deletions(-) diff --git a/src/node/plugins/staticDataPlugin.ts b/src/node/plugins/staticDataPlugin.ts index e8e48514..5349db0a 100644 --- a/src/node/plugins/staticDataPlugin.ts +++ b/src/node/plugins/staticDataPlugin.ts @@ -58,59 +58,9 @@ export const staticDataPlugin: Plugin = { load: { filter: { id: loaderMatch }, - async handler(id) { - let _resolve: ((res: any) => void) | undefined - if (isBuild) { - if (idToPendingPromiseMap[id]) return idToPendingPromiseMap[id] - idToPendingPromiseMap[id] = new Promise((r) => { - _resolve = r - }) - } - - const base = path.dirname(id) - let watch: LoaderModule['watch'] - let load: LoaderModule['load'] - let options: LoaderModule['options'] - - const existing = idToLoaderModulesMap[id] - if (existing) { - ;({ watch, load, options } = existing) - } else { - // use vite's load config util as a way to load Node.js file with - // TS & native ESM support - const res = await loadConfigFromFile({} as any, id.replace(/\?.*$/, '')) - - // record deps for hmr - if (server && res) { - for (const dep of res.dependencies) { - const depPath = normalizePath(path.resolve(dep)) - if (!depToLoaderModuleIdsMap[depPath]) { - depToLoaderModuleIdsMap[depPath] = new Set() - } - depToLoaderModuleIdsMap[depPath].add(id) - } - } - - const loaderModule = res?.config as LoaderModule - watch = normalizeGlob(loaderModule.watch, base) - load = loaderModule.load - options = loaderModule.options || {} - } - - // load the data - const watchedFiles = await glob(watch, { - absolute: true, - ...options.globOptions - }) - const data = await load(watchedFiles) - - // record loader module for HMR - if (server) idToLoaderModulesMap[id] = { watch, load, options } - - const result = `export const data = JSON.parse(${JSON.stringify(JSON.stringify(data))})` - - if (_resolve) _resolve(result) - return result + handler(id) { + if (isBuild) return (idToPendingPromiseMap[id] ??= loadData(id)) + return loadData(id) } }, @@ -146,3 +96,47 @@ export const staticDataPlugin: Plugin = { return modules.length ? [...existingMods, ...modules] : undefined } } + +async function loadData(id: string): Promise { + const base = path.dirname(id) + let watch: LoaderModule['watch'] + let load: LoaderModule['load'] + let options: LoaderModule['options'] + + const existing = idToLoaderModulesMap[id] + if (existing) { + ;({ watch, load, options } = existing) + } else { + // use vite's load config util as a way to load Node.js file with + // TS & native ESM support + const res = await loadConfigFromFile({} as any, id.replace(/\?.*$/, '')) + + // record deps for hmr + if (server && res) { + for (const dep of res.dependencies) { + const depPath = normalizePath(path.resolve(dep)) + if (!depToLoaderModuleIdsMap[depPath]) { + depToLoaderModuleIdsMap[depPath] = new Set() + } + depToLoaderModuleIdsMap[depPath].add(id) + } + } + + const loaderModule = res?.config as LoaderModule + watch = normalizeGlob(loaderModule.watch, base) + load = loaderModule.load + options = loaderModule.options || {} + } + + // load the data + const watchedFiles = await glob(watch, { + absolute: true, + ...options.globOptions + }) + const data = await load(watchedFiles) + + // record loader module for HMR + if (server) idToLoaderModulesMap[id] = { watch, load, options } + + return `export const data = JSON.parse(${JSON.stringify(JSON.stringify(data))})` +}