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 <noreply@anthropic.com>
pull/5341/head
Divyansh Singh 1 week ago
parent e8899f3273
commit 3fc09f67b7

@ -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<string> {
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))})`
}

Loading…
Cancel
Save