diff options
17 files changed, 259 insertions, 192 deletions
diff --git a/packages/astro/content-module.template.mjs b/packages/astro/content-module.template.mjs index 137e44252..35c6a55c9 100644 --- a/packages/astro/content-module.template.mjs +++ b/packages/astro/content-module.template.mjs @@ -42,7 +42,7 @@ function createGlobLookup(glob) { }; } -const renderEntryGlob = '@@RENDER_ENTRY_GLOB_PATH@@' +const renderEntryGlob = '@@RENDER_ENTRY_GLOB_PATH@@'; const collectionToRenderEntryMap = createCollectionToGlobResultMap({ globResult: renderEntryGlob, contentDir, diff --git a/packages/astro/src/content/vite-plugin-content-assets.ts b/packages/astro/src/content/vite-plugin-content-assets.ts index 8dafc1be8..d1f2ca4ce 100644 --- a/packages/astro/src/content/vite-plugin-content-assets.ts +++ b/packages/astro/src/content/vite-plugin-content-assets.ts @@ -194,10 +194,7 @@ export function astroConfigBuildPlugin( JSON.stringify(Array.from(entryStyles)) ); } else { - newCode = newCode.replace( - JSON.stringify(STYLES_PLACEHOLDER), - "[]" - ); + newCode = newCode.replace(JSON.stringify(STYLES_PLACEHOLDER), '[]'); } if (entryLinks.size) { newCode = newCode.replace( @@ -205,10 +202,7 @@ export function astroConfigBuildPlugin( JSON.stringify(Array.from(entryLinks).map(prependBase)) ); } else { - newCode = newCode.replace( - JSON.stringify(LINKS_PLACEHOLDER), - "[]" - ); + newCode = newCode.replace(JSON.stringify(LINKS_PLACEHOLDER), '[]'); } if (entryScripts.size) { const entryFileNames = new Set<string>(); @@ -235,10 +229,7 @@ export function astroConfigBuildPlugin( ) ); } else { - newCode = newCode.replace( - JSON.stringify(SCRIPTS_PLACEHOLDER), - "[]" - ); + newCode = newCode.replace(JSON.stringify(SCRIPTS_PLACEHOLDER), '[]'); } mutate(chunk, ['server'], newCode); } diff --git a/packages/astro/src/content/vite-plugin-content-virtual-mod.ts b/packages/astro/src/content/vite-plugin-content-virtual-mod.ts index 1f4f7eead..941359c97 100644 --- a/packages/astro/src/content/vite-plugin-content-virtual-mod.ts +++ b/packages/astro/src/content/vite-plugin-content-virtual-mod.ts @@ -5,11 +5,19 @@ import { fileURLToPath, pathToFileURL } from 'node:url'; import pLimit from 'p-limit'; import { type Plugin } from 'vite'; import type { AstroSettings } from '../@types/astro.js'; +import { encodeName } from '../core/build/util.js'; import { AstroError, AstroErrorData } from '../core/errors/index.js'; import { appendForwardSlash, removeFileExtension } from '../core/path.js'; import { rootRelativePath } from '../core/util.js'; -import { encodeName } from '../core/build/util.js'; -import { CONTENT_FLAG, CONTENT_RENDER_FLAG, DATA_FLAG, VIRTUAL_MODULE_ID, RESOLVED_VIRTUAL_MODULE_ID } from './consts.js'; +import { isServerLikeOutput } from '../prerender/utils.js'; +import type { AstroPluginMetadata } from '../vite-plugin-astro/index.js'; +import { + CONTENT_FLAG, + CONTENT_RENDER_FLAG, + DATA_FLAG, + RESOLVED_VIRTUAL_MODULE_ID, + VIRTUAL_MODULE_ID, +} from './consts.js'; import { getContentEntryIdAndSlug, getContentPaths, @@ -22,12 +30,10 @@ import { getExtGlob, type ContentLookupMap, } from './utils.js'; -import type { AstroPluginMetadata } from '../vite-plugin-astro/index.js'; -import { isServerLikeOutput } from '../prerender/utils.js'; interface AstroContentVirtualModPluginParams { settings: AstroSettings; - fs: typeof nodeFs + fs: typeof nodeFs; } export function astroContentVirtualModPlugin({ @@ -40,7 +46,7 @@ export function astroContentVirtualModPlugin({ name: 'astro-content-virtual-mod-plugin', enforce: 'pre', configResolved(config) { - IS_DEV = config.mode === 'development' + IS_DEV = config.mode === 'development'; }, resolveId(id) { if (id === VIRTUAL_MODULE_ID) { @@ -51,7 +57,7 @@ export function astroContentVirtualModPlugin({ return RESOLVED_VIRTUAL_MODULE_ID; } else { // For SSG (production), we will build this file ourselves - return { id: RESOLVED_VIRTUAL_MODULE_ID, external: true } + return { id: RESOLVED_VIRTUAL_MODULE_ID, external: true }; } } }, @@ -72,9 +78,9 @@ export function astroContentVirtualModPlugin({ scripts: [], containsHead: true, propagation: 'in-tree', - pageOptions: {} - } - } satisfies AstroPluginMetadata + pageOptions: {}, + }, + } satisfies AstroPluginMetadata, }; } }, @@ -87,7 +93,7 @@ export function astroContentVirtualModPlugin({ const prefix = depth > 0 ? '../'.repeat(depth) : './'; return code.replaceAll(RESOLVED_VIRTUAL_MODULE_ID, `${prefix}content/entry.mjs`); } - } + }, }; } @@ -95,11 +101,11 @@ export async function generateContentEntryFile({ settings, lookupMap, IS_DEV, - IS_SERVER + IS_SERVER, }: { settings: AstroSettings; fs: typeof nodeFs; - lookupMap: ContentLookupMap + lookupMap: ContentLookupMap; IS_DEV: boolean; IS_SERVER: boolean; }) { @@ -113,12 +119,26 @@ export async function generateContentEntryFile({ const contentEntryConfigByExt = getEntryConfigByExtMap(settings.contentEntryTypes); const contentEntryExts = [...contentEntryConfigByExt.keys()]; const dataEntryExts = getDataEntryExts(settings); - const createGlob = (value: string[], flag: string) => `import.meta.glob(${JSON.stringify(value)}, { query: { ${flag}: true } })` - contentEntryGlobResult = createGlob(globWithUnderscoresIgnored(relContentDir, contentEntryExts), CONTENT_FLAG); - dataEntryGlobResult = createGlob(globWithUnderscoresIgnored(relContentDir, dataEntryExts), DATA_FLAG); - renderEntryGlobResult = createGlob(globWithUnderscoresIgnored(relContentDir, contentEntryExts), CONTENT_RENDER_FLAG); + const createGlob = (value: string[], flag: string) => + `import.meta.glob(${JSON.stringify(value)}, { query: { ${flag}: true } })`; + contentEntryGlobResult = createGlob( + globWithUnderscoresIgnored(relContentDir, contentEntryExts), + CONTENT_FLAG + ); + dataEntryGlobResult = createGlob( + globWithUnderscoresIgnored(relContentDir, dataEntryExts), + DATA_FLAG + ); + renderEntryGlobResult = createGlob( + globWithUnderscoresIgnored(relContentDir, contentEntryExts), + CONTENT_RENDER_FLAG + ); } else { - contentEntryGlobResult = getStringifiedCollectionFromLookup('content', relContentDir, lookupMap); + contentEntryGlobResult = getStringifiedCollectionFromLookup( + 'content', + relContentDir, + lookupMap + ); dataEntryGlobResult = getStringifiedCollectionFromLookup('data', relContentDir, lookupMap); renderEntryGlobResult = getStringifiedCollectionFromLookup('render', relContentDir, lookupMap); } @@ -126,48 +146,42 @@ export async function generateContentEntryFile({ const virtualModContents = nodeFs .readFileSync(contentPaths.virtualModTemplate, 'utf-8') .replace('@@CONTENT_DIR@@', relContentDir) - .replace( - "'@@CONTENT_ENTRY_GLOB_PATH@@'", - contentEntryGlobResult - ) - .replace( - "'@@DATA_ENTRY_GLOB_PATH@@'", - dataEntryGlobResult - ) - .replace( - "'@@RENDER_ENTRY_GLOB_PATH@@'", - renderEntryGlobResult - ).replace( - '/* @@LOOKUP_MAP_ASSIGNMENT@@ */', - `lookupMap = ${JSON.stringify(lookupMap)};` - ); + .replace("'@@CONTENT_ENTRY_GLOB_PATH@@'", contentEntryGlobResult) + .replace("'@@DATA_ENTRY_GLOB_PATH@@'", dataEntryGlobResult) + .replace("'@@RENDER_ENTRY_GLOB_PATH@@'", renderEntryGlobResult) + .replace('/* @@LOOKUP_MAP_ASSIGNMENT@@ */', `lookupMap = ${JSON.stringify(lookupMap)};`); return virtualModContents; } -function getStringifiedCollectionFromLookup(wantedType: 'content' | 'data' | 'render', relContentDir: string, lookupMap: ContentLookupMap) { +function getStringifiedCollectionFromLookup( + wantedType: 'content' | 'data' | 'render', + relContentDir: string, + lookupMap: ContentLookupMap +) { let str = '{'; // In dev, we don't need to normalize the import specifier at all. Vite handles it. let normalize = (slug: string) => slug; // For prod builds, we need to transform from `/src/content/**/*.{md,mdx,json,yaml}` to a relative `./**/*.mjs` import if (process.env.NODE_ENV === 'production') { const suffix = wantedType === 'render' ? '.entry.mjs' : '.mjs'; - normalize = (slug: string) => `${removeFileExtension(encodeName(slug)).replace(relContentDir, './')}${suffix}` + normalize = (slug: string) => + `${removeFileExtension(encodeName(slug)).replace(relContentDir, './')}${suffix}`; } else { let suffix = ''; if (wantedType === 'content') suffix = CONTENT_FLAG; else if (wantedType === 'data') suffix = DATA_FLAG; else if (wantedType === 'render') suffix = CONTENT_RENDER_FLAG; - normalize = (slug: string) => `${slug}?${suffix}` + normalize = (slug: string) => `${slug}?${suffix}`; } for (const { type, entries } of Object.values(lookupMap)) { - if (type === wantedType || wantedType === 'render' && type === 'content') { + if (type === wantedType || (wantedType === 'render' && type === 'content')) { for (const slug of Object.values(entries)) { - str += `\n "${slug}": () => import("${normalize(slug)}"),` + str += `\n "${slug}": () => import("${normalize(slug)}"),`; } } } - str += '\n}' + str += '\n}'; return str; } @@ -202,7 +216,7 @@ export async function generateLookupMap({ cwd: fileURLToPath(root), fs, } - ) + ); // Run 10 at a time to prevent `await getEntrySlug` from accessing the filesystem all at once. // Each await shouldn't take too long for the work to be noticably slow too. diff --git a/packages/astro/src/core/build/index.ts b/packages/astro/src/core/build/index.ts index 0960760a4..f096b8f76 100644 --- a/packages/astro/src/core/build/index.ts +++ b/packages/astro/src/core/build/index.ts @@ -70,7 +70,7 @@ export default async function build( const contentCacheDir = new URL('./content/', astroConfig.cacheDir); if (fs.existsSync(contentCacheDir)) { logger.warn('content', 'clearing cache'); - await fs.promises.rm(contentCacheDir, { force: true, recursive: true }) + await fs.promises.rm(contentCacheDir, { force: true, recursive: true }); } } diff --git a/packages/astro/src/core/build/plugin.ts b/packages/astro/src/core/build/plugin.ts index ef1207e3f..c611c5186 100644 --- a/packages/astro/src/core/build/plugin.ts +++ b/packages/astro/src/core/build/plugin.ts @@ -17,7 +17,10 @@ export interface BuildBeforeHookResult { export type AstroBuildPlugin = { targets: BuildTarget[]; hooks?: { - 'build:before'?: (opts: { target: BuildTarget; input: Set<string> }) => BuildBeforeHookResult | Promise<BuildBeforeHookResult>; + 'build:before'?: (opts: { + target: BuildTarget; + input: Set<string>; + }) => BuildBeforeHookResult | Promise<BuildBeforeHookResult>; 'build:post'?: (opts: { ssrOutputs: RollupOutputArray; clientOutputs: RollupOutputArray; diff --git a/packages/astro/src/core/build/plugins/index.ts b/packages/astro/src/core/build/plugins/index.ts index 3e6a5e6d6..879f72210 100644 --- a/packages/astro/src/core/build/plugins/index.ts +++ b/packages/astro/src/core/build/plugins/index.ts @@ -3,6 +3,7 @@ import { astroHeadBuildPlugin } from '../../../vite-plugin-head/index.js'; import type { AstroBuildPluginContainer } from '../plugin.js'; import { pluginAliasResolve } from './plugin-alias-resolve.js'; import { pluginAnalyzer } from './plugin-analyzer.js'; +import { pluginChunks } from './plugin-chunks.js'; import { pluginComponentEntry } from './plugin-component-entry.js'; import { pluginContent } from './plugin-content.js'; import { pluginCSS } from './plugin-css.js'; @@ -13,7 +14,6 @@ import { pluginMiddleware } from './plugin-middleware.js'; import { pluginPages } from './plugin-pages.js'; import { pluginPrerender } from './plugin-prerender.js'; import { pluginRenderers } from './plugin-renderers.js'; -import { pluginChunks } from './plugin-chunks.js'; import { pluginSSR, pluginSSRSplit } from './plugin-ssr.js'; export function registerAllPlugins({ internals, options, register }: AstroBuildPluginContainer) { diff --git a/packages/astro/src/core/build/plugins/plugin-chunks.ts b/packages/astro/src/core/build/plugins/plugin-chunks.ts index 3a2767ef1..30b3e4938 100644 --- a/packages/astro/src/core/build/plugins/plugin-chunks.ts +++ b/packages/astro/src/core/build/plugins/plugin-chunks.ts @@ -3,19 +3,19 @@ import type { AstroBuildPlugin } from '../plugin.js'; import { extendManualChunks } from './util.js'; export function vitePluginChunks(): VitePlugin { - return { - name: 'astro:chunks', - outputOptions(outputOptions) { - extendManualChunks(outputOptions, { - after(id) { - // Place Astro's server runtime in a single `astro/server.mjs` file - if (id.includes('astro/dist/runtime/server/')) { - return 'astro/server' - } - }, - }); - } - } + return { + name: 'astro:chunks', + outputOptions(outputOptions) { + extendManualChunks(outputOptions, { + after(id) { + // Place Astro's server runtime in a single `astro/server.mjs` file + if (id.includes('astro/dist/runtime/server/')) { + return 'astro/server'; + } + }, + }); + }, + }; } // Build plugin that configures specific chunking behavior diff --git a/packages/astro/src/core/build/plugins/plugin-content.ts b/packages/astro/src/core/build/plugins/plugin-content.ts index 537fbbc12..c28fa6904 100644 --- a/packages/astro/src/core/build/plugins/plugin-content.ts +++ b/packages/astro/src/core/build/plugins/plugin-content.ts @@ -1,20 +1,23 @@ -import { normalizePath, type Plugin as VitePlugin } from 'vite'; -import fsMod from 'node:fs'; import { createHash } from 'node:crypto'; +import fsMod from 'node:fs'; +import { fileURLToPath } from 'node:url'; +import pLimit from 'p-limit'; +import { normalizePath, type Plugin as VitePlugin } from 'vite'; +import { CONTENT_RENDER_FLAG, PROPAGATED_ASSET_FLAG } from '../../../content/consts.js'; +import { hasContentFlag, type ContentLookupMap } from '../../../content/utils.js'; +import { + generateContentEntryFile, + generateLookupMap, +} from '../../../content/vite-plugin-content-virtual-mod.js'; +import { isServerLikeOutput } from '../../../prerender/utils.js'; +import { joinPaths, removeFileExtension, removeLeadingForwardSlash } from '../../path.js'; import { addRollupInput } from '../add-rollup-input.js'; import { type BuildInternals } from '../internal.js'; import type { AstroBuildPlugin } from '../plugin.js'; -import type { StaticBuildOptions } from '../types.js'; -import { generateContentEntryFile, generateLookupMap } from '../../../content/vite-plugin-content-virtual-mod.js'; -import { joinPaths, removeFileExtension, removeLeadingForwardSlash } from '../../path.js'; -import { fileURLToPath } from 'node:url'; -import { type ContentLookupMap, hasContentFlag } from '../../../content/utils.js'; -import { CONTENT_RENDER_FLAG, PROPAGATED_ASSET_FLAG } from '../../../content/consts.js'; import { copyFiles } from '../static-build.js'; -import pLimit from 'p-limit'; -import { extendManualChunks } from './util.js'; -import { isServerLikeOutput } from '../../../prerender/utils.js'; +import type { StaticBuildOptions } from '../types.js'; import { encodeName } from '../util.js'; +import { extendManualChunks } from './util.js'; const CONTENT_CACHE_DIR = './content/'; const CONTENT_MANIFEST_FILE = './manifest.json'; @@ -45,7 +48,11 @@ function createContentManifest(): ContentManifest { return { version: -1, entries: [], serverEntries: [], clientEntries: [] }; } -function vitePluginContent(opts: StaticBuildOptions, lookupMap: ContentLookupMap, internals: BuildInternals): VitePlugin { +function vitePluginContent( + opts: StaticBuildOptions, + lookupMap: ContentLookupMap, + internals: BuildInternals +): VitePlugin { const { config } = opts.settings; const { cacheDir } = config; const distRoot = config.outDir; @@ -66,7 +73,7 @@ function vitePluginContent(opts: StaticBuildOptions, lookupMap: ContentLookupMap const data = fsMod.readFileSync(contentManifestFile, { encoding: 'utf8' }); oldManifest = JSON.parse(data); internals.cachedClientEntries = oldManifest.clientEntries; - } catch { } + } catch {} } return { @@ -85,7 +92,7 @@ function vitePluginContent(opts: StaticBuildOptions, lookupMap: ContentLookupMap const inputs = [`${input}?${collectionTypeToFlag(type)}`]; if (type === 'content') { // Content entries also need to include the version with the RENDER flag - inputs.push(`${input}?${CONTENT_RENDER_FLAG}`) + inputs.push(`${input}?${CONTENT_RENDER_FLAG}`); } newOptions = addRollupInput(newOptions, inputs); } @@ -95,7 +102,7 @@ function vitePluginContent(opts: StaticBuildOptions, lookupMap: ContentLookupMap } // If nothing needs to be rebuilt, we inject a fake entrypoint to appease Rollup if (entries.buildFromSource.length === 0) { - newOptions = addRollupInput(newOptions, [virtualEmptyModuleId]) + newOptions = addRollupInput(newOptions, [virtualEmptyModuleId]); injectedEmptyFile = true; } return newOptions; @@ -108,9 +115,14 @@ function vitePluginContent(opts: StaticBuildOptions, lookupMap: ContentLookupMap before(id, meta) { if (id.startsWith(srcPath) && id.slice(srcPath.length).startsWith('content')) { const info = meta.getModuleInfo(id); - if (info?.dynamicImporters.length === 1 && hasContentFlag(info.dynamicImporters[0], PROPAGATED_ASSET_FLAG)) { + if ( + info?.dynamicImporters.length === 1 && + hasContentFlag(info.dynamicImporters[0], PROPAGATED_ASSET_FLAG) + ) { const [srcRelativePath] = id.replace(rootPath, '/').split('?'); - const resultId = encodeName(`${removeLeadingForwardSlash(removeFileExtension(srcRelativePath))}.render.mjs`); + const resultId = encodeName( + `${removeLeadingForwardSlash(removeFileExtension(srcRelativePath))}.render.mjs` + ); return resultId; } const [srcRelativePath, flag] = id.replace(rootPath, '/').split('?'); @@ -120,11 +132,14 @@ function vitePluginContent(opts: StaticBuildOptions, lookupMap: ContentLookupMap if (flag === PROPAGATED_ASSET_FLAG) { suffix = '.entry.mjs'; } - id = removeLeadingForwardSlash(removeFileExtension(encodeName(id.replace(srcPath, '/')))) + suffix; + id = + removeLeadingForwardSlash( + removeFileExtension(encodeName(id.replace(srcPath, '/'))) + ) + suffix; return id; } } - } + }, }); }, @@ -137,20 +152,26 @@ function vitePluginContent(opts: StaticBuildOptions, lookupMap: ContentLookupMap async load(id) { if (id === resolvedVirtualEmptyModuleId) { return { - code: `// intentionally left empty!\nexport default {}` - } + code: `// intentionally left empty!\nexport default {}`, + }; } }, async generateBundle(_options, bundle) { - const code = await generateContentEntryFile({ settings: opts.settings, fs: fsMod, lookupMap, IS_DEV: false, IS_SERVER: false }); + const code = await generateContentEntryFile({ + settings: opts.settings, + fs: fsMod, + lookupMap, + IS_DEV: false, + IS_SERVER: false, + }); this.emitFile({ type: 'prebuilt-chunk', code, - fileName: 'content/entry.mjs' - }) + fileName: 'content/entry.mjs', + }); if (!injectedEmptyFile) return; - Object.keys(bundle).forEach(key => { + Object.keys(bundle).forEach((key) => { const mod = bundle[key]; if (mod.type === 'asset') return; if (mod.facadeModuleId === resolvedVirtualEmptyModuleId) { @@ -168,7 +189,7 @@ function vitePluginContent(opts: StaticBuildOptions, lookupMap: ContentLookupMap ...internals.discoveredHydratedComponents.keys(), ...internals.discoveredClientOnlyComponents.keys(), ...internals.discoveredScripts, - ]) + ]); // Likewise, these are server modules that might not be referenced // once the cached items are excluded from the build process const serverComponents = new Set([ @@ -178,10 +199,12 @@ function vitePluginContent(opts: StaticBuildOptions, lookupMap: ContentLookupMap newManifest.serverEntries = Array.from(serverComponents); newManifest.clientEntries = Array.from(clientComponents); await fsMod.promises.mkdir(contentCacheDir, { recursive: true }); - await fsMod.promises.writeFile(contentManifestFile, JSON.stringify(newManifest), { encoding: 'utf8' }); + await fsMod.promises.writeFile(contentManifestFile, JSON.stringify(newManifest), { + encoding: 'utf8', + }); const cacheExists = fsMod.existsSync(cache); - fsMod.mkdirSync(cache, { recursive: true }) + fsMod.mkdirSync(cache, { recursive: true }); await fsMod.promises.mkdir(cacheTmp, { recursive: true }); await copyFiles(distContentRoot, cacheTmp, true); if (cacheExists) { @@ -189,7 +212,7 @@ function vitePluginContent(opts: StaticBuildOptions, lookupMap: ContentLookupMap } await copyFiles(cacheTmp, contentCacheDir); await fsMod.promises.rm(cacheTmp, { recursive: true, force: true }); - } + }, }; } @@ -214,7 +237,10 @@ interface ContentEntries { restoreFromCache: ContentManifestKey[]; buildFromSource: ContentManifestKey[]; } -function getEntriesFromManifests(oldManifest: ContentManifest, newManifest: ContentManifest): ContentEntries { +function getEntriesFromManifests( + oldManifest: ContentManifest, + newManifest: ContentManifest +): ContentEntries { const { version: oldVersion, entries: oldEntries } = oldManifest; const { version: newVersion, entries: newEntries } = newManifest; let entries: ContentEntries = { restoreFromCache: [], buildFromSource: [] }; @@ -224,7 +250,9 @@ function getEntriesFromManifests(oldManifest: ContentManifest, newManifest: Cont entries.buildFromSource = Array.from(newEntryMap.keys()); return entries; } - const oldEntryHashMap = new Map<string, ContentManifestKey>(oldEntries.map(([key, hash]) => [hash, key])) + const oldEntryHashMap = new Map<string, ContentManifestKey>( + oldEntries.map(([key, hash]) => [hash, key]) + ); for (const [entry, hash] of newEntryMap) { if (oldEntryHashMap.has(hash)) { @@ -236,8 +264,16 @@ function getEntriesFromManifests(oldManifest: ContentManifest, newManifest: Cont return entries; } -async function generateContentManifest(opts: StaticBuildOptions, lookupMap: ContentLookupMap): Promise<ContentManifest> { - let manifest: ContentManifest = { version: CONTENT_MANIFEST_VERSION, entries: [], serverEntries: [], clientEntries: [] }; +async function generateContentManifest( + opts: StaticBuildOptions, + lookupMap: ContentLookupMap +): Promise<ContentManifest> { + let manifest: ContentManifest = { + version: CONTENT_MANIFEST_VERSION, + entries: [], + serverEntries: [], + clientEntries: [], + }; const limit = pLimit(10); const promises: Promise<void>[] = []; @@ -245,10 +281,12 @@ async function generateContentManifest(opts: StaticBuildOptions, lookupMap: Cont for (const entry of Object.values(entries)) { const key: ContentManifestKey = { collection, type, entry }; const fileURL = new URL(encodeURI(joinPaths(opts.settings.config.root.toString(), entry))); - promises.push(limit(async () => { - const data = await fsMod.promises.readFile(fileURL, { encoding: 'utf8' }); - manifest.entries.push([key, checksum(data)]) - })); + promises.push( + limit(async () => { + const data = await fsMod.promises.readFile(fileURL, { encoding: 'utf8' }); + manifest.entries.push([key, checksum(data)]); + }) + ); } } @@ -262,10 +300,13 @@ function checksum(data: string): string { function collectionTypeToFlag(type: 'content' | 'data') { const name = type[0].toUpperCase() + type.slice(1); - return `astro${name}CollectionEntry` + return `astro${name}CollectionEntry`; } -export function pluginContent(opts: StaticBuildOptions, internals: BuildInternals): AstroBuildPlugin { +export function pluginContent( + opts: StaticBuildOptions, + internals: BuildInternals +): AstroBuildPlugin { const cachedChunks = new URL('./chunks/', opts.settings.config.cacheDir); const distChunks = new URL('./chunks/', opts.settings.config.outDir); @@ -296,7 +337,7 @@ export function pluginContent(opts: StaticBuildOptions, internals: BuildInternal if (fsMod.existsSync(distChunks)) { await copyFiles(distChunks, cachedChunks, true); } - } + }, }, }; } diff --git a/packages/astro/src/core/build/plugins/plugin-css.ts b/packages/astro/src/core/build/plugins/plugin-css.ts index dd5d0af03..1073ed056 100644 --- a/packages/astro/src/core/build/plugins/plugin-css.ts +++ b/packages/astro/src/core/build/plugins/plugin-css.ts @@ -93,9 +93,13 @@ function rollupPluginAstroBuildCSS(options: PluginOptions): VitePlugin[] { // so they can be injected where needed const chunkId = assetName.createNameHash(id, [id]); internals.cssModuleToChunkIdMap.set(id, chunkId); - if (options.buildOptions.settings.config.output === 'static' && options.buildOptions.settings.config.experimental.contentCollectionCache) { + if ( + options.buildOptions.settings.config.output === 'static' && + options.buildOptions.settings.config.experimental.contentCollectionCache + ) { // TODO: Handle inlining? - const propagatedStyles = internals.propagatedStylesMap.get(pageInfo.id) ?? new Set(); + const propagatedStyles = + internals.propagatedStylesMap.get(pageInfo.id) ?? new Set(); propagatedStyles.add({ type: 'external', src: chunkId }); internals.propagatedStylesMap.set(pageInfo.id, propagatedStyles); } diff --git a/packages/astro/src/core/build/static-build.ts b/packages/astro/src/core/build/static-build.ts index e80c288c0..81dcdb4a0 100644 --- a/packages/astro/src/core/build/static-build.ts +++ b/packages/astro/src/core/build/static-build.ts @@ -7,6 +7,8 @@ import path, { extname } from 'node:path'; import { fileURLToPath, pathToFileURL } from 'node:url'; import * as vite from 'vite'; import type { RouteData } from '../../@types/astro.js'; +import { PROPAGATED_ASSET_FLAG } from '../../content/consts.js'; +import { hasAnyContentFlag } from '../../content/utils.js'; import { createBuildInternals, eachPageData, @@ -32,8 +34,6 @@ import { RESOLVED_SPLIT_MODULE_ID, RESOLVED_SSR_VIRTUAL_MODULE_ID } from './plug import { ASTRO_PAGE_EXTENSION_POST_PATTERN } from './plugins/util.js'; import type { StaticBuildOptions } from './types.js'; import { encodeName, getTimeStat } from './util.js'; -import { hasAnyContentFlag } from '../../content/utils.js'; -import { PROPAGATED_ASSET_FLAG } from '../../content/consts.js'; export async function viteBuild(opts: StaticBuildOptions) { const { allPages, settings } = opts; @@ -82,7 +82,7 @@ export async function viteBuild(opts: StaticBuildOptions) { const ssrOutput = await ssrBuild(opts, internals, pageInput, container); opts.logger.info('build', dim(`Completed in ${getTimeStat(ssrTime, performance.now())}.`)); settings.timer.end('SSR build'); - + settings.timer.start('Client build'); const rendererClientEntrypoints = settings.renderers @@ -183,7 +183,7 @@ async function ssrBuild( const { name } = chunkInfo; let prefix = 'chunks/'; let suffix = '_[hash].mjs'; - + if (isContentCache) { prefix += `${buildID}/`; suffix = '.mjs'; @@ -207,7 +207,7 @@ async function ssrBuild( return [prefix, sanitizedName, suffix].join(''); } const encoded = encodeName(name); - return [prefix, encoded, suffix].join('') + return [prefix, encoded, suffix].join(''); }, assetFileNames: `${settings.config.build.assets}/[name].[hash][extname]`, ...viteConfig.build?.rollupOptions?.output, @@ -226,7 +226,11 @@ async function ssrBuild( return 'renderers.mjs'; } else if (chunkInfo.facadeModuleId === RESOLVED_SSR_MANIFEST_VIRTUAL_MODULE_ID) { return 'manifest_[hash].mjs'; - } else if (settings.config.experimental.contentCollectionCache && chunkInfo.facadeModuleId && hasAnyContentFlag(chunkInfo.facadeModuleId)) { + } else if ( + settings.config.experimental.contentCollectionCache && + chunkInfo.facadeModuleId && + hasAnyContentFlag(chunkInfo.facadeModuleId) + ) { const [srcRelative, flag] = chunkInfo.facadeModuleId.split('/src/')[1].split('?'); if (flag === PROPAGATED_ASSET_FLAG) { return encodeName(`${removeFileExtension(srcRelative)}.entry.mjs`); @@ -438,12 +442,10 @@ export async function copyFiles(fromFolder: URL, toFolder: URL, includeDotfiles const from = new URL(filename, fromFolder); const to = new URL(filename, toFolder); const lastFolder = new URL('./', to); - return fs.promises - .mkdir(lastFolder, { recursive: true }) - .then(async function fsCopyFile() { - const p = await fs.promises.copyFile(from, to, fs.constants.COPYFILE_FICLONE); - return p; - }); + return fs.promises.mkdir(lastFolder, { recursive: true }).then(async function fsCopyFile() { + const p = await fs.promises.copyFile(from, to, fs.constants.COPYFILE_FICLONE); + return p; + }); }) ); } diff --git a/packages/astro/src/core/build/util.ts b/packages/astro/src/core/build/util.ts index e3551f73a..e46a0713a 100644 --- a/packages/astro/src/core/build/util.ts +++ b/packages/astro/src/core/build/util.ts @@ -41,8 +41,8 @@ export function encodeName(name: string): string { // Detect if the chunk name has as % sign that is not encoded. // This is borrowed from Node core: https://github.com/nodejs/node/blob/3838b579e44bf0c2db43171c3ce0da51eb6b05d5/lib/internal/url.js#L1382-L1391 // We do this because you cannot import a module with this character in it. - for(let i = 0; i < name.length; i++) { - if(name[i] === '%') { + for (let i = 0; i < name.length; i++) { + if (name[i] === '%') { const third = name.codePointAt(i + 2)! | 0x20; if (name[i + 1] !== '2' || third !== 102) { return `${name.replace(/%/g, '_percent_')}`; diff --git a/packages/astro/src/core/config/schema.ts b/packages/astro/src/core/config/schema.ts index 13b31c7e0..eaa11786a 100644 --- a/packages/astro/src/core/config/schema.ts +++ b/packages/astro/src/core/config/schema.ts @@ -389,7 +389,10 @@ export const AstroConfigSchema = z.object({ } }) ), - contentCollectionCache: z.boolean().optional().default(ASTRO_CONFIG_DEFAULTS.experimental.contentCollectionCache), + contentCollectionCache: z + .boolean() + .optional() + .default(ASTRO_CONFIG_DEFAULTS.experimental.contentCollectionCache), }) .strict( `Invalid or outdated experimental feature.\nCheck for incorrect spelling or outdated Astro version.\nSee https://docs.astro.build/en/reference/configuration-reference/#experimental-flags for a list of all current experiments.` diff --git a/packages/astro/src/runtime/server/render/component.ts b/packages/astro/src/runtime/server/render/component.ts index 68c61bcc6..dfc5d6c5b 100644 --- a/packages/astro/src/runtime/server/render/component.ts +++ b/packages/astro/src/runtime/server/render/component.ts @@ -13,16 +13,9 @@ import { extractDirectives, generateHydrateScript } from '../hydration.js'; import { serializeProps } from '../serialize.js'; import { shorthash } from '../shorthash.js'; import { isPromise } from '../util.js'; -import { - isAstroComponentFactory, - type AstroComponentFactory, -} from './astro/factory.js'; -import { - createAstroComponentInstance -} from './astro/instance.js' -import { - renderTemplate, -} from './astro/index.js'; +import { isAstroComponentFactory, type AstroComponentFactory } from './astro/factory.js'; +import { renderTemplate } from './astro/index.js'; +import { createAstroComponentInstance } from './astro/instance.js'; import { Fragment, diff --git a/packages/astro/test/experimental-content-collection-references.test.js b/packages/astro/test/experimental-content-collection-references.test.js index ae55fc9de..b5de6a61e 100644 --- a/packages/astro/test/experimental-content-collection-references.test.js +++ b/packages/astro/test/experimental-content-collection-references.test.js @@ -6,7 +6,10 @@ describe('Experimental Content Collections cache - references', () => { let fixture; let devServer; before(async () => { - fixture = await loadFixture({ root: './fixtures/content-collection-references/', experimental: { contentCollectionCache: true } }); + fixture = await loadFixture({ + root: './fixtures/content-collection-references/', + experimental: { contentCollectionCache: true }, + }); }); after(() => fixture.clean()); diff --git a/packages/astro/test/experimental-content-collections-css-inline-stylesheets.test.js b/packages/astro/test/experimental-content-collections-css-inline-stylesheets.test.js index 10dee6d9f..6f762a24f 100644 --- a/packages/astro/test/experimental-content-collections-css-inline-stylesheets.test.js +++ b/packages/astro/test/experimental-content-collections-css-inline-stylesheets.test.js @@ -18,8 +18,8 @@ describe('Experimental Content Collections cache inlineStylesheets', () => { inlineStylesheets: 'never', }, experimental: { - contentCollectionCache: true - } + contentCollectionCache: true, + }, }); await fixture.build(); }); @@ -62,8 +62,8 @@ describe('Experimental Content Collections cache - inlineStylesheets to never in inlineStylesheets: 'never', }, experimental: { - contentCollectionCache: true - } + contentCollectionCache: true, + }, }); await fixture.build(); app = await fixture.loadTestAdapterApp(); @@ -111,14 +111,14 @@ describe.skip('Experimental Content Collections cache - inlineStylesheets to aut }, }, experimental: { - contentCollectionCache: true - } + contentCollectionCache: true, + }, }); await fixture.build(); }); after(() => fixture.clean()); - + it.skip('Renders some <style> and some <link> tags', async () => { const html = await fixture.readFile('/index.html'); const $ = cheerio.load(html); @@ -162,8 +162,8 @@ describe('Setting inlineStylesheets to auto in server output', () => { }, }, experimental: { - contentCollectionCache: true - } + contentCollectionCache: true, + }, }); await fixture.build(); app = await fixture.loadTestAdapterApp(); @@ -209,8 +209,8 @@ describe('Setting inlineStylesheets to always in static output', () => { inlineStylesheets: 'always', }, experimental: { - contentCollectionCache: true - } + contentCollectionCache: true, + }, }); await fixture.build(); }); @@ -252,8 +252,8 @@ describe('Setting inlineStylesheets to always in server output', () => { inlineStylesheets: 'always', }, experimental: { - contentCollectionCache: true - } + contentCollectionCache: true, + }, }); await fixture.build(); app = await fixture.loadTestAdapterApp(); diff --git a/packages/astro/test/experimental-content-collections-render.test.js b/packages/astro/test/experimental-content-collections-render.test.js index 2ae03d6c4..d3ec1b1dc 100644 --- a/packages/astro/test/experimental-content-collections-render.test.js +++ b/packages/astro/test/experimental-content-collections-render.test.js @@ -16,8 +16,8 @@ describe('Experimental Content Collections cache - render()', () => { // test suite was authored when inlineStylesheets defaulted to never build: { inlineStylesheets: 'never' }, experimental: { - contentCollectionCache: true - } + contentCollectionCache: true, + }, }); await fixture.build(); }); @@ -116,8 +116,8 @@ describe('Experimental Content Collections cache - render()', () => { // test suite was authored when inlineStylesheets defaulted to never build: { inlineStylesheets: 'never' }, experimental: { - contentCollectionCache: true - } + contentCollectionCache: true, + }, }); await fixture.build(); }); @@ -209,8 +209,8 @@ describe('Experimental Content Collections cache - render()', () => { fixture = await loadFixture({ root: './fixtures/content/', experimental: { - contentCollectionCache: true - } + contentCollectionCache: true, + }, }); devServer = await fixture.startDevServer(); }); diff --git a/packages/astro/test/experimental-content-collections.test.js b/packages/astro/test/experimental-content-collections.test.js index 30ef8b20a..25e19b250 100644 --- a/packages/astro/test/experimental-content-collections.test.js +++ b/packages/astro/test/experimental-content-collections.test.js @@ -9,7 +9,10 @@ describe('Experimental Content Collections cache', () => { describe('Query', () => { let fixture; before(async () => { - fixture = await loadFixture({ root: './fixtures/content-collections/', experimental: { contentCollectionCache: true } }); + fixture = await loadFixture({ + root: './fixtures/content-collections/', + experimental: { contentCollectionCache: true }, + }); await fixture.build(); }); @@ -27,13 +30,15 @@ describe('Experimental Content Collections cache', () => { expect(Array.isArray(json.withoutConfig)).to.equal(true); const ids = json.withoutConfig.map((item) => item.id).sort(); - expect(ids).to.deep.equal([ - 'columbia.md', - 'endeavour.md', - 'enterprise.md', - // Spaces allowed in IDs - 'promo/launch week.mdx', - ].sort()); + expect(ids).to.deep.equal( + [ + 'columbia.md', + 'endeavour.md', + 'enterprise.md', + // Spaces allowed in IDs + 'promo/launch week.mdx', + ].sort() + ); }); it('Handles spaces in `without config` slugs', async () => { @@ -41,13 +46,15 @@ describe('Experimental Content Collections cache', () => { expect(Array.isArray(json.withoutConfig)).to.equal(true); const slugs = json.withoutConfig.map((item) => item.slug).sort(); - expect(slugs).to.deep.equal([ - 'columbia', - 'endeavour', - 'enterprise', - // "launch week.mdx" is converted to "launch-week.mdx" - 'promo/launch-week', - ].sort()); + expect(slugs).to.deep.equal( + [ + 'columbia', + 'endeavour', + 'enterprise', + // "launch week.mdx" is converted to "launch-week.mdx" + 'promo/launch-week', + ].sort() + ); }); it('Returns `with schema` collection', async () => { @@ -163,9 +170,12 @@ describe('Experimental Content Collections cache', () => { let fixture; before(async () => { - fixture = await loadFixture({ root: './fixtures/content-static-paths-integration/', experimental: { - contentCollectionCache: true - } }); + fixture = await loadFixture({ + root: './fixtures/content-static-paths-integration/', + experimental: { + contentCollectionCache: true, + }, + }); await fixture.build(); }); @@ -198,16 +208,19 @@ describe('Experimental Content Collections cache', () => { describe('With spaces in path', () => { it('Does not throw', async () => { - const fixture = await loadFixture({ root: './fixtures/content with spaces in folder name/', experimental: { - contentCollectionCache: true - } }); + const fixture = await loadFixture({ + root: './fixtures/content with spaces in folder name/', + experimental: { + contentCollectionCache: true, + }, + }); let error = null; try { await fixture.build(); } catch (e) { error = e.message; } finally { - await fixture.clean() + await fixture.clean(); } expect(error).to.be.null; }); @@ -217,8 +230,8 @@ describe('Experimental Content Collections cache', () => { const fixture = await loadFixture({ root: './fixtures/content-collections-with-config-mjs/', experimental: { - contentCollectionCache: true - } + contentCollectionCache: true, + }, }); let error; try { @@ -226,7 +239,7 @@ describe('Experimental Content Collections cache', () => { } catch (e) { error = e.message; } finally { - await fixture.clean() + await fixture.clean(); } expect(error).to.include('**title**: Expected type `"string"`, received "number"'); }); @@ -236,8 +249,8 @@ describe('Experimental Content Collections cache', () => { const fixture = await loadFixture({ root: './fixtures/content-collections-with-config-mts/', experimental: { - contentCollectionCache: true - } + contentCollectionCache: true, + }, }); let error; try { @@ -245,7 +258,7 @@ describe('Experimental Content Collections cache', () => { } catch (e) { error = e.message; } finally { - await fixture.clean() + await fixture.clean(); } expect(error).to.include('**title**: Expected type `"string"`, received "number"'); }); @@ -256,8 +269,8 @@ describe('Experimental Content Collections cache', () => { const fixture = await loadFixture({ root: './fixtures/content-collections-empty-md-file/', experimental: { - contentCollectionCache: true - } + contentCollectionCache: true, + }, }); let error; try { @@ -265,7 +278,7 @@ describe('Experimental Content Collections cache', () => { } catch (e) { error = e.message; } finally { - await fixture.clean() + await fixture.clean(); } expect(error).to.include('**title**: Required'); }); @@ -276,8 +289,8 @@ describe('Experimental Content Collections cache', () => { const fixture = await loadFixture({ root: './fixtures/content-collections-empty-dir/', experimental: { - contentCollectionCache: true - } + contentCollectionCache: true, + }, }); let error; try { @@ -285,7 +298,7 @@ describe('Experimental Content Collections cache', () => { } catch (e) { error = e.message; } finally { - await fixture.clean() + await fixture.clean(); } expect(error).to.be.undefined; // TODO: try to render a page @@ -305,8 +318,8 @@ describe('Experimental Content Collections cache', () => { plugins: [preventNodeBuiltinDependencyPlugin()], }, experimental: { - contentCollectionCache: true - } + contentCollectionCache: true, + }, }); await fixture.build(); app = await fixture.loadTestAdapterApp(); |