wip: content collection refactor
This commit is contained in:
parent
e8495c853b
commit
6782a8611a
23 changed files with 359 additions and 130 deletions
|
@ -13,17 +13,13 @@ export { z } from 'astro/zod';
|
|||
|
||||
const contentDir = '@@CONTENT_DIR@@';
|
||||
|
||||
const contentEntryGlob = import.meta.glob('@@CONTENT_ENTRY_GLOB_PATH@@', {
|
||||
query: { astroContentCollectionEntry: true },
|
||||
});
|
||||
const contentEntryGlob = '@@CONTENT_ENTRY_GLOB_PATH@@';
|
||||
const contentCollectionToEntryMap = createCollectionToGlobResultMap({
|
||||
globResult: contentEntryGlob,
|
||||
contentDir,
|
||||
});
|
||||
|
||||
const dataEntryGlob = import.meta.glob('@@DATA_ENTRY_GLOB_PATH@@', {
|
||||
query: { astroDataCollectionEntry: true },
|
||||
});
|
||||
const dataEntryGlob = '@@DATA_ENTRY_GLOB_PATH@@';
|
||||
const dataCollectionToEntryMap = createCollectionToGlobResultMap({
|
||||
globResult: dataEntryGlob,
|
||||
contentDir,
|
||||
|
@ -45,9 +41,7 @@ function createGlobLookup(glob) {
|
|||
};
|
||||
}
|
||||
|
||||
const renderEntryGlob = import.meta.glob('@@RENDER_ENTRY_GLOB_PATH@@', {
|
||||
query: { astroRenderContent: true },
|
||||
});
|
||||
const renderEntryGlob = '@@RENDER_ENTRY_GLOB_PATH@@'
|
||||
const collectionToRenderEntryMap = createCollectionToGlobResultMap({
|
||||
globResult: renderEntryGlob,
|
||||
contentDir,
|
||||
|
|
|
@ -69,7 +69,7 @@ export function createGetCollection({
|
|||
let entries: any[] = [];
|
||||
// Cache `getCollection()` calls in production only
|
||||
// prevents stale cache in development
|
||||
if (import.meta.env.PROD && cacheEntriesByCollection.has(collection)) {
|
||||
if (!import.meta.env?.DEV && cacheEntriesByCollection.has(collection)) {
|
||||
// Always return a new instance so consumers can safely mutate it
|
||||
entries = [...cacheEntriesByCollection.get(collection)!];
|
||||
} else {
|
||||
|
|
|
@ -112,14 +112,14 @@ export function astroConfigBuildPlugin(
|
|||
): AstroBuildPlugin {
|
||||
let ssrPluginContext: any = undefined;
|
||||
return {
|
||||
build: 'ssr',
|
||||
targets: ['server', 'content'],
|
||||
hooks: {
|
||||
'build:before': ({ build }) => {
|
||||
'build:before': ({ target }) => {
|
||||
return {
|
||||
vitePlugin: {
|
||||
name: 'astro:content-build-plugin',
|
||||
generateBundle() {
|
||||
if (build === 'ssr') {
|
||||
if (target === 'server') {
|
||||
ssrPluginContext = this;
|
||||
}
|
||||
},
|
||||
|
@ -206,7 +206,7 @@ export function astroConfigBuildPlugin(
|
|||
)
|
||||
);
|
||||
}
|
||||
mutate(chunk, 'server', newCode);
|
||||
mutate(chunk, ['server'], newCode);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
@ -4,9 +4,9 @@ import { extname } from 'node:path';
|
|||
import { fileURLToPath, pathToFileURL } from 'node:url';
|
||||
import pLimit from 'p-limit';
|
||||
import type { Plugin } from 'vite';
|
||||
import type { AstroSettings, ContentEntryType } from '../@types/astro.js';
|
||||
import type { AstroSettings } from '../@types/astro.js';
|
||||
import { AstroError, AstroErrorData } from '../core/errors/index.js';
|
||||
import { appendForwardSlash } from '../core/path.js';
|
||||
import { appendForwardSlash, joinPaths, removeFileExtension, removeLeadingForwardSlash, slash } from '../core/path.js';
|
||||
import { rootRelativePath } from '../core/util.js';
|
||||
import { VIRTUAL_MODULE_ID } from './consts.js';
|
||||
import {
|
||||
|
@ -20,7 +20,6 @@ import {
|
|||
getEntryType,
|
||||
getExtGlob,
|
||||
type ContentLookupMap,
|
||||
type ContentPaths,
|
||||
} from './utils.js';
|
||||
|
||||
interface AstroContentVirtualModPluginParams {
|
||||
|
@ -30,38 +29,6 @@ interface AstroContentVirtualModPluginParams {
|
|||
export function astroContentVirtualModPlugin({
|
||||
settings,
|
||||
}: AstroContentVirtualModPluginParams): Plugin {
|
||||
const contentPaths = getContentPaths(settings.config);
|
||||
const relContentDir = rootRelativePath(settings.config.root, contentPaths.contentDir);
|
||||
|
||||
const contentEntryConfigByExt = getEntryConfigByExtMap(settings.contentEntryTypes);
|
||||
const contentEntryExts = [...contentEntryConfigByExt.keys()];
|
||||
const dataEntryExts = getDataEntryExts(settings);
|
||||
|
||||
const virtualModContents = fsMod
|
||||
.readFileSync(contentPaths.virtualModTemplate, 'utf-8')
|
||||
.replace(
|
||||
'@@COLLECTION_NAME_BY_REFERENCE_KEY@@',
|
||||
new URL('reference-map.json', contentPaths.cacheDir).pathname
|
||||
)
|
||||
.replace('@@CONTENT_DIR@@', relContentDir)
|
||||
.replace(
|
||||
"'@@CONTENT_ENTRY_GLOB_PATH@@'",
|
||||
JSON.stringify(globWithUnderscoresIgnored(relContentDir, contentEntryExts))
|
||||
)
|
||||
.replace(
|
||||
"'@@DATA_ENTRY_GLOB_PATH@@'",
|
||||
JSON.stringify(globWithUnderscoresIgnored(relContentDir, dataEntryExts))
|
||||
)
|
||||
.replace(
|
||||
"'@@RENDER_ENTRY_GLOB_PATH@@'",
|
||||
JSON.stringify(
|
||||
globWithUnderscoresIgnored(
|
||||
relContentDir,
|
||||
/** Note: data collections excluded */ contentEntryExts
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
const astroContentVirtualModuleId = '\0' + VIRTUAL_MODULE_ID;
|
||||
|
||||
return {
|
||||
|
@ -73,45 +40,80 @@ export function astroContentVirtualModPlugin({
|
|||
},
|
||||
async load(id) {
|
||||
if (id === astroContentVirtualModuleId) {
|
||||
const stringifiedLookupMap = await getStringifiedLookupMap({
|
||||
const lookupMap = await generateLookupMap({
|
||||
settings,
|
||||
fs: fsMod,
|
||||
contentPaths,
|
||||
contentEntryConfigByExt,
|
||||
dataEntryExts,
|
||||
root: settings.config.root,
|
||||
});
|
||||
const code = await generateContentEntryFile({ settings, fs: fsMod, lookupMap });
|
||||
|
||||
return {
|
||||
code: virtualModContents.replace(
|
||||
'/* @@LOOKUP_MAP_ASSIGNMENT@@ */',
|
||||
`lookupMap = ${stringifiedLookupMap};`
|
||||
),
|
||||
code
|
||||
};
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export async function generateContentEntryFile({
|
||||
settings,
|
||||
fs,
|
||||
lookupMap,
|
||||
}: {
|
||||
settings: AstroSettings;
|
||||
fs: typeof fsMod;
|
||||
lookupMap: ContentLookupMap
|
||||
}) {
|
||||
const contentPaths = getContentPaths(settings.config);
|
||||
const relContentDir = rootRelativePath(settings.config.root, contentPaths.contentDir);
|
||||
|
||||
const contentEntryConfigByExt = getEntryConfigByExtMap(settings.contentEntryTypes);
|
||||
const contentEntryExts = [...contentEntryConfigByExt.keys()];
|
||||
const dataEntryExts = getDataEntryExts(settings);
|
||||
|
||||
const [contentEntryGlobResult, dataEntryGlobResult, renderEntryGlobResult] = await Promise.all([contentEntryExts, dataEntryExts, contentEntryExts].map((exts, i) => getStringifiedGlobResult(settings, exts, i === 2 ? '.render.mjs' : undefined)));
|
||||
|
||||
const virtualModContents = fs
|
||||
.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)};`
|
||||
);
|
||||
|
||||
return virtualModContents;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a map from a collection + slug to the local file path.
|
||||
* This is used internally to resolve entry imports when using `getEntry()`.
|
||||
* @see `content-module.template.mjs`
|
||||
*/
|
||||
export async function getStringifiedLookupMap({
|
||||
contentPaths,
|
||||
contentEntryConfigByExt,
|
||||
dataEntryExts,
|
||||
root,
|
||||
export async function generateLookupMap({
|
||||
settings,
|
||||
fs,
|
||||
}: {
|
||||
contentEntryConfigByExt: Map<string, ContentEntryType>;
|
||||
dataEntryExts: string[];
|
||||
contentPaths: Pick<ContentPaths, 'contentDir' | 'config'>;
|
||||
root: URL;
|
||||
settings: AstroSettings;
|
||||
fs: typeof fsMod;
|
||||
}) {
|
||||
const { root } = settings.config;
|
||||
const contentPaths = getContentPaths(settings.config);
|
||||
const relContentDir = rootRelativePath(root, contentPaths.contentDir, false);
|
||||
|
||||
const contentEntryConfigByExt = getEntryConfigByExtMap(settings.contentEntryTypes);
|
||||
const dataEntryExts = getDataEntryExts(settings);
|
||||
|
||||
const { contentDir } = contentPaths;
|
||||
const relContentDir = rootRelativePath(root, contentDir, false);
|
||||
const contentEntryExts = [...contentEntryConfigByExt.keys()];
|
||||
|
||||
let lookupMap: ContentLookupMap = {};
|
||||
|
@ -200,7 +202,7 @@ export async function getStringifiedLookupMap({
|
|||
|
||||
await Promise.all(promises);
|
||||
|
||||
return JSON.stringify(lookupMap);
|
||||
return lookupMap;
|
||||
}
|
||||
|
||||
const UnexpectedLookupMapError = new AstroError({
|
||||
|
@ -208,6 +210,31 @@ const UnexpectedLookupMapError = new AstroError({
|
|||
message: `Unexpected error while parsing content entry IDs and slugs.`,
|
||||
});
|
||||
|
||||
async function getStringifiedGlobResult(settings: AstroSettings, exts: string[], importExtension = '.mjs'): Promise<string> {
|
||||
const pattern = globWithUnderscoresIgnored('./', exts);
|
||||
const contentPaths = getContentPaths(settings.config);
|
||||
|
||||
const files = await glob(pattern, {
|
||||
cwd: fileURLToPath(contentPaths.contentDir),
|
||||
fs: {
|
||||
readdir: fsMod.readdir.bind(fsMod),
|
||||
readdirSync: fsMod.readdirSync.bind(fsMod),
|
||||
},
|
||||
onlyFiles: true,
|
||||
objectMode: true,
|
||||
})
|
||||
|
||||
let str = '{';
|
||||
for (const file of files) {
|
||||
const importSpecifier = `./${removeFileExtension(removeLeadingForwardSlash(slash(file.path)))}${importExtension}`;
|
||||
const srcRelativePath = new URL(`./${slash(file.path)}`, contentPaths.contentDir).toString().replace(settings.config.root.toString(), '/')
|
||||
str += `\n "${srcRelativePath}": () => import("${importSpecifier}"),`
|
||||
}
|
||||
str += '\n}'
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
function globWithUnderscoresIgnored(relContentDir: string, exts: string[]): string[] {
|
||||
const extGlob = getExtGlob(exts);
|
||||
const contentDir = appendForwardSlash(relContentDir);
|
||||
|
|
|
@ -5,16 +5,19 @@ import type { StaticBuildOptions, ViteBuildReturn } from './types.js';
|
|||
type RollupOutputArray = Extract<ViteBuildReturn, Array<any>>;
|
||||
type OutputChunkorAsset = RollupOutputArray[number]['output'][number];
|
||||
type OutputChunk = Extract<OutputChunkorAsset, { type: 'chunk' }>;
|
||||
export type BuildTarget = 'server' | 'client' | 'content';
|
||||
|
||||
type MutateChunk = (chunk: OutputChunk, build: 'server' | 'client', newCode: string) => void;
|
||||
type MutateChunk = (chunk: OutputChunk, targets: BuildTarget[], newCode: string) => void;
|
||||
|
||||
export interface BuildBeforeHookResult {
|
||||
enforce?: 'after-user-plugins';
|
||||
vitePlugin: VitePlugin | VitePlugin[] | undefined;
|
||||
}
|
||||
|
||||
export type AstroBuildPlugin = {
|
||||
build: 'ssr' | 'client' | 'both';
|
||||
targets: BuildTarget[];
|
||||
hooks?: {
|
||||
'build:before'?: (opts: { build: 'ssr' | 'client'; input: Set<string> }) => {
|
||||
enforce?: 'after-user-plugins';
|
||||
vitePlugin: VitePlugin | VitePlugin[] | undefined;
|
||||
};
|
||||
'build:before'?: (opts: { target: BuildTarget; input: Set<string> }) => BuildBeforeHookResult | Promise<BuildBeforeHookResult>;
|
||||
'build:post'?: (opts: {
|
||||
ssrOutputs: RollupOutputArray;
|
||||
clientOutputs: RollupOutputArray;
|
||||
|
@ -24,40 +27,32 @@ export type AstroBuildPlugin = {
|
|||
};
|
||||
|
||||
export function createPluginContainer(options: StaticBuildOptions, internals: BuildInternals) {
|
||||
const clientPlugins: AstroBuildPlugin[] = [];
|
||||
const ssrPlugins: AstroBuildPlugin[] = [];
|
||||
const plugins = new Map<BuildTarget, AstroBuildPlugin[]>();
|
||||
const allPlugins = new Set<AstroBuildPlugin>();
|
||||
for (const target of ['client', 'server', 'content'] satisfies BuildTarget[]) {
|
||||
plugins.set(target, []);
|
||||
}
|
||||
|
||||
return {
|
||||
options,
|
||||
internals,
|
||||
register(plugin: AstroBuildPlugin) {
|
||||
allPlugins.add(plugin);
|
||||
switch (plugin.build) {
|
||||
case 'client': {
|
||||
clientPlugins.push(plugin);
|
||||
break;
|
||||
}
|
||||
case 'ssr': {
|
||||
ssrPlugins.push(plugin);
|
||||
break;
|
||||
}
|
||||
case 'both': {
|
||||
clientPlugins.push(plugin);
|
||||
ssrPlugins.push(plugin);
|
||||
break;
|
||||
}
|
||||
for (const target of plugin.targets) {
|
||||
const targetPlugins = plugins.get(target) ?? [];
|
||||
targetPlugins.push(plugin);
|
||||
plugins.set(target, targetPlugins);
|
||||
}
|
||||
},
|
||||
|
||||
// Hooks
|
||||
runBeforeHook(build: 'ssr' | 'client', input: Set<string>) {
|
||||
let plugins = build === 'ssr' ? ssrPlugins : clientPlugins;
|
||||
async runBeforeHook(target: BuildTarget, input: Set<string>) {
|
||||
let targetPlugins = plugins.get(target) ?? [];
|
||||
let vitePlugins: Array<VitePlugin | VitePlugin[]> = [];
|
||||
let lastVitePlugins: Array<VitePlugin | VitePlugin[]> = [];
|
||||
for (const plugin of plugins) {
|
||||
for (const plugin of targetPlugins) {
|
||||
if (plugin.hooks?.['build:before']) {
|
||||
let result = plugin.hooks['build:before']({ build, input });
|
||||
let result = await plugin.hooks['build:before']({ target, input });
|
||||
if (result.vitePlugin) {
|
||||
vitePlugins.push(result.vitePlugin);
|
||||
}
|
||||
|
@ -74,7 +69,7 @@ export function createPluginContainer(options: StaticBuildOptions, internals: Bu
|
|||
const mutations = new Map<
|
||||
string,
|
||||
{
|
||||
build: 'server' | 'client';
|
||||
targets: BuildTarget[];
|
||||
code: string;
|
||||
}
|
||||
>();
|
||||
|
@ -93,10 +88,10 @@ export function createPluginContainer(options: StaticBuildOptions, internals: Bu
|
|||
clientOutputs.push(clientReturn);
|
||||
}
|
||||
|
||||
const mutate: MutateChunk = (chunk, build, newCode) => {
|
||||
const mutate: MutateChunk = (chunk, targets, newCode) => {
|
||||
chunk.code = newCode;
|
||||
mutations.set(chunk.fileName, {
|
||||
build,
|
||||
targets,
|
||||
code: newCode,
|
||||
});
|
||||
};
|
||||
|
|
|
@ -4,7 +4,9 @@ import type { AstroBuildPluginContainer } from '../plugin.js';
|
|||
import { pluginAliasResolve } from './plugin-alias-resolve.js';
|
||||
import { pluginAnalyzer } from './plugin-analyzer.js';
|
||||
import { pluginComponentEntry } from './plugin-component-entry.js';
|
||||
import { pluginContent } from './plugin-content.js';
|
||||
import { pluginCSS } from './plugin-css.js';
|
||||
import { pluginExternalize } from './plugin-external.js';
|
||||
import { pluginHoistedScripts } from './plugin-hoisted-scripts.js';
|
||||
import { pluginInternals } from './plugin-internals.js';
|
||||
import { pluginManifest } from './plugin-manifest.js';
|
||||
|
@ -23,6 +25,7 @@ export function registerAllPlugins({ internals, options, register }: AstroBuildP
|
|||
register(pluginRenderers(options));
|
||||
register(pluginMiddleware(options, internals));
|
||||
register(pluginPages(options, internals));
|
||||
register(pluginContent(options, internals));
|
||||
register(pluginCSS(options, internals));
|
||||
register(astroHeadBuildPlugin(internals));
|
||||
register(pluginPrerender(options, internals));
|
||||
|
@ -30,4 +33,5 @@ export function registerAllPlugins({ internals, options, register }: AstroBuildP
|
|||
register(pluginHoistedScripts(options, internals));
|
||||
register(pluginSSR(options, internals));
|
||||
register(pluginSSRSplit(options, internals));
|
||||
register(pluginExternalize());
|
||||
}
|
||||
|
|
|
@ -52,7 +52,7 @@ function matches(pattern: string | RegExp, importee: string) {
|
|||
|
||||
export function pluginAliasResolve(internals: BuildInternals): AstroBuildPlugin {
|
||||
return {
|
||||
build: 'client',
|
||||
targets: ['client'],
|
||||
hooks: {
|
||||
'build:before': () => {
|
||||
return {
|
||||
|
|
|
@ -330,7 +330,7 @@ export function pluginAnalyzer(
|
|||
internals: BuildInternals
|
||||
): AstroBuildPlugin {
|
||||
return {
|
||||
build: 'ssr',
|
||||
targets: ['server'],
|
||||
hooks: {
|
||||
'build:before': () => {
|
||||
return {
|
||||
|
|
|
@ -77,7 +77,7 @@ export function normalizeEntryId(id: string): string {
|
|||
|
||||
export function pluginComponentEntry(internals: BuildInternals): AstroBuildPlugin {
|
||||
return {
|
||||
build: 'client',
|
||||
targets: ['client'],
|
||||
hooks: {
|
||||
'build:before': () => {
|
||||
return {
|
||||
|
|
70
packages/astro/src/core/build/plugins/plugin-content.ts
Normal file
70
packages/astro/src/core/build/plugins/plugin-content.ts
Normal file
|
@ -0,0 +1,70 @@
|
|||
import type { Plugin as VitePlugin } from 'vite';
|
||||
import fsMod from 'node:fs';
|
||||
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 } from '../../path.js';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import type { ContentLookupMap } from '../../../content/utils.js';
|
||||
import { CONTENT_RENDER_FLAG } from '../../../content/consts.js';
|
||||
|
||||
function vitePluginContent(opts: StaticBuildOptions, lookupMap: ContentLookupMap): VitePlugin {
|
||||
return {
|
||||
name: '@astro/plugin-build-content',
|
||||
|
||||
options(options) {
|
||||
let newOptions = Object.assign({}, options);
|
||||
if (opts.settings.config.output === 'static') {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
for (const [_collectionName, { type, entries }] of Object.entries(lookupMap)) {
|
||||
let newInputs = Object.values(entries).flatMap(entry => {
|
||||
const input = fileURLToPath(joinPaths(opts.settings.config.root.toString(), entry));
|
||||
const inputs = [`${input}?${collectionTypeToFlag(type)}`];
|
||||
if (type === 'content') {
|
||||
inputs.push(`${input}?${CONTENT_RENDER_FLAG}`)
|
||||
}
|
||||
return inputs;
|
||||
})
|
||||
newOptions = addRollupInput(newOptions, newInputs)
|
||||
}
|
||||
}
|
||||
return newOptions;
|
||||
},
|
||||
|
||||
resolveId(id) {
|
||||
console.log(id);
|
||||
},
|
||||
|
||||
async generateBundle() {
|
||||
const content = await generateContentEntryFile({ settings: opts.settings, fs: fsMod, lookupMap });
|
||||
this.emitFile({
|
||||
type: 'prebuilt-chunk',
|
||||
code: content,
|
||||
fileName: 'content/index.mjs'
|
||||
})
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function collectionTypeToFlag(type: 'content' | 'data') {
|
||||
const name = type[0].toUpperCase() + type.slice(1);
|
||||
return `astro${name}CollectionEntry`
|
||||
}
|
||||
|
||||
export function pluginContent(opts: StaticBuildOptions, _internals: BuildInternals): AstroBuildPlugin {
|
||||
return {
|
||||
targets: ['content'],
|
||||
hooks: {
|
||||
async 'build:before'() {
|
||||
// TODO: filter lookupMap based on file hashes
|
||||
const lookupMap = await generateLookupMap({ settings: opts.settings, fs: fsMod });
|
||||
|
||||
return {
|
||||
vitePlugin: vitePluginContent(opts, lookupMap),
|
||||
};
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
|
@ -4,7 +4,7 @@ import type { GetModuleInfo } from 'rollup';
|
|||
import { type ResolvedConfig, type Plugin as VitePlugin } from 'vite';
|
||||
import { isBuildableCSSRequest } from '../../../vite-plugin-astro-server/util.js';
|
||||
import type { BuildInternals } from '../internal.js';
|
||||
import type { AstroBuildPlugin } from '../plugin.js';
|
||||
import type { AstroBuildPlugin, BuildTarget } from '../plugin.js';
|
||||
import type { PageBuildData, StaticBuildOptions, StylesheetAsset } from '../types.js';
|
||||
|
||||
import { PROPAGATED_ASSET_FLAG } from '../../../content/consts.js';
|
||||
|
@ -22,7 +22,7 @@ import { extendManualChunks } from './util.js';
|
|||
interface PluginOptions {
|
||||
internals: BuildInternals;
|
||||
buildOptions: StaticBuildOptions;
|
||||
target: 'client' | 'server';
|
||||
target: BuildTarget;
|
||||
}
|
||||
|
||||
/***** ASTRO PLUGIN *****/
|
||||
|
@ -32,13 +32,13 @@ export function pluginCSS(
|
|||
internals: BuildInternals
|
||||
): AstroBuildPlugin {
|
||||
return {
|
||||
build: 'both',
|
||||
targets: ['client', 'server'],
|
||||
hooks: {
|
||||
'build:before': ({ build }) => {
|
||||
'build:before': ({ target }) => {
|
||||
let plugins = rollupPluginAstroBuildCSS({
|
||||
buildOptions: options,
|
||||
internals,
|
||||
target: build === 'ssr' ? 'server' : 'client',
|
||||
target,
|
||||
});
|
||||
|
||||
return {
|
||||
|
|
42
packages/astro/src/core/build/plugins/plugin-external.ts
Normal file
42
packages/astro/src/core/build/plugins/plugin-external.ts
Normal file
|
@ -0,0 +1,42 @@
|
|||
import type { Plugin as VitePlugin } from 'vite';
|
||||
import type { AstroBuildPlugin } from '../plugin.js';
|
||||
import { slash, removeLeadingForwardSlash } from '../../path.js';
|
||||
|
||||
export function vitePluginExternalize(): VitePlugin {
|
||||
const MODULE_ID = `astro:content`;
|
||||
const VIRTUAL_MODULE_ID = `\0${MODULE_ID}`;
|
||||
|
||||
return {
|
||||
name: '@astro/plugin-externalize',
|
||||
enforce: 'pre',
|
||||
resolveId(id) {
|
||||
// Ensure that `astro:content` is treated as external and rewritten to the final entrypoint
|
||||
if (id === MODULE_ID) {
|
||||
return { id: VIRTUAL_MODULE_ID, external: true }
|
||||
}
|
||||
return null;
|
||||
},
|
||||
renderChunk(code, chunk) {
|
||||
if (chunk.imports.find(name => name === VIRTUAL_MODULE_ID)) {
|
||||
// We want to generate a relative path and avoid a hardcoded absolute path in the output!
|
||||
const steps = removeLeadingForwardSlash(slash(chunk.fileName)).split('/').length - 1;
|
||||
const prefix = '../'.repeat(steps) || './';
|
||||
// dist/content/index.mjs is generated by the "content" build
|
||||
return code.replace(VIRTUAL_MODULE_ID, `${prefix}content/index.mjs`);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function pluginExternalize(): AstroBuildPlugin {
|
||||
return {
|
||||
targets: ['server'],
|
||||
hooks: {
|
||||
'build:before': () => {
|
||||
return {
|
||||
vitePlugin: vitePluginExternalize(),
|
||||
};
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
|
@ -108,7 +108,7 @@ export function pluginHoistedScripts(
|
|||
internals: BuildInternals
|
||||
): AstroBuildPlugin {
|
||||
return {
|
||||
build: 'client',
|
||||
targets: ['client'],
|
||||
hooks: {
|
||||
'build:before': () => {
|
||||
return {
|
||||
|
|
|
@ -62,7 +62,7 @@ export function vitePluginInternals(input: Set<string>, internals: BuildInternal
|
|||
|
||||
export function pluginInternals(internals: BuildInternals): AstroBuildPlugin {
|
||||
return {
|
||||
build: 'both',
|
||||
targets: ['client', 'server'],
|
||||
hooks: {
|
||||
'build:before': ({ input }) => {
|
||||
return {
|
||||
|
|
|
@ -79,7 +79,7 @@ export function pluginManifest(
|
|||
internals: BuildInternals
|
||||
): AstroBuildPlugin {
|
||||
return {
|
||||
build: 'ssr',
|
||||
targets: ['server'],
|
||||
hooks: {
|
||||
'build:before': () => {
|
||||
return {
|
||||
|
@ -107,7 +107,7 @@ export function pluginManifest(
|
|||
: undefined,
|
||||
});
|
||||
const code = injectManifest(manifest, internals.manifestEntryChunk);
|
||||
mutate(internals.manifestEntryChunk, 'server', code);
|
||||
mutate(internals.manifestEntryChunk, ['server'], code);
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
|
@ -71,7 +71,7 @@ export function pluginMiddleware(
|
|||
internals: BuildInternals
|
||||
): AstroBuildPlugin {
|
||||
return {
|
||||
build: 'ssr',
|
||||
targets: ['server'],
|
||||
hooks: {
|
||||
'build:before': () => {
|
||||
return {
|
||||
|
|
|
@ -104,7 +104,7 @@ export function shouldBundleMiddleware(settings: AstroSettings) {
|
|||
|
||||
export function pluginPages(opts: StaticBuildOptions, internals: BuildInternals): AstroBuildPlugin {
|
||||
return {
|
||||
build: 'ssr',
|
||||
targets: ['server'],
|
||||
hooks: {
|
||||
'build:before': () => {
|
||||
return {
|
||||
|
|
|
@ -40,7 +40,7 @@ export function pluginPrerender(
|
|||
internals: BuildInternals
|
||||
): AstroBuildPlugin {
|
||||
return {
|
||||
build: 'ssr',
|
||||
targets: ['server'],
|
||||
hooks: {
|
||||
'build:before': () => {
|
||||
return {
|
||||
|
|
|
@ -48,7 +48,8 @@ export function vitePluginRenderers(opts: StaticBuildOptions): VitePlugin {
|
|||
|
||||
export function pluginRenderers(opts: StaticBuildOptions): AstroBuildPlugin {
|
||||
return {
|
||||
build: 'ssr',
|
||||
// TODO: handle for content?
|
||||
targets: ['server'],
|
||||
hooks: {
|
||||
'build:before': () => {
|
||||
return {
|
||||
|
|
|
@ -97,7 +97,7 @@ export function pluginSSR(
|
|||
const ssr = isServerLikeOutput(options.settings.config);
|
||||
const functionPerRouteEnabled = isFunctionPerRouteEnabled(options.settings.adapter);
|
||||
return {
|
||||
build: 'ssr',
|
||||
targets: ['server'],
|
||||
hooks: {
|
||||
'build:before': () => {
|
||||
let vitePlugin =
|
||||
|
@ -218,7 +218,7 @@ export function pluginSSRSplit(
|
|||
const functionPerRouteEnabled = isFunctionPerRouteEnabled(options.settings.adapter);
|
||||
|
||||
return {
|
||||
build: 'ssr',
|
||||
targets: ['server'],
|
||||
hooks: {
|
||||
'build:before': () => {
|
||||
let vitePlugin =
|
||||
|
|
|
@ -4,7 +4,7 @@ import glob from 'fast-glob';
|
|||
import { bgGreen, bgMagenta, black, dim } from 'kleur/colors';
|
||||
import fs from 'node:fs';
|
||||
import path, { extname } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { fileURLToPath, pathToFileURL } from 'node:url';
|
||||
import * as vite from 'vite';
|
||||
import type { RouteData } from '../../@types/astro.js';
|
||||
import {
|
||||
|
@ -13,7 +13,7 @@ import {
|
|||
type BuildInternals,
|
||||
} from '../../core/build/internal.js';
|
||||
import { emptyDir, removeEmptyDirs } from '../../core/fs/index.js';
|
||||
import { appendForwardSlash, prependForwardSlash } from '../../core/path.js';
|
||||
import { appendForwardSlash, prependForwardSlash, removeFileExtension } from '../../core/path.js';
|
||||
import { isModeServerWithNoAdapter } from '../../core/util.js';
|
||||
import { runHookBuildSetup } from '../../integrations/index.js';
|
||||
import { getOutputDirectory, isServerLikeOutput } from '../../prerender/utils.js';
|
||||
|
@ -32,6 +32,8 @@ import { RESOLVED_SPLIT_MODULE_ID, RESOLVED_SSR_VIRTUAL_MODULE_ID } from './plug
|
|||
import { ASTRO_PAGE_EXTENSION_POST_PATTERN } from './plugins/util.js';
|
||||
import type { PageBuildData, StaticBuildOptions } from './types.js';
|
||||
import { getTimeStat } from './util.js';
|
||||
import { hasContentFlag } from '../../content/utils.js';
|
||||
import { CONTENT_FLAGS, CONTENT_RENDER_FLAG, PROPAGATED_ASSET_FLAG } from '../../content/consts.js';
|
||||
|
||||
export async function viteBuild(opts: StaticBuildOptions) {
|
||||
const { allPages, settings } = opts;
|
||||
|
@ -77,13 +79,26 @@ export async function viteBuild(opts: StaticBuildOptions) {
|
|||
const container = createPluginContainer(opts, internals);
|
||||
registerAllPlugins(container);
|
||||
|
||||
// Build your project (SSR application code, assets, client JS, etc.)
|
||||
const ssrTime = performance.now();
|
||||
opts.logger.info('build', `Building ${settings.config.output} entrypoints...`);
|
||||
const ssrOutput = await ssrBuild(opts, internals, pageInput, container);
|
||||
opts.logger.info('build', dim(`Completed in ${getTimeStat(ssrTime, performance.now())}.`));
|
||||
let buildContent = async () => {
|
||||
const contentTime = performance.now();
|
||||
opts.logger.info('content', `Building collections...`);
|
||||
await contentBuild(opts, internals, new Set(), container);
|
||||
opts.logger.info('content', dim(`Completed in ${getTimeStat(contentTime, performance.now())}.`));
|
||||
}
|
||||
|
||||
let ssrOutput: any;
|
||||
let buildServer = async () => {
|
||||
// Build your project (SSR application code, assets, client JS, etc.)
|
||||
const ssrTime = performance.now();
|
||||
opts.logger.info('build', `Building ${settings.config.output} entrypoints...`);
|
||||
ssrOutput = await ssrBuild(opts, internals, pageInput, container);
|
||||
opts.logger.info('build', dim(`Completed in ${getTimeStat(ssrTime, performance.now())}.`));
|
||||
|
||||
settings.timer.end('SSR build');
|
||||
}
|
||||
|
||||
await Promise.all([buildContent(), buildServer()]);
|
||||
|
||||
settings.timer.end('SSR build');
|
||||
settings.timer.start('Client build');
|
||||
|
||||
const rendererClientEntrypoints = settings.renderers
|
||||
|
@ -149,7 +164,7 @@ async function ssrBuild(
|
|||
const ssr = isServerLikeOutput(settings.config);
|
||||
const out = getOutputDirectory(settings.config);
|
||||
const routes = Object.values(allPages).map((pd) => pd.route);
|
||||
const { lastVitePlugins, vitePlugins } = container.runBeforeHook('ssr', input);
|
||||
const { lastVitePlugins, vitePlugins } = await container.runBeforeHook('server', input);
|
||||
|
||||
const viteBuildConfig: vite.InlineConfig = {
|
||||
...viteConfig,
|
||||
|
@ -234,6 +249,87 @@ async function ssrBuild(
|
|||
return await vite.build(updatedViteBuildConfig);
|
||||
}
|
||||
|
||||
async function contentBuild(
|
||||
opts: StaticBuildOptions,
|
||||
internals: BuildInternals,
|
||||
input: Set<string>,
|
||||
container: AstroBuildPluginContainer
|
||||
) {
|
||||
const { settings, viteConfig } = opts;
|
||||
const ssr = isServerLikeOutput(settings.config);
|
||||
const out = getOutputDirectory(settings.config);
|
||||
const { lastVitePlugins, vitePlugins } = await container.runBeforeHook('content', input);
|
||||
|
||||
const viteBuildConfig: vite.InlineConfig = {
|
||||
...viteConfig,
|
||||
mode: viteConfig.mode || 'production',
|
||||
// Check using `settings...` as `viteConfig` always defaults to `warn` by Astro
|
||||
logLevel: settings.config.vite.logLevel ?? 'error',
|
||||
build: {
|
||||
target: 'esnext',
|
||||
// Vite defaults cssMinify to false in SSR by default, but we want to minify it
|
||||
// as the CSS generated are used and served to the client.
|
||||
cssMinify: viteConfig.build?.minify == null ? true : !!viteConfig.build?.minify,
|
||||
...viteConfig.build,
|
||||
emptyOutDir: false,
|
||||
manifest: false,
|
||||
outDir: fileURLToPath(out),
|
||||
copyPublicDir: !ssr,
|
||||
rollupOptions: {
|
||||
...viteConfig.build?.rollupOptions,
|
||||
input: [],
|
||||
output: {
|
||||
format: 'esm',
|
||||
chunkFileNames(info) {
|
||||
if (info.moduleIds.length === 1) {
|
||||
const url = pathToFileURL(info.moduleIds[0]);
|
||||
const distRelative = url.toString().replace(settings.config.srcDir.toString(), '')
|
||||
let entryFileName = removeFileExtension(distRelative);
|
||||
return `${entryFileName}.render.mjs`;
|
||||
}
|
||||
return '[name]_[hash].mjs';
|
||||
},
|
||||
...viteConfig.build?.rollupOptions?.output,
|
||||
entryFileNames(info) {
|
||||
const params = new URLSearchParams(info.moduleIds[0].split('?').pop() ?? '');
|
||||
const flags = Array.from(params.keys());
|
||||
const url = pathToFileURL(info.moduleIds[0]);
|
||||
const distRelative = url.toString().replace(settings.config.srcDir.toString(), '')
|
||||
|
||||
let entryFileName = removeFileExtension(distRelative);
|
||||
if (flags[0] === PROPAGATED_ASSET_FLAG) {
|
||||
entryFileName += `.assets`
|
||||
} else if (flags[0] === CONTENT_RENDER_FLAG) {
|
||||
entryFileName += '.render'
|
||||
}
|
||||
return `${entryFileName}.mjs`;
|
||||
},
|
||||
assetFileNames: `${settings.config.build.assets}/[name].[extname]`,
|
||||
},
|
||||
},
|
||||
ssr: true,
|
||||
ssrEmitAssets: true,
|
||||
// improve build performance
|
||||
minify: false,
|
||||
modulePreload: { polyfill: false },
|
||||
reportCompressedSize: false,
|
||||
},
|
||||
plugins: [...vitePlugins, ...(viteConfig.plugins || []), ...lastVitePlugins],
|
||||
envPrefix: viteConfig.envPrefix ?? 'PUBLIC_',
|
||||
base: settings.config.base,
|
||||
};
|
||||
|
||||
const updatedViteBuildConfig = await runHookBuildSetup({
|
||||
config: settings.config,
|
||||
pages: internals.pagesByComponent,
|
||||
vite: viteBuildConfig,
|
||||
target: 'server',
|
||||
logger: opts.logger,
|
||||
});
|
||||
|
||||
return await vite.build(updatedViteBuildConfig);
|
||||
}
|
||||
|
||||
async function clientBuild(
|
||||
opts: StaticBuildOptions,
|
||||
internals: BuildInternals,
|
||||
|
@ -255,7 +351,7 @@ async function clientBuild(
|
|||
return null;
|
||||
}
|
||||
|
||||
const { lastVitePlugins, vitePlugins } = container.runBeforeHook('client', input);
|
||||
const { lastVitePlugins, vitePlugins } = await container.runBeforeHook('client', input);
|
||||
opts.logger.info(null, `\n${bgGreen(black(' building client '))}`);
|
||||
|
||||
const viteBuildConfig: vite.InlineConfig = {
|
||||
|
@ -309,7 +405,7 @@ async function runPostBuildHooks(
|
|||
const build = container.options.settings.config.build;
|
||||
for (const [fileName, mutation] of mutations) {
|
||||
const root = isServerLikeOutput(config)
|
||||
? mutation.build === 'server'
|
||||
? mutation.targets.includes('server')
|
||||
? build.server
|
||||
: build.client
|
||||
: config.outDir;
|
||||
|
@ -412,7 +508,7 @@ async function copyFiles(fromFolder: URL, toFolder: URL, includeDotfiles = false
|
|||
const lastFolder = new URL('./', to);
|
||||
return fs.promises
|
||||
.mkdir(lastFolder, { recursive: true })
|
||||
.then(() => fs.promises.copyFile(from, to));
|
||||
.then(() => fs.promises.copyFile(from, to, fs.constants.COPYFILE_FICLONE));
|
||||
})
|
||||
);
|
||||
}
|
||||
|
|
|
@ -103,7 +103,7 @@ export default function configHeadVitePlugin(): vite.Plugin {
|
|||
|
||||
export function astroHeadBuildPlugin(internals: BuildInternals): AstroBuildPlugin {
|
||||
return {
|
||||
build: 'ssr',
|
||||
targets: ['server'],
|
||||
hooks: {
|
||||
'build:before'() {
|
||||
return {
|
||||
|
|
|
@ -65,7 +65,7 @@ export default async function copy() {
|
|||
const dest = resolve(file.replace(/^[^/]+/, 'dist'));
|
||||
return fs
|
||||
.mkdir(dirname(dest), { recursive: true })
|
||||
.then(() => fs.copyFile(resolve(file), dest));
|
||||
.then(() => fs.copyFile(resolve(file), dest, fs.constants.COPYFILE_FICLONE));
|
||||
})
|
||||
);
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue