import {CachedResult, SassPluginOptions} from './index' import {OnLoadArgs, OnLoadResult} from 'esbuild' import {promises as fsp, Stats} from 'fs' type OnLoadCallback = (args: OnLoadArgs) => (OnLoadResult | Promise) type PluginLoadCallback = (path: string) => (OnLoadResult | Promise) function collectStats(watchFiles: string[], fsStatCache: Map): Promise { return Promise.all(watchFiles.map(async filename => { if (!fsStatCache.has(filename)) { const stats = await fsp.stat(filename) fsStatCache.set(filename, stats) } return fsStatCache.get(filename) as Stats })) } function maxMtimeMs(stats: Stats[]) { return stats.reduce((max, {mtimeMs}) => Math.max(max, mtimeMs), 0) } function getCache(options: SassPluginOptions): Map | undefined { if (options.cache ?? true) { if (typeof options.cache === 'object') { return options.cache } else { return new Map() } } } export function useCache(options: SassPluginOptions = {}, fsStatCache: Map, loadCallback: PluginLoadCallback): OnLoadCallback { const cache = getCache(options) if (cache) { return async ({path}: OnLoadArgs) => { try { let cached = cache.get(path) if (cached) { let watchFiles = cached.result.watchFiles! let stats = await collectStats(watchFiles, fsStatCache) for (const {mtimeMs} of stats) { if (mtimeMs > cached.mtimeMs) { cached.result = await loadCallback(watchFiles[0]) cached.mtimeMs = maxMtimeMs(stats) break } } } else { let result = await loadCallback(path) cached = { mtimeMs: maxMtimeMs(await collectStats(result.watchFiles!, fsStatCache)), result } cache.set(path, cached) } if (cached.result.errors) { cache.delete(path) } return cached.result } catch (error) { cache.delete(path) throw error } } } else { return ({path}) => loadCallback(path) } }