wip: add incremental plugin, collect timings from Rollup

This commit is contained in:
Nate Moore 2023-09-22 18:52:52 -05:00
parent 119473921b
commit ca5517ba91
4 changed files with 158 additions and 25 deletions

View file

@ -39,12 +39,12 @@ export function astroContentVirtualModPlugin({
let contentEntryGlobPath = globWithUnderscoresIgnored(relContentDir, contentEntryExts); let contentEntryGlobPath = globWithUnderscoresIgnored(relContentDir, contentEntryExts);
// HACK(nate): filter contentEntryGlobPath to simulate incremental build // HACK(nate): filter contentEntryGlobPath to simulate incremental build
contentEntryGlobPath = [contentEntryGlobPath[0].replace('**/*', '**/a-e*')] // contentEntryGlobPath = [contentEntryGlobPath[0].replace('**/*', '**/a-e*')]
const dataEntryGlobPath = globWithUnderscoresIgnored(relContentDir, dataEntryExts); const dataEntryGlobPath = globWithUnderscoresIgnored(relContentDir, dataEntryExts);
/** Note: data collections excluded */ /** Note: data collections excluded */
let renderEntryGlobPath = globWithUnderscoresIgnored(relContentDir, contentEntryExts); let renderEntryGlobPath = globWithUnderscoresIgnored(relContentDir, contentEntryExts);
renderEntryGlobPath = [renderEntryGlobPath[0].replace('**/*', '**/a-e*')] // renderEntryGlobPath = [renderEntryGlobPath[0].replace('**/*', '**/a-e*')]
const virtualModContents = fsMod const virtualModContents = fsMod
.readFileSync(contentPaths.virtualModTemplate, 'utf-8') .readFileSync(contentPaths.virtualModTemplate, 'utf-8')

View file

@ -33,6 +33,9 @@ import { ASTRO_PAGE_EXTENSION_POST_PATTERN } from './plugins/util.js';
import type { PageBuildData, StaticBuildOptions } from './types.js'; import type { PageBuildData, StaticBuildOptions } from './types.js';
import { getTimeStat } from './util.js'; import { getTimeStat } from './util.js';
function isWatcher(value: unknown): value is vite.Rollup.RollupWatcher {
return true;
}
export async function viteBuild(opts: StaticBuildOptions) { export async function viteBuild(opts: StaticBuildOptions) {
const { allPages, settings } = opts; const { allPages, settings } = opts;
@ -78,13 +81,13 @@ export async function viteBuild(opts: StaticBuildOptions) {
registerAllPlugins(container); registerAllPlugins(container);
// HACK(nate): Begin cache restoration // HACK(nate): Begin cache restoration
const cacheDir = new URL('./node_modules/.cache/astro/', settings.config.root); // const cacheDir = new URL('./node_modules/.cache/astro/', settings.config.root);
const cacheFile = new URL('./build.json', cacheDir); // const cacheFile = new URL('./build.json', cacheDir);
let restore = false; // let restore = false;
if (fs.existsSync(cacheDir)) { // if (fs.existsSync(cacheDir)) {
internals.cache = JSON.parse(fs.readFileSync(cacheFile, { encoding: 'utf8' })); // internals.cache = JSON.parse(fs.readFileSync(cacheFile, { encoding: 'utf8' }));
restore = true; // restore = true;
} // }
// Build your project (SSR application code, assets, client JS, etc.) // Build your project (SSR application code, assets, client JS, etc.)
const ssrTime = performance.now(); const ssrTime = performance.now();
@ -92,24 +95,38 @@ export async function viteBuild(opts: StaticBuildOptions) {
const ssrOutput = await ssrBuild(opts, internals, pageInput, container); const ssrOutput = await ssrBuild(opts, internals, pageInput, container);
opts.logger.info('build', dim(`Completed in ${getTimeStat(ssrTime, performance.now())}.`)); opts.logger.info('build', dim(`Completed in ${getTimeStat(ssrTime, performance.now())}.`));
// HACK(nate): write to cache if (isWatcher(ssrOutput)) {
internals.cache = []; await new Promise<void>(resolve => ssrOutput.on("event", (e) => {
for (const output of ssrOutput.output) { if (e.code === "BUNDLE_END") {
const md = output.moduleIds.filter(id => id.endsWith('.md')) const timings = e.result.getTimings?.();
if (!!md) { for (const [key, [elapsed]] of Object.entries(timings!)) {
// const { fileName: id, code: content } = output.moduleIds if (elapsed > 50) {
// internals.cache.push({ input, output: { id, content } }) console.log(key, `${(elapsed).toFixed(0)}ms`)
} }
} }
fs.mkdirSync(cacheDir, { recursive: true }); resolve()
fs.writeFileSync(cacheFile, JSON.stringify(internals.cache), { encoding: 'utf8' }); }
}));
return;
}
if (restore) { // HACK(nate): write to cache
// console.log('RESTORING CACHE'); // internals.cache = [];
for (const cached of internals.cache) { // for (const output of ssrOutput.output) {
fs.writeFileSync(new URL(`./${cached.output.id}`, settings.config.outDir), cached.output.content, { encoding: 'utf8' }); // const md = output.moduleIds.filter(id => id.endsWith('.md'))
} // if (!!md) {
} // // const { fileName: id, code: content } = output.moduleIds
// // internals.cache.push({ input, output: { id, content } })
// }
// }
// fs.mkdirSync(cacheDir, { recursive: true });
// fs.writeFileSync(cacheFile, JSON.stringify(internals.cache), { encoding: 'utf8' });
// if (restore) {
// // console.log('RESTORING CACHE');
// for (const cached of internals.cache) {
// fs.writeFileSync(new URL(`./${cached.output.id}`, settings.config.outDir), cached.output.content, { encoding: 'utf8' });
// }
// }
settings.timer.end('SSR build'); settings.timer.end('SSR build');
settings.timer.start('Client build'); settings.timer.start('Client build');
@ -194,8 +211,10 @@ async function ssrBuild(
manifest: false, manifest: false,
outDir: fileURLToPath(out), outDir: fileURLToPath(out),
copyPublicDir: !ssr, copyPublicDir: !ssr,
watch: {},
rollupOptions: { rollupOptions: {
...viteConfig.build?.rollupOptions, ...viteConfig.build?.rollupOptions,
perf: true,
input: [], input: [],
output: { output: {
format: 'esm', format: 'esm',

View file

@ -11,6 +11,7 @@ import {
astroContentImportPlugin, astroContentImportPlugin,
astroContentVirtualModPlugin, astroContentVirtualModPlugin,
} from '../content/index.js'; } from '../content/index.js';
import { incremental } from '../vite-plugin-incremental/index.js';
import astroTransitions from '../transitions/vite-plugin-transitions.js'; import astroTransitions from '../transitions/vite-plugin-transitions.js';
import astroPostprocessVitePlugin from '../vite-plugin-astro-postprocess/index.js'; import astroPostprocessVitePlugin from '../vite-plugin-astro-postprocess/index.js';
import { vitePluginAstroServer } from '../vite-plugin-astro-server/index.js'; import { vitePluginAstroServer } from '../vite-plugin-astro-server/index.js';
@ -111,6 +112,7 @@ export async function createVite(
exclude: ['astro', 'node-fetch'], exclude: ['astro', 'node-fetch'],
}, },
plugins: [ plugins: [
incremental({ settings }),
configAliasVitePlugin({ settings }), configAliasVitePlugin({ settings }),
astroLoadFallbackPlugin({ fs, root: settings.config.root }), astroLoadFallbackPlugin({ fs, root: settings.config.root }),
astroVitePlugin({ settings, logger }), astroVitePlugin({ settings, logger }),

View file

@ -0,0 +1,112 @@
import type * as vite from 'vite';
import type { AstroSettings } from '../@types/astro.js';
import { existsSync, readFileSync, mkdirSync, writeFileSync, } from 'node:fs';
interface IncrementalOptions {
settings: AstroSettings;
}
export interface IncrementalAPI {
cache: Cache;
}
type RollupTransformCache = Map<string, vite.Rollup.SourceDescription>;
export function incremental(opts: IncrementalOptions): vite.Plugin {
const { settings } = opts;
// let target: 'server' | 'client' = 'server';
const diskCache = new DiskCache(settings)
let cache: RollupTransformCache = new Map();
const plugin: vite.Plugin = {
name: 'astro:incremental',
apply: 'build',
config(viteConfig) {
diskCache.setTarget(!!viteConfig?.build?.ssr ? 'server' : 'client');
cache = diskCache.read();
if (cache.size > 0) {
console.log(`Cache restored!`, cache.size);
}
viteConfig.build!.rollupOptions!.perf = true;
},
buildStart(options) {
// Important! Skips any `transform` calls for modules in the cache
options.plugins = options.plugins.map(v => memoizePlugin(v, cache));
},
moduleParsed(info) {
const meta = info.meta ?? {};
meta.cached = true;
cache.set(info.id, {
ast: info.ast!,
code: info.code!,
assertions: info.assertions,
meta,
moduleSideEffects: info.moduleSideEffects,
syntheticNamedExports: info.syntheticNamedExports,
})
},
load(id) {
if (cache.has(id)) {
const cached = cache.get(id)!;
// const { code } = this.load({ id });
// No match, eject from cache and skip
// if (cached.code !== code) {
// cache.delete(id);
// return;
// }
return cached;
}
},
buildEnd() {
diskCache.write(cache);
}
}
return plugin;
}
class DiskCache {
#url: URL;
#raw: RollupTransformCache | undefined;
constructor(settings: AstroSettings) {
this.#url = new URL(`./node_modules/.astro/build.cache.json`, settings.config.root);
}
setTarget(target: 'server' | 'client') {
this.#url = new URL(`./build-${target}.cache.json`, this.#url);
}
read(): RollupTransformCache {
if (this.#raw) return this.#raw;
try {
if (existsSync(this.#url)) {
return new Map(JSON.parse(readFileSync(this.#url, { encoding: 'utf-8' })));
}
} catch {}
return new Map()
}
write(value: RollupTransformCache) {
this.#raw = value;
mkdirSync(new URL('./', this.#url), { recursive: true });
writeFileSync(this.#url, JSON.stringify(Array.from(value.entries())), { encoding: 'utf-8' });
}
}
// function checksum(code: string) {
// return crypto.createHash('md5').update(code, 'utf-8').digest('hex');
// }
function memoizeHook<T extends (...args: any[]) => any>(original: T, override: (params: Parameters<T>, original: () => ReturnType<T>) => ReturnType<T>) {
return new Proxy(original, {
apply(target, thisArg, argArray) {
return override(argArray as Parameters<T>, () => Reflect.apply(target, thisArg, argArray))
}
})
}
function memoizePlugin(plugin: vite.Plugin, cache: RollupTransformCache) {
if (typeof plugin.transform === 'function') {
plugin.transform = memoizeHook(plugin.transform, function load([_code, id], next) {
if (cache.has(id)) return;
return next();
})
}
return plugin;
}