Compare commits

...

3 commits

Author SHA1 Message Date
Nate Moore
ca5517ba91 wip: add incremental plugin, collect timings from Rollup 2023-09-27 10:51:28 -05:00
Nate Moore
119473921b hack: filter content collection glob 2023-09-27 10:51:28 -05:00
Nate Moore
c7e3c8d0e2 wip: incremental 2023-09-27 10:51:28 -05:00
5 changed files with 178 additions and 9 deletions

View file

@ -37,6 +37,15 @@ export function astroContentVirtualModPlugin({
const contentEntryExts = [...contentEntryConfigByExt.keys()]; const contentEntryExts = [...contentEntryConfigByExt.keys()];
const dataEntryExts = getDataEntryExts(settings); const dataEntryExts = getDataEntryExts(settings);
let contentEntryGlobPath = globWithUnderscoresIgnored(relContentDir, contentEntryExts);
// HACK(nate): filter contentEntryGlobPath to simulate incremental build
// contentEntryGlobPath = [contentEntryGlobPath[0].replace('**/*', '**/a-e*')]
const dataEntryGlobPath = globWithUnderscoresIgnored(relContentDir, dataEntryExts);
/** Note: data collections excluded */
let renderEntryGlobPath = globWithUnderscoresIgnored(relContentDir, contentEntryExts);
// renderEntryGlobPath = [renderEntryGlobPath[0].replace('**/*', '**/a-e*')]
const virtualModContents = fsMod const virtualModContents = fsMod
.readFileSync(contentPaths.virtualModTemplate, 'utf-8') .readFileSync(contentPaths.virtualModTemplate, 'utf-8')
.replace( .replace(
@ -46,20 +55,15 @@ export function astroContentVirtualModPlugin({
.replace('@@CONTENT_DIR@@', relContentDir) .replace('@@CONTENT_DIR@@', relContentDir)
.replace( .replace(
"'@@CONTENT_ENTRY_GLOB_PATH@@'", "'@@CONTENT_ENTRY_GLOB_PATH@@'",
JSON.stringify(globWithUnderscoresIgnored(relContentDir, contentEntryExts)) JSON.stringify(contentEntryGlobPath)
) )
.replace( .replace(
"'@@DATA_ENTRY_GLOB_PATH@@'", "'@@DATA_ENTRY_GLOB_PATH@@'",
JSON.stringify(globWithUnderscoresIgnored(relContentDir, dataEntryExts)) JSON.stringify(dataEntryGlobPath)
) )
.replace( .replace(
"'@@RENDER_ENTRY_GLOB_PATH@@'", "'@@RENDER_ENTRY_GLOB_PATH@@'",
JSON.stringify( JSON.stringify(renderEntryGlobPath)
globWithUnderscoresIgnored(
relContentDir,
/** Note: data collections excluded */ contentEntryExts
)
)
); );
const astroContentVirtualModuleId = '\0' + VIRTUAL_MODULE_ID; const astroContentVirtualModuleId = '\0' + VIRTUAL_MODULE_ID;

View file

@ -12,6 +12,8 @@ import { ASTRO_PAGE_EXTENSION_POST_PATTERN } from './plugins/util.js';
import type { PageBuildData, StylesheetAsset, ViteID } from './types.js'; import type { PageBuildData, StylesheetAsset, ViteID } from './types.js';
export interface BuildInternals { export interface BuildInternals {
cache: any;
/** /**
* Each CSS module is named with a chunk id derived from the Astro pages they * Each CSS module is named with a chunk id derived from the Astro pages they
* are used in by default. It's easy to crawl this relation in the SSR build as * are used in by default. It's easy to crawl this relation in the SSR build as
@ -106,6 +108,8 @@ export function createBuildInternals(): BuildInternals {
const hoistedScriptIdToPagesMap = new Map<string, Set<string>>(); const hoistedScriptIdToPagesMap = new Map<string, Set<string>>();
return { return {
cache: [],
cssModuleToChunkIdMap: new Map(), cssModuleToChunkIdMap: new Map(),
hoistedScriptIdToHoistedMap, hoistedScriptIdToHoistedMap,
hoistedScriptIdToPagesMap, hoistedScriptIdToPagesMap,

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;
@ -77,12 +80,54 @@ export async function viteBuild(opts: StaticBuildOptions) {
const container = createPluginContainer(opts, internals); const container = createPluginContainer(opts, internals);
registerAllPlugins(container); registerAllPlugins(container);
// HACK(nate): Begin cache restoration
// const cacheDir = new URL('./node_modules/.cache/astro/', settings.config.root);
// const cacheFile = new URL('./build.json', cacheDir);
// let restore = false;
// if (fs.existsSync(cacheDir)) {
// internals.cache = JSON.parse(fs.readFileSync(cacheFile, { encoding: 'utf8' }));
// 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();
opts.logger.info('build', `Building ${settings.config.output} entrypoints...`); opts.logger.info('build', `Building ${settings.config.output} entrypoints...`);
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())}.`));
if (isWatcher(ssrOutput)) {
await new Promise<void>(resolve => ssrOutput.on("event", (e) => {
if (e.code === "BUNDLE_END") {
const timings = e.result.getTimings?.();
for (const [key, [elapsed]] of Object.entries(timings!)) {
if (elapsed > 50) {
console.log(key, `${(elapsed).toFixed(0)}ms`)
}
}
resolve()
}
}));
return;
}
// HACK(nate): write to cache
// internals.cache = [];
// for (const output of ssrOutput.output) {
// 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');
@ -123,7 +168,7 @@ export async function staticBuild(opts: StaticBuildOptions, internals: BuildInte
case settings.config.output === 'static': { case settings.config.output === 'static': {
settings.timer.start('Static generate'); settings.timer.start('Static generate');
await generatePages(opts, internals); await generatePages(opts, internals);
await cleanServerOutput(opts); // await cleanServerOutput(opts);
settings.timer.end('Static generate'); settings.timer.end('Static generate');
return; return;
} }
@ -166,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;
}