From dee945d3151b8af85fbafb643b06f76432d5a4c9 Mon Sep 17 00:00:00 2001 From: Matt Lewis Date: Tue, 5 Sep 2023 13:14:02 +0100 Subject: [PATCH] perf: cache results of fs.stat call for each build to improve incremental performance --- src/cache.ts | 18 ++++++++++++------ src/plugin.ts | 7 +++++-- 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/src/cache.ts b/src/cache.ts index 15023bb..d1d27aa 100644 --- a/src/cache.ts +++ b/src/cache.ts @@ -5,8 +5,14 @@ import {promises as fsp, Stats} from 'fs' type OnLoadCallback = (args: OnLoadArgs) => (OnLoadResult | Promise) type PluginLoadCallback = (path: string) => (OnLoadResult | Promise) -function collectStats(watchFiles): Promise { - return Promise.all(watchFiles.map(filename => fsp.stat(filename))) +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[]) { @@ -23,7 +29,7 @@ function getCache(options: SassPluginOptions): Map | undef } } -export function useCache(options: SassPluginOptions = {}, loadCallback: PluginLoadCallback): OnLoadCallback { +export function useCache(options: SassPluginOptions = {}, fsStatCache: Map, loadCallback: PluginLoadCallback): OnLoadCallback { const cache = getCache(options) if (cache) { return async ({path}: OnLoadArgs) => { @@ -31,7 +37,7 @@ export function useCache(options: SassPluginOptions = {}, loadCallback: PluginLo let cached = cache.get(path) if (cached) { let watchFiles = cached.result.watchFiles! - let stats = await collectStats(watchFiles) + let stats = await collectStats(watchFiles, fsStatCache) for (const {mtimeMs} of stats) { if (mtimeMs > cached.mtimeMs) { cached.result = await loadCallback(watchFiles[0]) @@ -42,7 +48,7 @@ export function useCache(options: SassPluginOptions = {}, loadCallback: PluginLo } else { let result = await loadCallback(path) cached = { - mtimeMs: maxMtimeMs(await collectStats(result.watchFiles)), + mtimeMs: maxMtimeMs(await collectStats(result.watchFiles!, fsStatCache)), result } cache.set(path, cached) @@ -59,4 +65,4 @@ export function useCache(options: SassPluginOptions = {}, loadCallback: PluginLo } else { return ({path}) => loadCallback(path) } -} \ No newline at end of file +} diff --git a/src/plugin.ts b/src/plugin.ts index a91d9b4..e29def4 100755 --- a/src/plugin.ts +++ b/src/plugin.ts @@ -31,7 +31,7 @@ export function sassPlugin(options: SassPluginOptions = {}): Plugin { return { name: 'sass-plugin', - setup({initialOptions, onResolve, onLoad, resolve}) { + setup({initialOptions, onResolve, onLoad, resolve, onStart}) { options.loadPaths = Array.from(new Set([ ...options.loadPaths || modulesPaths(initialOptions.absWorkingDir), @@ -49,6 +49,9 @@ export function sassPlugin(options: SassPluginOptions = {}): Plugin { }) } + const fsStatCache = new Map() + onStart(() => fsStatCache.clear()) + const transform = options.transform ? options.transform.bind(options) : null const cssChunks: Record = {} @@ -71,7 +74,7 @@ export function sassPlugin(options: SassPluginOptions = {}): Plugin { const renderSync = createRenderer(options, options.sourceMap ?? sourcemap) - onLoad({filter: options.filter ?? DEFAULT_FILTER}, useCache(options, async path => { + onLoad({filter: options.filter ?? DEFAULT_FILTER}, useCache(options, fsStatCache, async path => { try { let {cssText, watchFiles, warnings} = renderSync(path) if (!warnings) {