Refactor to remove AstroConfig['_ctx'] (#4771)

* Refactor to remove AstroConfig['_ctx']

* Fix type error

* Export validateConfig

* Move to an options bag for createSettings

* Move config tests into test/untils/config

* Add a changeste

* fix build
This commit is contained in:
Matthew Phillips 2022-09-16 11:29:17 -04:00 committed by GitHub
parent 48567639e0
commit f3a81d82f6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
44 changed files with 776 additions and 744 deletions

View file

@ -0,0 +1,5 @@
---
'astro': patch
---
Internal refactor

View file

@ -11,7 +11,7 @@ import type * as babel from '@babel/core';
import type { AddressInfo } from 'net'; import type { AddressInfo } from 'net';
import type { TsConfigJson } from 'tsconfig-resolver'; import type { TsConfigJson } from 'tsconfig-resolver';
import type * as vite from 'vite'; import type * as vite from 'vite';
import { z } from 'zod'; import type { z } from 'zod';
import type { SerializedSSRManifest } from '../core/app/types'; import type { SerializedSSRManifest } from '../core/app/types';
import type { PageBuildData } from '../core/build/types'; import type { PageBuildData } from '../core/build/types';
import type { AstroConfigSchema } from '../core/config'; import type { AstroConfigSchema } from '../core/config';
@ -871,20 +871,21 @@ export interface AstroConfig extends z.output<typeof AstroConfigSchema> {
// This is a more detailed type than zod validation gives us. // This is a more detailed type than zod validation gives us.
// TypeScript still confirms zod validation matches this type. // TypeScript still confirms zod validation matches this type.
integrations: AstroIntegration[]; integrations: AstroIntegration[];
}
// Private: export interface AstroSettings {
// We have a need to pass context based on configured state, config: AstroConfig;
// that is different from the user-exposed configuration.
// TODO: Create an AstroConfig class to manage this, long-term. adapter: AstroAdapter | undefined;
_ctx: { injectedRoutes: InjectedRoute[];
tsConfig: TsConfigJson | undefined; pageExtensions: string[];
tsConfigPath: string | undefined; renderers: AstroRenderer[];
pageExtensions: string[]; scripts: {
injectedRoutes: InjectedRoute[]; stage: InjectedScriptStage;
adapter: AstroAdapter | undefined; content: string
renderers: AstroRenderer[]; }[];
scripts: { stage: InjectedScriptStage; content: string }[]; tsConfig: TsConfigJson | undefined;
}; tsConfigPath: string | undefined;
} }
export type AsyncRendererComponentFn<U> = ( export type AsyncRendererComponentFn<U> = (

View file

@ -1,6 +1,6 @@
/* eslint-disable no-console */ /* eslint-disable no-console */
import { AstroCheck, DiagnosticSeverity } from '@astrojs/language-server'; import { AstroCheck, DiagnosticSeverity } from '@astrojs/language-server';
import type { AstroConfig } from '../../@types/astro'; import type { AstroSettings } from '../../@types/astro';
import glob from 'fast-glob'; import glob from 'fast-glob';
import * as fs from 'fs'; import * as fs from 'fs';
@ -16,10 +16,10 @@ interface Result {
hints: number; hints: number;
} }
export async function check(astroConfig: AstroConfig) { export async function check(settings: AstroSettings) {
console.log(bold('astro check')); console.log(bold('astro check'));
const root = astroConfig.root; const root = settings.config.root;
const spinner = ora(` Getting diagnostics for Astro files in ${fileURLToPath(root)}`).start(); const spinner = ora(` Getting diagnostics for Astro files in ${fileURLToPath(root)}`).start();

View file

@ -7,7 +7,7 @@ import yargs from 'yargs-parser';
import { z } from 'zod'; import { z } from 'zod';
import add from '../core/add/index.js'; import add from '../core/add/index.js';
import build from '../core/build/index.js'; import build from '../core/build/index.js';
import { openConfig, resolveConfigPath, resolveFlags, resolveRoot } from '../core/config.js'; import { openConfig, resolveConfigPath, resolveFlags, resolveRoot, createSettings, loadTSConfig } from '../core/config/index.js';
import devServer from '../core/dev/index.js'; import devServer from '../core/dev/index.js';
import { collectErrorMetadata } from '../core/errors.js'; import { collectErrorMetadata } from '../core/errors.js';
import { debug, error, info, LogOptions } from '../core/logger/core.js'; import { debug, error, info, LogOptions } from '../core/logger/core.js';
@ -150,7 +150,7 @@ async function runCommand(cmd: string, flags: yargs.Arguments) {
} }
} }
let { astroConfig, userConfig } = await openConfig({ let { astroConfig: initialAstroConfig, userConfig: initialUserConfig } = await openConfig({
cwd: root, cwd: root,
flags, flags,
cmd, cmd,
@ -159,8 +159,14 @@ async function runCommand(cmd: string, flags: yargs.Arguments) {
await handleConfigError(e, { cwd: root, flags, logging }); await handleConfigError(e, { cwd: root, flags, logging });
return {} as any; return {} as any;
}); });
if (!astroConfig) return; if (!initialAstroConfig) return;
telemetry.record(event.eventCliSession(cmd, userConfig, flags)); telemetry.record(event.eventCliSession(cmd, initialUserConfig, flags));
let initialTsConfig = loadTSConfig(root);
let settings = createSettings({
config: initialAstroConfig,
tsConfig: initialTsConfig?.config,
tsConfigPath: initialTsConfig?.path,
});
// Common CLI Commands: // Common CLI Commands:
// These commands run normally. All commands are assumed to have been handled // These commands run normally. All commands are assumed to have been handled
@ -168,7 +174,7 @@ async function runCommand(cmd: string, flags: yargs.Arguments) {
switch (cmd) { switch (cmd) {
case 'dev': { case 'dev': {
async function startDevServer({ isRestart = false }: { isRestart?: boolean } = {}) { async function startDevServer({ isRestart = false }: { isRestart?: boolean } = {}) {
const { watcher, stop } = await devServer(astroConfig, { logging, telemetry, isRestart }); const { watcher, stop } = await devServer(settings, { logging, telemetry, isRestart });
let restartInFlight = false; let restartInFlight = false;
const configFlag = resolveFlags(flags).config; const configFlag = resolveFlags(flags).config;
const configFlagPath = configFlag const configFlagPath = configFlag
@ -199,7 +205,13 @@ async function runCommand(cmd: string, flags: yargs.Arguments) {
isConfigReload: true, isConfigReload: true,
}); });
info(logging, 'astro', logMsg + '\n'); info(logging, 'astro', logMsg + '\n');
astroConfig = newConfig.astroConfig; let astroConfig = newConfig.astroConfig;
let tsconfig = loadTSConfig(root);
settings = createSettings({
config: astroConfig,
tsConfig: tsconfig?.config,
tsConfigPath: tsconfig?.path
});
await stop(); await stop();
await startDevServer({ isRestart: true }); await startDevServer({ isRestart: true });
} catch (e) { } catch (e) {
@ -220,16 +232,16 @@ async function runCommand(cmd: string, flags: yargs.Arguments) {
} }
case 'build': { case 'build': {
return await build(astroConfig, { logging, telemetry }); return await build(settings, { logging, telemetry });
} }
case 'check': { case 'check': {
const ret = await check(astroConfig); const ret = await check(settings);
return process.exit(ret); return process.exit(ret);
} }
case 'preview': { case 'preview': {
const server = await preview(astroConfig, { logging, telemetry }); const server = await preview(settings, { logging, telemetry });
return await server.closed(); // keep alive until the server is closed return await server.closed(); // keep alive until the server is closed
} }
} }

View file

@ -10,7 +10,7 @@ import preferredPM from 'preferred-pm';
import prompts from 'prompts'; import prompts from 'prompts';
import { fileURLToPath, pathToFileURL } from 'url'; import { fileURLToPath, pathToFileURL } from 'url';
import type yargs from 'yargs-parser'; import type yargs from 'yargs-parser';
import { resolveConfigPath } from '../config.js'; import { resolveConfigPath } from '../config/index.js';
import { debug, info, LogOptions } from '../logger/core.js'; import { debug, info, LogOptions } from '../logger/core.js';
import * as msg from '../messages.js'; import * as msg from '../messages.js';
import { printHelp } from '../messages.js'; import { printHelp } from '../messages.js';

View file

@ -6,6 +6,7 @@ import type { OutputAsset, OutputChunk } from 'rollup';
import { fileURLToPath } from 'url'; import { fileURLToPath } from 'url';
import type { import type {
AstroConfig, AstroConfig,
AstroSettings,
ComponentInstance, ComponentInstance,
EndpointHandler, EndpointHandler,
RouteType, RouteType,
@ -62,10 +63,10 @@ function* throttle(max: number, inPaths: string[]) {
} }
} }
function shouldSkipDraft(pageModule: ComponentInstance, astroConfig: AstroConfig): boolean { function shouldSkipDraft(pageModule: ComponentInstance, settings: AstroSettings): boolean {
return ( return (
// Drafts are disabled // Drafts are disabled
!astroConfig.markdown.drafts && !settings.config.markdown.drafts &&
// This is a draft post // This is a draft post
'frontmatter' in pageModule && 'frontmatter' in pageModule &&
(pageModule as any).frontmatter?.draft === true (pageModule as any).frontmatter?.draft === true
@ -74,13 +75,13 @@ function shouldSkipDraft(pageModule: ComponentInstance, astroConfig: AstroConfig
// Gives back a facadeId that is relative to the root. // Gives back a facadeId that is relative to the root.
// ie, src/pages/index.astro instead of /Users/name..../src/pages/index.astro // ie, src/pages/index.astro instead of /Users/name..../src/pages/index.astro
export function rootRelativeFacadeId(facadeId: string, astroConfig: AstroConfig): string { export function rootRelativeFacadeId(facadeId: string, settings: AstroSettings): string {
return facadeId.slice(fileURLToPath(astroConfig.root).length); return facadeId.slice(fileURLToPath(settings.config.root).length);
} }
// Determines of a Rollup chunk is an entrypoint page. // Determines of a Rollup chunk is an entrypoint page.
export function chunkIsPage( export function chunkIsPage(
astroConfig: AstroConfig, settings: AstroSettings,
output: OutputAsset | OutputChunk, output: OutputAsset | OutputChunk,
internals: BuildInternals internals: BuildInternals
) { ) {
@ -90,7 +91,7 @@ export function chunkIsPage(
const chunk = output as OutputChunk; const chunk = output as OutputChunk;
if (chunk.facadeModuleId) { if (chunk.facadeModuleId) {
const facadeToEntryId = prependForwardSlash( const facadeToEntryId = prependForwardSlash(
rootRelativeFacadeId(chunk.facadeModuleId, astroConfig) rootRelativeFacadeId(chunk.facadeModuleId, settings)
); );
return internals.entrySpecifierToBundleMap.has(facadeToEntryId); return internals.entrySpecifierToBundleMap.has(facadeToEntryId);
} }
@ -101,9 +102,9 @@ export async function generatePages(opts: StaticBuildOptions, internals: BuildIn
const timer = performance.now(); const timer = performance.now();
info(opts.logging, null, `\n${bgGreen(black(' generating static routes '))}`); info(opts.logging, null, `\n${bgGreen(black(' generating static routes '))}`);
const ssr = opts.astroConfig.output === 'server'; const ssr = opts.settings.config.output === 'server';
const serverEntry = opts.buildConfig.serverEntry; const serverEntry = opts.buildConfig.serverEntry;
const outFolder = ssr ? opts.buildConfig.server : getOutDirWithinCwd(opts.astroConfig.outDir); const outFolder = ssr ? opts.buildConfig.server : getOutDirWithinCwd(opts.settings.config.outDir);
const ssrEntryURL = new URL('./' + serverEntry + `?time=${Date.now()}`, outFolder); const ssrEntryURL = new URL('./' + serverEntry + `?time=${Date.now()}`, outFolder);
const ssrEntry = await import(ssrEntryURL.toString()); const ssrEntry = await import(ssrEntryURL.toString());
const builtPaths = new Set<string>(); const builtPaths = new Set<string>();
@ -137,7 +138,7 @@ async function generatePage(
); );
} }
if (shouldSkipDraft(pageModule, opts.astroConfig)) { if (shouldSkipDraft(pageModule, opts.settings)) {
info(opts.logging, null, `${magenta('⚠️')} Skipping draft ${pageData.route.component}`); info(opts.logging, null, `${magenta('⚠️')} Skipping draft ${pageData.route.component}`);
return; return;
} }
@ -163,7 +164,7 @@ async function generatePage(
const timeEnd = performance.now(); const timeEnd = performance.now();
const timeChange = getTimeStat(timeStart, timeEnd); const timeChange = getTimeStat(timeStart, timeEnd);
const timeIncrease = `(+${timeChange})`; const timeIncrease = `(+${timeChange})`;
const filePath = getOutputFilename(opts.astroConfig, path, pageData.route.type); const filePath = getOutputFilename(opts.settings.config, path, pageData.route.type);
const lineIcon = i === paths.length - 1 ? '└─' : '├─'; const lineIcon = i === paths.length - 1 ? '└─' : '├─';
info(opts.logging, null, ` ${cyan(lineIcon)} ${dim(filePath)} ${dim(timeIncrease)}`); info(opts.logging, null, ` ${cyan(lineIcon)} ${dim(filePath)} ${dim(timeIncrease)}`);
} }
@ -186,7 +187,7 @@ async function getPathsForRoute(
route: pageData.route, route: pageData.route,
isValidate: false, isValidate: false,
logging: opts.logging, logging: opts.logging,
ssr: opts.astroConfig.output === 'server', ssr: opts.settings.config.output === 'server',
}) })
.then((_result) => { .then((_result) => {
const label = _result.staticPaths.length === 1 ? 'page' : 'pages'; const label = _result.staticPaths.length === 1 ? 'page' : 'pages';
@ -262,8 +263,8 @@ function shouldAppendForwardSlash(
} }
function addPageName(pathname: string, opts: StaticBuildOptions): void { function addPageName(pathname: string, opts: StaticBuildOptions): void {
const trailingSlash = opts.astroConfig.trailingSlash; const trailingSlash = opts.settings.config.trailingSlash;
const buildFormat = opts.astroConfig.build.format; const buildFormat = opts.settings.config.build.format;
const pageName = shouldAppendForwardSlash(trailingSlash, buildFormat) const pageName = shouldAppendForwardSlash(trailingSlash, buildFormat)
? pathname.replace(/\/?$/, '/').replace(/^\//, '') ? pathname.replace(/\/?$/, '/').replace(/^\//, '')
: pathname.replace(/^\//, ''); : pathname.replace(/^\//, '');
@ -303,7 +304,7 @@ async function generatePath(
opts: StaticBuildOptions, opts: StaticBuildOptions,
gopts: GeneratePathOptions gopts: GeneratePathOptions
) { ) {
const { astroConfig, logging, origin, routeCache } = opts; const { settings, logging, origin, routeCache } = opts;
const { mod, internals, linkIds, scripts: hoistedScripts, pageData, renderers } = gopts; const { mod, internals, linkIds, scripts: hoistedScripts, pageData, renderers } = gopts;
// This adds the page name to the array so it can be shown as part of stats. // This adds the page name to the array so it can be shown as part of stats.
@ -316,18 +317,18 @@ async function generatePath(
// If a base path was provided, append it to the site URL. This ensures that // If a base path was provided, append it to the site URL. This ensures that
// all injected scripts and links are referenced relative to the site and subpath. // all injected scripts and links are referenced relative to the site and subpath.
const site = const site =
astroConfig.base !== '/' settings.config.base !== '/'
? joinPaths(astroConfig.site?.toString() || 'http://localhost/', astroConfig.base) ? joinPaths(settings.config.site?.toString() || 'http://localhost/', settings.config.base)
: astroConfig.site; : settings.config.site;
const links = createLinkStylesheetElementSet(linkIds, site); const links = createLinkStylesheetElementSet(linkIds, site);
const scripts = createModuleScriptsSet(hoistedScripts ? [hoistedScripts] : [], site); const scripts = createModuleScriptsSet(hoistedScripts ? [hoistedScripts] : [], site);
if (astroConfig._ctx.scripts.some((script) => script.stage === 'page')) { if (settings.scripts.some((script) => script.stage === 'page')) {
const hashedFilePath = internals.entrySpecifierToBundleMap.get(PAGE_SCRIPT_ID); const hashedFilePath = internals.entrySpecifierToBundleMap.get(PAGE_SCRIPT_ID);
if (typeof hashedFilePath !== 'string') { if (typeof hashedFilePath !== 'string') {
throw new Error(`Cannot find the built path for ${PAGE_SCRIPT_ID}`); throw new Error(`Cannot find the built path for ${PAGE_SCRIPT_ID}`);
} }
const src = prependForwardSlash(npath.posix.join(astroConfig.base, hashedFilePath)); const src = prependForwardSlash(npath.posix.join(settings.config.base, hashedFilePath));
scripts.add({ scripts.add({
props: { type: 'module', src }, props: { type: 'module', src },
children: '', children: '',
@ -335,7 +336,7 @@ async function generatePath(
} }
// Add all injected scripts to the page. // Add all injected scripts to the page.
for (const script of astroConfig._ctx.scripts) { for (const script of settings.scripts) {
if (script.stage === 'head-inline') { if (script.stage === 'head-inline') {
scripts.add({ scripts.add({
props: {}, props: {},
@ -344,12 +345,12 @@ async function generatePath(
} }
} }
const ssr = opts.astroConfig.output === 'server'; const ssr = settings.config.output === 'server';
const url = getUrlForPath( const url = getUrlForPath(
pathname, pathname,
opts.astroConfig.base, opts.settings.config.base,
origin, origin,
opts.astroConfig.build.format, opts.settings.config.build.format,
pageData.route.type pageData.route.type
); );
const options: RenderOptions = { const options: RenderOptions = {
@ -357,8 +358,8 @@ async function generatePath(
links, links,
logging, logging,
markdown: { markdown: {
...astroConfig.markdown, ...settings.config.markdown,
isAstroFlavoredMd: astroConfig.legacy.astroFlavoredMarkdown, isAstroFlavoredMd: settings.config.legacy.astroFlavoredMarkdown,
}, },
mod, mod,
mode: opts.mode, mode: opts.mode,
@ -376,14 +377,14 @@ async function generatePath(
} }
throw new Error(`Cannot find the built path for ${specifier}`); throw new Error(`Cannot find the built path for ${specifier}`);
} }
return prependForwardSlash(npath.posix.join(astroConfig.base, hashedFilePath)); return prependForwardSlash(npath.posix.join(settings.config.base, hashedFilePath));
}, },
request: createRequest({ url, headers: new Headers(), logging, ssr }), request: createRequest({ url, headers: new Headers(), logging, ssr }),
route: pageData.route, route: pageData.route,
routeCache, routeCache,
site: astroConfig.site site: settings.config.site
? new URL(astroConfig.base, astroConfig.site).toString() ? new URL(settings.config.base, settings.config.site).toString()
: astroConfig.site, : settings.config.site,
ssr, ssr,
streaming: true, streaming: true,
}; };
@ -409,8 +410,8 @@ async function generatePath(
body = await response.text(); body = await response.text();
} }
const outFolder = getOutFolder(astroConfig, pathname, pageData.route.type); const outFolder = getOutFolder(settings.config, pathname, pageData.route.type);
const outFile = getOutFile(astroConfig, outFolder, pathname, pageData.route.type); const outFile = getOutFile(settings.config, outFolder, pathname, pageData.route.type);
pageData.route.distURL = outFile; pageData.route.distURL = outFile;
await fs.promises.mkdir(outFolder, { recursive: true }); await fs.promises.mkdir(outFolder, { recursive: true });
await fs.promises.writeFile(outFile, body, encoding ?? 'utf-8'); await fs.promises.writeFile(outFile, body, encoding ?? 'utf-8');

View file

@ -1,5 +1,5 @@
import type { AstroTelemetry } from '@astrojs/telemetry'; import type { AstroTelemetry } from '@astrojs/telemetry';
import type { AstroConfig, BuildConfig, ManifestData, RuntimeMode } from '../../@types/astro'; import type { AstroSettings, BuildConfig, ManifestData, RuntimeMode } from '../../@types/astro';
import type { LogOptions } from '../logger/core'; import type { LogOptions } from '../logger/core';
import fs from 'fs'; import fs from 'fs';
@ -28,14 +28,14 @@ export interface BuildOptions {
} }
/** `astro build` */ /** `astro build` */
export default async function build(config: AstroConfig, options: BuildOptions): Promise<void> { export default async function build(settings: AstroSettings, options: BuildOptions): Promise<void> {
applyPolyfill(); applyPolyfill();
const builder = new AstroBuilder(config, options); const builder = new AstroBuilder(settings, options);
await builder.run(); await builder.run();
} }
class AstroBuilder { class AstroBuilder {
private config: AstroConfig; private settings: AstroSettings;
private logging: LogOptions; private logging: LogOptions;
private mode: RuntimeMode = 'production'; private mode: RuntimeMode = 'production';
private origin: string; private origin: string;
@ -43,16 +43,16 @@ class AstroBuilder {
private manifest: ManifestData; private manifest: ManifestData;
private timer: Record<string, number>; private timer: Record<string, number>;
constructor(config: AstroConfig, options: BuildOptions) { constructor(settings: AstroSettings, options: BuildOptions) {
if (options.mode) { if (options.mode) {
this.mode = options.mode; this.mode = options.mode;
} }
this.config = config; this.settings = settings;
this.logging = options.logging; this.logging = options.logging;
this.routeCache = new RouteCache(this.logging); this.routeCache = new RouteCache(this.logging);
this.origin = config.site this.origin = settings.config.site
? new URL(config.site).origin ? new URL(settings.config.site).origin
: `http://localhost:${config.server.port}`; : `http://localhost:${settings.config.server.port}`;
this.manifest = { routes: [] }; this.manifest = { routes: [] };
this.timer = {}; this.timer = {};
} }
@ -62,8 +62,8 @@ class AstroBuilder {
debug('build', 'Initial setup...'); debug('build', 'Initial setup...');
const { logging } = this; const { logging } = this;
this.timer.init = performance.now(); this.timer.init = performance.now();
this.config = await runHookConfigSetup({ config: this.config, command: 'build', logging }); this.settings = await runHookConfigSetup({ settings: this.settings, command: 'build', logging });
this.manifest = createRouteManifest({ config: this.config }, this.logging); this.manifest = createRouteManifest({ settings: this.settings }, this.logging);
const viteConfig = await createVite( const viteConfig = await createVite(
{ {
@ -73,29 +73,29 @@ class AstroBuilder {
middlewareMode: true, middlewareMode: true,
}, },
}, },
{ astroConfig: this.config, logging, mode: 'build' } { settings: this.settings, logging, mode: 'build' }
); );
await runHookConfigDone({ config: this.config, logging }); await runHookConfigDone({ settings: this.settings, logging });
return { viteConfig }; return { viteConfig };
} }
/** Run the build logic. build() is marked private because usage should go through ".run()" */ /** Run the build logic. build() is marked private because usage should go through ".run()" */
private async build({ viteConfig }: { viteConfig: ViteConfigWithSSR }) { private async build({ viteConfig }: { viteConfig: ViteConfigWithSSR }) {
const buildConfig: BuildConfig = { const buildConfig: BuildConfig = {
client: new URL('./client/', this.config.outDir), client: new URL('./client/', this.settings.config.outDir),
server: new URL('./server/', this.config.outDir), server: new URL('./server/', this.settings.config.outDir),
serverEntry: 'entry.mjs', serverEntry: 'entry.mjs',
}; };
await runHookBuildStart({ config: this.config, buildConfig, logging: this.logging }); await runHookBuildStart({ config: this.settings.config, buildConfig, logging: this.logging });
info(this.logging, 'build', `output target: ${colors.green(this.config.output)}`); info(this.logging, 'build', `output target: ${colors.green(this.settings.config.output)}`);
if (this.config._ctx.adapter) { if (this.settings.adapter) {
info(this.logging, 'build', `deploy adapter: ${colors.green(this.config._ctx.adapter.name)}`); info(this.logging, 'build', `deploy adapter: ${colors.green(this.settings.adapter.name)}`);
} }
info(this.logging, 'build', 'Collecting build info...'); info(this.logging, 'build', 'Collecting build info...');
this.timer.loadStart = performance.now(); this.timer.loadStart = performance.now();
const { assets, allPages } = await collectPagesData({ const { assets, allPages } = await collectPagesData({
astroConfig: this.config, settings: this.settings,
logging: this.logging, logging: this.logging,
manifest: this.manifest, manifest: this.manifest,
}); });
@ -116,7 +116,7 @@ class AstroBuilder {
await staticBuild({ await staticBuild({
allPages, allPages,
astroConfig: this.config, settings: this.settings,
logging: this.logging, logging: this.logging,
manifest: this.manifest, manifest: this.manifest,
mode: this.mode, mode: this.mode,
@ -140,7 +140,7 @@ class AstroBuilder {
// You're done! Time to clean up. // You're done! Time to clean up.
await runHookBuildDone({ await runHookBuildDone({
config: this.config, config: this.settings.config,
buildConfig, buildConfig,
pages: pageNames, pages: pageNames,
routes: Object.values(allPages).map((pd) => pd.route), routes: Object.values(allPages).map((pd) => pd.route),
@ -152,7 +152,7 @@ class AstroBuilder {
logging: this.logging, logging: this.logging,
timeStart: this.timer.init, timeStart: this.timer.init,
pageCount: pageNames.length, pageCount: pageNames.length,
buildMode: this.config.output, buildMode: this.settings.config.output,
}); });
} }
} }

View file

@ -1,4 +1,4 @@
import type { AstroConfig, ManifestData } from '../../@types/astro'; import type { AstroSettings, ManifestData } from '../../@types/astro';
import type { LogOptions } from '../logger/core'; import type { LogOptions } from '../logger/core';
import { info } from '../logger/core.js'; import { info } from '../logger/core.js';
import type { AllPagesData } from './types'; import type { AllPagesData } from './types';
@ -7,7 +7,7 @@ import * as colors from 'kleur/colors';
import { debug } from '../logger/core.js'; import { debug } from '../logger/core.js';
export interface CollectPagesDataOptions { export interface CollectPagesDataOptions {
astroConfig: AstroConfig; settings: AstroSettings;
logging: LogOptions; logging: LogOptions;
manifest: ManifestData; manifest: ManifestData;
} }
@ -21,7 +21,7 @@ export interface CollectPagesDataResult {
export async function collectPagesData( export async function collectPagesData(
opts: CollectPagesDataOptions opts: CollectPagesDataOptions
): Promise<CollectPagesDataResult> { ): Promise<CollectPagesDataResult> {
const { astroConfig, manifest } = opts; const { settings, manifest } = opts;
const assets: Record<string, string> = {}; const assets: Record<string, string> = {};
const allPages: AllPagesData = {}; const allPages: AllPagesData = {};
@ -58,7 +58,7 @@ export async function collectPagesData(
}; };
clearInterval(routeCollectionLogTimeout); clearInterval(routeCollectionLogTimeout);
if (astroConfig.output === 'static') { if (settings.config.output === 'static') {
const html = `${route.pathname}`.replace(/\/?$/, '/index.html'); const html = `${route.pathname}`.replace(/\/?$/, '/index.html');
debug( debug(
'build', 'build',

View file

@ -23,10 +23,10 @@ import { vitePluginPages } from './vite-plugin-pages.js';
import { injectManifest, vitePluginSSR } from './vite-plugin-ssr.js'; import { injectManifest, vitePluginSSR } from './vite-plugin-ssr.js';
export async function staticBuild(opts: StaticBuildOptions) { export async function staticBuild(opts: StaticBuildOptions) {
const { allPages, astroConfig } = opts; const { allPages, settings } = opts;
// Verify this app is buildable. // Verify this app is buildable.
if (isModeServerWithNoAdapter(opts.astroConfig)) { if (isModeServerWithNoAdapter(opts.settings)) {
throw new Error(`Cannot use \`output: 'server'\` without an adapter. throw new Error(`Cannot use \`output: 'server'\` without an adapter.
Install and configure the appropriate server adapter for your final deployment. Install and configure the appropriate server adapter for your final deployment.
Learn more: https://docs.astro.build/en/guides/server-side-rendering/ Learn more: https://docs.astro.build/en/guides/server-side-rendering/
@ -55,7 +55,7 @@ Learn more: https://docs.astro.build/en/guides/server-side-rendering/
timer.buildStart = performance.now(); timer.buildStart = performance.now();
for (const [component, pageData] of Object.entries(allPages)) { for (const [component, pageData] of Object.entries(allPages)) {
const astroModuleURL = new URL('./' + component, astroConfig.root); const astroModuleURL = new URL('./' + component, settings.config.root);
const astroModuleId = prependForwardSlash(component); const astroModuleId = prependForwardSlash(component);
// Track the page data in internals // Track the page data in internals
@ -68,15 +68,15 @@ Learn more: https://docs.astro.build/en/guides/server-side-rendering/
// Empty out the dist folder, if needed. Vite has a config for doing this // Empty out the dist folder, if needed. Vite has a config for doing this
// but because we are running 2 vite builds in parallel, that would cause a race // but because we are running 2 vite builds in parallel, that would cause a race
// condition, so we are doing it ourselves // condition, so we are doing it ourselves
emptyDir(astroConfig.outDir, new Set('.git')); emptyDir(settings.config.outDir, new Set('.git'));
// Build your project (SSR application code, assets, client JS, etc.) // Build your project (SSR application code, assets, client JS, etc.)
timer.ssr = performance.now(); timer.ssr = performance.now();
info(opts.logging, 'build', `Building ${astroConfig.output} entrypoints...`); info(opts.logging, 'build', `Building ${settings.config.output} entrypoints...`);
await ssrBuild(opts, internals, pageInput); await ssrBuild(opts, internals, pageInput);
info(opts.logging, 'build', dim(`Completed in ${getTimeStat(timer.ssr, performance.now())}.`)); info(opts.logging, 'build', dim(`Completed in ${getTimeStat(timer.ssr, performance.now())}.`));
const rendererClientEntrypoints = opts.astroConfig._ctx.renderers const rendererClientEntrypoints = settings.renderers
.map((r) => r.clientEntrypoint) .map((r) => r.clientEntrypoint)
.filter((a) => typeof a === 'string') as string[]; .filter((a) => typeof a === 'string') as string[];
@ -87,7 +87,7 @@ Learn more: https://docs.astro.build/en/guides/server-side-rendering/
...internals.discoveredScripts, ...internals.discoveredScripts,
]); ]);
if (astroConfig._ctx.scripts.some((script) => script.stage === 'page')) { if (settings.scripts.some((script) => script.stage === 'page')) {
clientInput.add(PAGE_SCRIPT_ID); clientInput.add(PAGE_SCRIPT_ID);
} }
@ -96,7 +96,7 @@ Learn more: https://docs.astro.build/en/guides/server-side-rendering/
await clientBuild(opts, internals, clientInput); await clientBuild(opts, internals, clientInput);
timer.generate = performance.now(); timer.generate = performance.now();
if (astroConfig.output === 'static') { if (settings.config.output === 'static') {
await generatePages(opts, internals); await generatePages(opts, internals);
await cleanSsrOutput(opts); await cleanSsrOutput(opts);
} else { } else {
@ -109,9 +109,9 @@ Learn more: https://docs.astro.build/en/guides/server-side-rendering/
} }
async function ssrBuild(opts: StaticBuildOptions, internals: BuildInternals, input: Set<string>) { async function ssrBuild(opts: StaticBuildOptions, internals: BuildInternals, input: Set<string>) {
const { astroConfig, viteConfig } = opts; const { settings, viteConfig } = opts;
const ssr = astroConfig.output === 'server'; const ssr = settings.config.output === 'server';
const out = ssr ? opts.buildConfig.server : getOutDirWithinCwd(astroConfig.outDir); const out = ssr ? opts.buildConfig.server : getOutDirWithinCwd(settings.config.outDir);
const viteBuildConfig: ViteConfigWithSSR = { const viteBuildConfig: ViteConfigWithSSR = {
...viteConfig, ...viteConfig,
@ -148,21 +148,20 @@ async function ssrBuild(opts: StaticBuildOptions, internals: BuildInternals, inp
buildOptions: opts, buildOptions: opts,
internals, internals,
target: 'server', target: 'server',
astroConfig,
}), }),
...(viteConfig.plugins || []), ...(viteConfig.plugins || []),
// SSR needs to be last // SSR needs to be last
opts.astroConfig.output === 'server' && settings.config.output === 'server' &&
vitePluginSSR(internals, opts.astroConfig._ctx.adapter!), vitePluginSSR(internals, settings.adapter!),
vitePluginAnalyzer(internals), vitePluginAnalyzer(internals),
], ],
publicDir: ssr ? false : viteConfig.publicDir, publicDir: ssr ? false : viteConfig.publicDir,
envPrefix: 'PUBLIC_', envPrefix: 'PUBLIC_',
base: astroConfig.base, base: settings.config.base,
}; };
await runHookBuildSetup({ await runHookBuildSetup({
config: astroConfig, config: settings.config,
pages: internals.pagesByComponent, pages: internals.pagesByComponent,
vite: viteBuildConfig, vite: viteBuildConfig,
target: 'server', target: 'server',
@ -177,16 +176,16 @@ async function clientBuild(
internals: BuildInternals, internals: BuildInternals,
input: Set<string> input: Set<string>
) { ) {
const { astroConfig, viteConfig } = opts; const { settings, viteConfig } = opts;
const timer = performance.now(); const timer = performance.now();
const ssr = astroConfig.output === 'server'; const ssr = settings.config.output === 'server';
const out = ssr ? opts.buildConfig.client : astroConfig.outDir; const out = ssr ? opts.buildConfig.client : settings.config.outDir;
// Nothing to do if there is no client-side JS. // Nothing to do if there is no client-side JS.
if (!input.size) { if (!input.size) {
// If SSR, copy public over // If SSR, copy public over
if (ssr) { if (ssr) {
await copyFiles(astroConfig.publicDir, out); await copyFiles(settings.config.publicDir, out);
} }
return null; return null;
@ -219,21 +218,20 @@ async function clientBuild(
}, },
plugins: [ plugins: [
vitePluginInternals(input, internals), vitePluginInternals(input, internals),
vitePluginHoistedScripts(astroConfig, internals), vitePluginHoistedScripts(settings, internals),
rollupPluginAstroBuildCSS({ rollupPluginAstroBuildCSS({
buildOptions: opts, buildOptions: opts,
internals, internals,
target: 'client', target: 'client',
astroConfig,
}), }),
...(viteConfig.plugins || []), ...(viteConfig.plugins || []),
], ],
envPrefix: 'PUBLIC_', envPrefix: 'PUBLIC_',
base: astroConfig.base, base: settings.config.base,
} as ViteConfigWithSSR; } as ViteConfigWithSSR;
await runHookBuildSetup({ await runHookBuildSetup({
config: astroConfig, config: settings.config,
pages: internals.pagesByComponent, pages: internals.pagesByComponent,
vite: viteBuildConfig, vite: viteBuildConfig,
target: 'client', target: 'client',
@ -246,9 +244,9 @@ async function clientBuild(
} }
async function cleanSsrOutput(opts: StaticBuildOptions) { async function cleanSsrOutput(opts: StaticBuildOptions) {
const out = getOutDirWithinCwd(opts.astroConfig.outDir); const out = getOutDirWithinCwd(opts.settings.config.outDir);
// Clean out directly if the outDir is outside of root // Clean out directly if the outDir is outside of root
if (out.toString() !== opts.astroConfig.outDir.toString()) { if (out.toString() !== opts.settings.config.outDir.toString()) {
await fs.promises.rm(out, { recursive: true }); await fs.promises.rm(out, { recursive: true });
return; return;
} }
@ -284,7 +282,7 @@ async function copyFiles(fromFolder: URL, toFolder: URL) {
async function ssrMoveAssets(opts: StaticBuildOptions) { async function ssrMoveAssets(opts: StaticBuildOptions) {
info(opts.logging, 'build', 'Rearranging server assets...'); info(opts.logging, 'build', 'Rearranging server assets...');
const serverRoot = const serverRoot =
opts.astroConfig.output === 'static' ? opts.buildConfig.client : opts.buildConfig.server; opts.settings.config.output === 'static' ? opts.buildConfig.client : opts.buildConfig.server;
const clientRoot = opts.buildConfig.client; const clientRoot = opts.buildConfig.client;
const serverAssets = new URL('./assets/', serverRoot); const serverAssets = new URL('./assets/', serverRoot);
const clientAssets = new URL('./assets/', clientRoot); const clientAssets = new URL('./assets/', clientRoot);

View file

@ -1,5 +1,5 @@
import type { import type {
AstroConfig, AstroSettings,
BuildConfig, BuildConfig,
ComponentInstance, ComponentInstance,
ManifestData, ManifestData,
@ -26,7 +26,7 @@ export type AllPagesData = Record<ComponentPath, PageBuildData>;
/** Options for the static build */ /** Options for the static build */
export interface StaticBuildOptions { export interface StaticBuildOptions {
allPages: AllPagesData; allPages: AllPagesData;
astroConfig: AstroConfig; settings: AstroSettings;
buildConfig: BuildConfig; buildConfig: BuildConfig;
logging: LogOptions; logging: LogOptions;
manifest: ManifestData; manifest: ManifestData;

View file

@ -22,7 +22,6 @@ interface PluginOptions {
internals: BuildInternals; internals: BuildInternals;
buildOptions: StaticBuildOptions; buildOptions: StaticBuildOptions;
target: 'client' | 'server'; target: 'client' | 'server';
astroConfig: AstroConfig;
} }
// Arbitrary magic number, can change. // Arbitrary magic number, can change.
@ -30,13 +29,13 @@ const MAX_NAME_LENGTH = 70;
export function rollupPluginAstroBuildCSS(options: PluginOptions): VitePlugin[] { export function rollupPluginAstroBuildCSS(options: PluginOptions): VitePlugin[] {
const { internals, buildOptions } = options; const { internals, buildOptions } = options;
const { astroConfig } = buildOptions; const { settings } = buildOptions;
let resolvedConfig: ResolvedConfig; let resolvedConfig: ResolvedConfig;
// Turn a page location into a name to be used for the CSS file. // Turn a page location into a name to be used for the CSS file.
function nameifyPage(id: string) { function nameifyPage(id: string) {
let rel = relativeToSrcDir(astroConfig, id); let rel = relativeToSrcDir(settings.config, id);
// Remove pages, ex. blog/posts/something.astro // Remove pages, ex. blog/posts/something.astro
if (rel.startsWith('pages/')) { if (rel.startsWith('pages/')) {
rel = rel.slice(6); rel = rel.slice(6);
@ -240,7 +239,7 @@ export function rollupPluginAstroBuildCSS(options: PluginOptions): VitePlugin[]
for (const [, output] of Object.entries(bundle)) { for (const [, output] of Object.entries(bundle)) {
if (output.type === 'asset') { if (output.type === 'asset') {
if (output.name?.endsWith('.css') && typeof output.source === 'string') { if (output.name?.endsWith('.css') && typeof output.source === 'string') {
const cssTarget = options.astroConfig.vite.build?.cssTarget; const cssTarget = settings.config.vite.build?.cssTarget;
const { code: minifiedCSS } = await esbuild.transform(output.source, { const { code: minifiedCSS } = await esbuild.transform(output.source, {
loader: 'css', loader: 'css',
minify: true, minify: true,

View file

@ -1,5 +1,5 @@
import type { Plugin as VitePlugin } from 'vite'; import type { Plugin as VitePlugin } from 'vite';
import type { AstroConfig } from '../../@types/astro'; import type { AstroSettings } from '../../@types/astro';
import type { BuildInternals } from '../../core/build/internal.js'; import type { BuildInternals } from '../../core/build/internal.js';
import { viteID } from '../util.js'; import { viteID } from '../util.js';
import { getPageDataByViteID } from './internal.js'; import { getPageDataByViteID } from './internal.js';
@ -9,7 +9,7 @@ function virtualHoistedEntry(id: string) {
} }
export function vitePluginHoistedScripts( export function vitePluginHoistedScripts(
astroConfig: AstroConfig, settings: AstroSettings,
internals: BuildInternals internals: BuildInternals
): VitePlugin { ): VitePlugin {
return { return {
@ -40,7 +40,7 @@ export function vitePluginHoistedScripts(
}, },
async generateBundle(_options, bundle) { async generateBundle(_options, bundle) {
let assetInlineLimit = astroConfig.vite?.build?.assetsInlineLimit || 4096; let assetInlineLimit = settings.config.vite?.build?.assetsInlineLimit || 4096;
// Find all page entry points and create a map of the entry point to the hashed hoisted script. // Find all page entry points and create a map of the entry point to the hashed hoisted script.
// This is used when we render so that we can add the script to the head. // This is used when we render so that we can add the script to the head.
@ -58,7 +58,7 @@ export function vitePluginHoistedScripts(
const facadeId = output.facadeModuleId!; const facadeId = output.facadeModuleId!;
const pages = internals.hoistedScriptIdToPagesMap.get(facadeId)!; const pages = internals.hoistedScriptIdToPagesMap.get(facadeId)!;
for (const pathname of pages) { for (const pathname of pages) {
const vid = viteID(new URL('.' + pathname, astroConfig.root)); const vid = viteID(new URL('.' + pathname, settings.config.root));
const pageInfo = getPageDataByViteID(internals, vid); const pageInfo = getPageDataByViteID(internals, vid);
if (pageInfo) { if (pageInfo) {
if (canBeInlined) { if (canBeInlined) {

View file

@ -10,7 +10,7 @@ export function vitePluginPages(opts: StaticBuildOptions, internals: BuildIntern
name: '@astro/plugin-build-pages', name: '@astro/plugin-build-pages',
options(options) { options(options) {
if (opts.astroConfig.output === 'static') { if (opts.settings.config.output === 'static') {
return addRollupInput(options, [pagesVirtualModuleId]); return addRollupInput(options, [pagesVirtualModuleId]);
} }
}, },
@ -35,7 +35,7 @@ export function vitePluginPages(opts: StaticBuildOptions, internals: BuildIntern
i = 0; i = 0;
let rendererItems = ''; let rendererItems = '';
for (const renderer of opts.astroConfig._ctx.renderers) { for (const renderer of opts.settings.renderers) {
const variable = `_renderer${i}`; const variable = `_renderer${i}`;
// Use unshift so that renderers are imported before user code, in case they set globals // Use unshift so that renderers are imported before user code, in case they set globals
// that user code depends on. // that user code depends on.

View file

@ -103,7 +103,7 @@ export async function injectManifest(buildOpts: StaticBuildOptions, internals: B
const staticFiles = internals.staticFiles; const staticFiles = internals.staticFiles;
const manifest = buildManifest(buildOpts, internals, Array.from(staticFiles)); const manifest = buildManifest(buildOpts, internals, Array.from(staticFiles));
await runHookBuildSsr({ config: buildOpts.astroConfig, manifest, logging: buildOpts.logging }); await runHookBuildSsr({ config: buildOpts.settings.config, manifest, logging: buildOpts.logging });
const chunk = internals.ssrEntryChunk; const chunk = internals.ssrEntryChunk;
const code = chunk.code; const code = chunk.code;
@ -120,11 +120,11 @@ function buildManifest(
internals: BuildInternals, internals: BuildInternals,
staticFiles: string[] staticFiles: string[]
): SerializedSSRManifest { ): SerializedSSRManifest {
const { astroConfig } = opts; const { settings } = opts;
const routes: SerializedRouteInfo[] = []; const routes: SerializedRouteInfo[] = [];
const entryModules = Object.fromEntries(internals.entrySpecifierToBundleMap.entries()); const entryModules = Object.fromEntries(internals.entrySpecifierToBundleMap.entries());
if (astroConfig._ctx.scripts.some((script) => script.stage === 'page')) { if (settings.scripts.some((script) => script.stage === 'page')) {
staticFiles.push(entryModules[PAGE_SCRIPT_ID]); staticFiles.push(entryModules[PAGE_SCRIPT_ID]);
} }
@ -133,7 +133,7 @@ function buildManifest(
if (pageData.hoistedScript) { if (pageData.hoistedScript) {
scripts.unshift(pageData.hoistedScript); scripts.unshift(pageData.hoistedScript);
} }
if (astroConfig._ctx.scripts.some((script) => script.stage === 'page')) { if (settings.scripts.some((script) => script.stage === 'page')) {
scripts.push({ type: 'external', value: entryModules[PAGE_SCRIPT_ID] }); scripts.push({ type: 'external', value: entryModules[PAGE_SCRIPT_ID] });
} }
@ -142,11 +142,11 @@ function buildManifest(
links: sortedCSS(pageData), links: sortedCSS(pageData),
scripts: [ scripts: [
...scripts, ...scripts,
...astroConfig._ctx.scripts ...settings.scripts
.filter((script) => script.stage === 'head-inline') .filter((script) => script.stage === 'head-inline')
.map(({ stage, content }) => ({ stage, children: content })), .map(({ stage, content }) => ({ stage, children: content })),
], ],
routeData: serializeRouteData(pageData.route, astroConfig.trailingSlash), routeData: serializeRouteData(pageData.route, settings.config.trailingSlash),
}); });
} }
@ -157,13 +157,13 @@ function buildManifest(
} }
const ssrManifest: SerializedSSRManifest = { const ssrManifest: SerializedSSRManifest = {
adapterName: opts.astroConfig._ctx.adapter!.name, adapterName: opts.settings.adapter!.name,
routes, routes,
site: astroConfig.site, site: settings.config.site,
base: astroConfig.base, base: settings.config.base,
markdown: { markdown: {
...astroConfig.markdown, ...settings.config.markdown,
isAstroFlavoredMd: astroConfig.legacy.astroFlavoredMarkdown, isAstroFlavoredMd: settings.config.legacy.astroFlavoredMarkdown,
}, },
pageMap: null as any, pageMap: null as any,
renderers: [], renderers: [],

View file

@ -1,90 +1,20 @@
import type { RehypePlugin, RemarkPlugin, RemarkRehype } from '@astrojs/markdown-remark';
import fs from 'fs';
import type * as Postcss from 'postcss';
import type { ILanguageRegistration, IThemeRegistration, Theme } from 'shiki';
import type { Arguments as Flags } from 'yargs-parser'; import type { Arguments as Flags } from 'yargs-parser';
import type { AstroConfig, AstroUserConfig, CLIFlags, ViteUserConfig } from '../@types/astro'; import type { AstroConfig, AstroUserConfig, CLIFlags } from '../../@types/astro';
import fs from 'fs';
import load, { ProloadError, resolve } from '@proload/core'; import load, { ProloadError, resolve } from '@proload/core';
import loadTypeScript from '@proload/plugin-tsm'; import loadTypeScript from '@proload/plugin-tsm';
import * as colors from 'kleur/colors'; import * as colors from 'kleur/colors';
import path from 'path'; import path from 'path';
import postcssrc from 'postcss-load-config';
import { BUNDLED_THEMES } from 'shiki';
import * as tsr from 'tsconfig-resolver';
import { fileURLToPath, pathToFileURL } from 'url'; import { fileURLToPath, pathToFileURL } from 'url';
import * as vite from 'vite'; import * as vite from 'vite';
import { mergeConfig as mergeViteConfig } from 'vite'; import { mergeConfig as mergeViteConfig } from 'vite';
import { z } from 'zod'; import { LogOptions } from '../logger/core.js';
import jsxRenderer from '../jsx/renderer.js'; import { arraify, isObject } from '../util.js';
import { LogOptions } from './logger/core.js'; import { createRelativeSchema } from './schema.js';
import { appendForwardSlash, prependForwardSlash, trimSlashes } from './path.js';
import { arraify, isObject } from './util.js';
load.use([loadTypeScript]); load.use([loadTypeScript]);
interface PostCSSConfigResult {
options: Postcss.ProcessOptions;
plugins: Postcss.Plugin[];
}
const ASTRO_CONFIG_DEFAULTS: AstroUserConfig & any = {
root: '.',
srcDir: './src',
publicDir: './public',
outDir: './dist',
base: '/',
trailingSlash: 'ignore',
build: { format: 'directory' },
server: {
host: false,
port: 3000,
streaming: true,
},
style: { postcss: { options: {}, plugins: [] } },
integrations: [],
markdown: {
drafts: false,
syntaxHighlight: 'shiki',
shikiConfig: {
langs: [],
theme: 'github-dark',
wrap: false,
},
remarkPlugins: [],
rehypePlugins: [],
remarkRehype: {},
},
vite: {},
legacy: {
astroFlavoredMarkdown: false,
},
};
async function resolvePostcssConfig(inlineOptions: any, root: URL): Promise<PostCSSConfigResult> {
if (isObject(inlineOptions)) {
const options = { ...inlineOptions };
delete options.plugins;
return {
options,
plugins: inlineOptions.plugins || [],
};
}
const searchPath = typeof inlineOptions === 'string' ? inlineOptions : fileURLToPath(root);
try {
// @ts-ignore
return await postcssrc({}, searchPath);
} catch (err: any) {
if (!/No PostCSS Config found/.test(err.message)) {
throw err;
}
return {
options: {},
plugins: [],
};
}
}
export const LEGACY_ASTRO_CONFIG_KEYS = new Set([ export const LEGACY_ASTRO_CONFIG_KEYS = new Set([
'projectRoot', 'projectRoot',
'src', 'src',
@ -97,146 +27,6 @@ export const LEGACY_ASTRO_CONFIG_KEYS = new Set([
'devOptions', 'devOptions',
]); ]);
export const AstroConfigSchema = z.object({
root: z
.string()
.optional()
.default(ASTRO_CONFIG_DEFAULTS.root)
.transform((val) => new URL(val)),
srcDir: z
.string()
.optional()
.default(ASTRO_CONFIG_DEFAULTS.srcDir)
.transform((val) => new URL(val)),
publicDir: z
.string()
.optional()
.default(ASTRO_CONFIG_DEFAULTS.publicDir)
.transform((val) => new URL(val)),
outDir: z
.string()
.optional()
.default(ASTRO_CONFIG_DEFAULTS.outDir)
.transform((val) => new URL(val)),
site: z
.string()
.url()
.optional()
.transform((val) => (val ? appendForwardSlash(val) : val)),
base: z
.string()
.optional()
.default(ASTRO_CONFIG_DEFAULTS.base)
.transform((val) => prependForwardSlash(appendForwardSlash(trimSlashes(val)))),
trailingSlash: z
.union([z.literal('always'), z.literal('never'), z.literal('ignore')])
.optional()
.default(ASTRO_CONFIG_DEFAULTS.trailingSlash),
output: z
.union([z.literal('static'), z.literal('server')])
.optional()
.default('static'),
adapter: z.object({ name: z.string(), hooks: z.object({}).passthrough().default({}) }).optional(),
integrations: z.preprocess(
// preprocess
(val) => (Array.isArray(val) ? val.flat(Infinity).filter(Boolean) : val),
// validate
z
.array(z.object({ name: z.string(), hooks: z.object({}).passthrough().default({}) }))
.default(ASTRO_CONFIG_DEFAULTS.integrations)
),
build: z
.object({
format: z
.union([z.literal('file'), z.literal('directory')])
.optional()
.default(ASTRO_CONFIG_DEFAULTS.build.format),
})
.optional()
.default({}),
server: z.preprocess(
// preprocess
// NOTE: Uses the "error" command here because this is overwritten by the
// individualized schema parser with the correct command.
(val) => (typeof val === 'function' ? val({ command: 'error' }) : val),
// validate
z
.object({
host: z
.union([z.string(), z.boolean()])
.optional()
.default(ASTRO_CONFIG_DEFAULTS.server.host),
port: z.number().optional().default(ASTRO_CONFIG_DEFAULTS.server.port),
})
.optional()
.default({})
),
style: z
.object({
postcss: z
.object({
options: z.any(),
plugins: z.array(z.any()),
})
.optional()
.default(ASTRO_CONFIG_DEFAULTS.style.postcss),
})
.optional()
.default({}),
markdown: z
.object({
drafts: z.boolean().default(false),
syntaxHighlight: z
.union([z.literal('shiki'), z.literal('prism'), z.literal(false)])
.default(ASTRO_CONFIG_DEFAULTS.markdown.syntaxHighlight),
shikiConfig: z
.object({
langs: z.custom<ILanguageRegistration>().array().default([]),
theme: z
.enum(BUNDLED_THEMES as [Theme, ...Theme[]])
.or(z.custom<IThemeRegistration>())
.default(ASTRO_CONFIG_DEFAULTS.markdown.shikiConfig.theme),
wrap: z.boolean().or(z.null()).default(ASTRO_CONFIG_DEFAULTS.markdown.shikiConfig.wrap),
})
.default({}),
remarkPlugins: z
.union([
z.string(),
z.tuple([z.string(), z.any()]),
z.custom<RemarkPlugin>((data) => typeof data === 'function'),
z.tuple([z.custom<RemarkPlugin>((data) => typeof data === 'function'), z.any()]),
])
.array()
.default(ASTRO_CONFIG_DEFAULTS.markdown.remarkPlugins),
rehypePlugins: z
.union([
z.string(),
z.tuple([z.string(), z.any()]),
z.custom<RehypePlugin>((data) => typeof data === 'function'),
z.tuple([z.custom<RehypePlugin>((data) => typeof data === 'function'), z.any()]),
])
.array()
.default(ASTRO_CONFIG_DEFAULTS.markdown.rehypePlugins),
remarkRehype: z
.custom<RemarkRehype>((data) => data instanceof Object && !Array.isArray(data))
.optional()
.default(ASTRO_CONFIG_DEFAULTS.markdown.remarkRehype),
extendDefaultPlugins: z.boolean().default(false),
})
.default({}),
vite: z
.custom<ViteUserConfig>((data) => data instanceof Object && !Array.isArray(data))
.default(ASTRO_CONFIG_DEFAULTS.vite),
legacy: z
.object({
astroFlavoredMarkdown: z
.boolean()
.optional()
.default(ASTRO_CONFIG_DEFAULTS.legacy.astroFlavoredMarkdown),
})
.optional()
.default({}),
});
/** Turn raw config values into normalized values */ /** Turn raw config values into normalized values */
export async function validateConfig( export async function validateConfig(
@ -294,72 +84,10 @@ export async function validateConfig(
} }
/* eslint-enable no-console */ /* eslint-enable no-console */
// We need to extend the global schema to add transforms that are relative to root. const AstroConfigRelativeSchema = createRelativeSchema(cmd, fileProtocolRoot);
// This is type checked against the global schema to make sure we still match.
const AstroConfigRelativeSchema = AstroConfigSchema.extend({
root: z
.string()
.default(ASTRO_CONFIG_DEFAULTS.root)
.transform((val) => new URL(appendForwardSlash(val), fileProtocolRoot)),
srcDir: z
.string()
.default(ASTRO_CONFIG_DEFAULTS.srcDir)
.transform((val) => new URL(appendForwardSlash(val), fileProtocolRoot)),
publicDir: z
.string()
.default(ASTRO_CONFIG_DEFAULTS.publicDir)
.transform((val) => new URL(appendForwardSlash(val), fileProtocolRoot)),
outDir: z
.string()
.default(ASTRO_CONFIG_DEFAULTS.outDir)
.transform((val) => new URL(appendForwardSlash(val), fileProtocolRoot)),
server: z.preprocess(
// preprocess
(val) =>
typeof val === 'function' ? val({ command: cmd === 'dev' ? 'dev' : 'preview' }) : val,
// validate
z
.object({
host: z
.union([z.string(), z.boolean()])
.optional()
.default(ASTRO_CONFIG_DEFAULTS.server.host),
port: z.number().optional().default(ASTRO_CONFIG_DEFAULTS.server.port),
streaming: z.boolean().optional().default(true),
})
.optional()
.default({})
),
style: z
.object({
postcss: z.preprocess(
(val) => resolvePostcssConfig(val, fileProtocolRoot),
z
.object({
options: z.any(),
plugins: z.array(z.any()),
})
.optional()
.default(ASTRO_CONFIG_DEFAULTS.style.postcss)
),
})
.optional()
.default({}),
});
const tsconfig = loadTSConfig(root);
// First-Pass Validation // First-Pass Validation
const result = { const result = await AstroConfigRelativeSchema.parseAsync(userConfig);
...(await AstroConfigRelativeSchema.parseAsync(userConfig)),
_ctx: {
pageExtensions: ['.astro', '.md', '.html'],
tsConfig: tsconfig?.config,
tsConfigPath: tsconfig?.path,
scripts: [],
renderers: [jsxRenderer],
injectedRoutes: [],
adapter: undefined,
},
};
// If successful, return the result as a verified AstroConfig object. // If successful, return the result as a verified AstroConfig object.
return result; return result;
@ -554,16 +282,6 @@ async function tryLoadConfig(
} }
} }
function loadTSConfig(cwd: string | undefined): tsr.TsConfigResult | undefined {
for (const searchName of ['tsconfig.json', 'jsconfig.json']) {
const config = tsr.tsconfigResolverSync({ cwd, searchName });
if (config.exists) {
return config;
}
}
return undefined;
}
/** /**
* Attempt to load an `astro.config.mjs` file * Attempt to load an `astro.config.mjs` file
* @deprecated * @deprecated

View file

@ -0,0 +1,20 @@
export type {
AstroConfigSchema
} from './schema';
export {
openConfig,
resolveConfigPath,
resolveFlags,
resolveRoot,
validateConfig,
} from './config.js';
export {
createSettings
} from './settings.js';
export {
loadTSConfig
} from './tsconfig.js';

View file

@ -0,0 +1,271 @@
import type { RehypePlugin, RemarkPlugin, RemarkRehype } from '@astrojs/markdown-remark';
import type * as Postcss from 'postcss';
import type { ILanguageRegistration, IThemeRegistration, Theme } from 'shiki';
import type { AstroUserConfig, ViteUserConfig } from '../../@types/astro';
import postcssrc from 'postcss-load-config';
import { BUNDLED_THEMES } from 'shiki';
import { fileURLToPath } from 'url';
import { z } from 'zod';
import { appendForwardSlash, prependForwardSlash, trimSlashes } from '../path.js';
import { isObject } from '../util.js';
const ASTRO_CONFIG_DEFAULTS: AstroUserConfig & any = {
root: '.',
srcDir: './src',
publicDir: './public',
outDir: './dist',
base: '/',
trailingSlash: 'ignore',
build: { format: 'directory' },
server: {
host: false,
port: 3000,
streaming: true,
},
style: { postcss: { options: {}, plugins: [] } },
integrations: [],
markdown: {
drafts: false,
syntaxHighlight: 'shiki',
shikiConfig: {
langs: [],
theme: 'github-dark',
wrap: false,
},
remarkPlugins: [],
rehypePlugins: [],
remarkRehype: {},
},
vite: {},
legacy: {
astroFlavoredMarkdown: false,
},
};
export const AstroConfigSchema = z.object({
root: z
.string()
.optional()
.default(ASTRO_CONFIG_DEFAULTS.root)
.transform((val) => new URL(val)),
srcDir: z
.string()
.optional()
.default(ASTRO_CONFIG_DEFAULTS.srcDir)
.transform((val) => new URL(val)),
publicDir: z
.string()
.optional()
.default(ASTRO_CONFIG_DEFAULTS.publicDir)
.transform((val) => new URL(val)),
outDir: z
.string()
.optional()
.default(ASTRO_CONFIG_DEFAULTS.outDir)
.transform((val) => new URL(val)),
site: z
.string()
.url()
.optional()
.transform((val) => (val ? appendForwardSlash(val) : val)),
base: z
.string()
.optional()
.default(ASTRO_CONFIG_DEFAULTS.base)
.transform((val) => prependForwardSlash(appendForwardSlash(trimSlashes(val)))),
trailingSlash: z
.union([z.literal('always'), z.literal('never'), z.literal('ignore')])
.optional()
.default(ASTRO_CONFIG_DEFAULTS.trailingSlash),
output: z
.union([z.literal('static'), z.literal('server')])
.optional()
.default('static'),
adapter: z.object({ name: z.string(), hooks: z.object({}).passthrough().default({}) }).optional(),
integrations: z.preprocess(
// preprocess
(val) => (Array.isArray(val) ? val.flat(Infinity).filter(Boolean) : val),
// validate
z
.array(z.object({ name: z.string(), hooks: z.object({}).passthrough().default({}) }))
.default(ASTRO_CONFIG_DEFAULTS.integrations)
),
build: z
.object({
format: z
.union([z.literal('file'), z.literal('directory')])
.optional()
.default(ASTRO_CONFIG_DEFAULTS.build.format),
})
.optional()
.default({}),
server: z.preprocess(
// preprocess
// NOTE: Uses the "error" command here because this is overwritten by the
// individualized schema parser with the correct command.
(val) => (typeof val === 'function' ? val({ command: 'error' }) : val),
// validate
z
.object({
host: z
.union([z.string(), z.boolean()])
.optional()
.default(ASTRO_CONFIG_DEFAULTS.server.host),
port: z.number().optional().default(ASTRO_CONFIG_DEFAULTS.server.port),
})
.optional()
.default({})
),
style: z
.object({
postcss: z
.object({
options: z.any(),
plugins: z.array(z.any()),
})
.optional()
.default(ASTRO_CONFIG_DEFAULTS.style.postcss),
})
.optional()
.default({}),
markdown: z
.object({
drafts: z.boolean().default(false),
syntaxHighlight: z
.union([z.literal('shiki'), z.literal('prism'), z.literal(false)])
.default(ASTRO_CONFIG_DEFAULTS.markdown.syntaxHighlight),
shikiConfig: z
.object({
langs: z.custom<ILanguageRegistration>().array().default([]),
theme: z
.enum(BUNDLED_THEMES as [Theme, ...Theme[]])
.or(z.custom<IThemeRegistration>())
.default(ASTRO_CONFIG_DEFAULTS.markdown.shikiConfig.theme),
wrap: z.boolean().or(z.null()).default(ASTRO_CONFIG_DEFAULTS.markdown.shikiConfig.wrap),
})
.default({}),
remarkPlugins: z
.union([
z.string(),
z.tuple([z.string(), z.any()]),
z.custom<RemarkPlugin>((data) => typeof data === 'function'),
z.tuple([z.custom<RemarkPlugin>((data) => typeof data === 'function'), z.any()]),
])
.array()
.default(ASTRO_CONFIG_DEFAULTS.markdown.remarkPlugins),
rehypePlugins: z
.union([
z.string(),
z.tuple([z.string(), z.any()]),
z.custom<RehypePlugin>((data) => typeof data === 'function'),
z.tuple([z.custom<RehypePlugin>((data) => typeof data === 'function'), z.any()]),
])
.array()
.default(ASTRO_CONFIG_DEFAULTS.markdown.rehypePlugins),
remarkRehype: z
.custom<RemarkRehype>((data) => data instanceof Object && !Array.isArray(data))
.optional()
.default(ASTRO_CONFIG_DEFAULTS.markdown.remarkRehype),
extendDefaultPlugins: z.boolean().default(false),
})
.default({}),
vite: z
.custom<ViteUserConfig>((data) => data instanceof Object && !Array.isArray(data))
.default(ASTRO_CONFIG_DEFAULTS.vite),
legacy: z
.object({
astroFlavoredMarkdown: z
.boolean()
.optional()
.default(ASTRO_CONFIG_DEFAULTS.legacy.astroFlavoredMarkdown),
})
.optional()
.default({}),
});
interface PostCSSConfigResult {
options: Postcss.ProcessOptions;
plugins: Postcss.Plugin[];
}
async function resolvePostcssConfig(inlineOptions: any, root: URL): Promise<PostCSSConfigResult> {
if (isObject(inlineOptions)) {
const options = { ...inlineOptions };
delete options.plugins;
return {
options,
plugins: inlineOptions.plugins || [],
};
}
const searchPath = typeof inlineOptions === 'string' ? inlineOptions : fileURLToPath(root);
try {
// @ts-ignore
return await postcssrc({}, searchPath);
} catch (err: any) {
if (!/No PostCSS Config found/.test(err.message)) {
throw err;
}
return {
options: {},
plugins: [],
};
}
}
export function createRelativeSchema(cmd: string, fileProtocolRoot: URL) {
// We need to extend the global schema to add transforms that are relative to root.
// This is type checked against the global schema to make sure we still match.
const AstroConfigRelativeSchema = AstroConfigSchema.extend({
root: z
.string()
.default(ASTRO_CONFIG_DEFAULTS.root)
.transform((val) => new URL(appendForwardSlash(val), fileProtocolRoot)),
srcDir: z
.string()
.default(ASTRO_CONFIG_DEFAULTS.srcDir)
.transform((val) => new URL(appendForwardSlash(val), fileProtocolRoot)),
publicDir: z
.string()
.default(ASTRO_CONFIG_DEFAULTS.publicDir)
.transform((val) => new URL(appendForwardSlash(val), fileProtocolRoot)),
outDir: z
.string()
.default(ASTRO_CONFIG_DEFAULTS.outDir)
.transform((val) => new URL(appendForwardSlash(val), fileProtocolRoot)),
server: z.preprocess(
// preprocess
(val) =>
typeof val === 'function' ? val({ command: cmd === 'dev' ? 'dev' : 'preview' }) : val,
// validate
z
.object({
host: z
.union([z.string(), z.boolean()])
.optional()
.default(ASTRO_CONFIG_DEFAULTS.server.host),
port: z.number().optional().default(ASTRO_CONFIG_DEFAULTS.server.port),
streaming: z.boolean().optional().default(true),
})
.optional()
.default({})
),
style: z
.object({
postcss: z.preprocess(
(val) => resolvePostcssConfig(val, fileProtocolRoot),
z
.object({
options: z.any(),
plugins: z.array(z.any()),
})
.optional()
.default(ASTRO_CONFIG_DEFAULTS.style.postcss)
),
})
.optional()
.default({}),
});
return AstroConfigRelativeSchema;
}

View file

@ -0,0 +1,31 @@
import type {
AstroConfig,
AstroSettings,
} from '../../@types/astro';
import type { TsConfigJson } from 'tsconfig-resolver';
import jsxRenderer from '../../jsx/renderer.js';
export interface CreateSettings {
config: AstroConfig;
tsConfig?: TsConfigJson;
tsConfigPath?: string;
}
export function createSettings({
config,
tsConfig,
tsConfigPath,
}: CreateSettings): AstroSettings {
return {
config,
tsConfig,
tsConfigPath,
adapter: undefined,
injectedRoutes: [],
pageExtensions: ['.astro', '.md', '.html'],
renderers: [jsxRenderer],
scripts: [],
};
}

View file

@ -0,0 +1,11 @@
import * as tsr from 'tsconfig-resolver';
export function loadTSConfig(cwd: string | undefined): tsr.TsConfigResult | undefined {
for (const searchName of ['tsconfig.json', 'jsconfig.json']) {
const config = tsr.tsconfigResolverSync({ cwd, searchName });
if (config.exists) {
return config;
}
}
return undefined;
}

View file

@ -1,4 +1,4 @@
import type { AstroConfig } from '../@types/astro'; import type { AstroSettings } from '../@types/astro';
import type { LogOptions } from './logger/core'; import type { LogOptions } from './logger/core';
import fs from 'fs'; import fs from 'fs';
@ -25,7 +25,7 @@ import { resolveDependency } from './util.js';
export type ViteConfigWithSSR = vite.InlineConfig & { ssr?: vite.SSROptions }; export type ViteConfigWithSSR = vite.InlineConfig & { ssr?: vite.SSROptions };
interface CreateViteOptions { interface CreateViteOptions {
astroConfig: AstroConfig; settings: AstroSettings;
logging: LogOptions; logging: LogOptions;
mode: 'dev' | 'build' | string; mode: 'dev' | 'build' | string;
} }
@ -59,12 +59,12 @@ function getSsrNoExternalDeps(projectRoot: URL): string[] {
/** Return a common starting point for all Vite actions */ /** Return a common starting point for all Vite actions */
export async function createVite( export async function createVite(
commandConfig: ViteConfigWithSSR, commandConfig: ViteConfigWithSSR,
{ astroConfig, logging, mode }: CreateViteOptions { settings, logging, mode }: CreateViteOptions
): Promise<ViteConfigWithSSR> { ): Promise<ViteConfigWithSSR> {
const thirdPartyAstroPackages = await getAstroPackages(astroConfig); const thirdPartyAstroPackages = await getAstroPackages(settings);
// Start with the Vite configuration that Astro core needs // Start with the Vite configuration that Astro core needs
const commonConfig: ViteConfigWithSSR = { const commonConfig: ViteConfigWithSSR = {
cacheDir: fileURLToPath(new URL('./node_modules/.vite/', astroConfig.root)), // using local caches allows Astro to be used in monorepos, etc. cacheDir: fileURLToPath(new URL('./node_modules/.vite/', settings.config.root)), // using local caches allows Astro to be used in monorepos, etc.
clearScreen: false, // we want to control the output, not Vite clearScreen: false, // we want to control the output, not Vite
logLevel: 'warn', // log warnings and errors only logLevel: 'warn', // log warnings and errors only
appType: 'custom', appType: 'custom',
@ -73,27 +73,27 @@ export async function createVite(
exclude: ['node-fetch'], exclude: ['node-fetch'],
}, },
plugins: [ plugins: [
configAliasVitePlugin({ config: astroConfig }), configAliasVitePlugin({ settings }),
astroVitePlugin({ config: astroConfig, logging }), astroVitePlugin({ settings, logging }),
astroScriptsPlugin({ config: astroConfig }), astroScriptsPlugin({ settings }),
// The server plugin is for dev only and having it run during the build causes // The server plugin is for dev only and having it run during the build causes
// the build to run very slow as the filewatcher is triggered often. // the build to run very slow as the filewatcher is triggered often.
mode !== 'build' && astroViteServerPlugin({ config: astroConfig, logging }), mode !== 'build' && astroViteServerPlugin({ settings, logging }),
envVitePlugin({ config: astroConfig }), envVitePlugin({ settings }),
astroConfig.legacy.astroFlavoredMarkdown settings.config.legacy.astroFlavoredMarkdown
? legacyMarkdownVitePlugin({ config: astroConfig, logging }) ? legacyMarkdownVitePlugin({ settings, logging })
: markdownVitePlugin({ config: astroConfig, logging }), : markdownVitePlugin({ settings, logging }),
htmlVitePlugin(), htmlVitePlugin(),
jsxVitePlugin({ config: astroConfig, logging }), jsxVitePlugin({ settings, logging }),
astroPostprocessVitePlugin({ config: astroConfig }), astroPostprocessVitePlugin({ settings }),
astroIntegrationsContainerPlugin({ config: astroConfig, logging }), astroIntegrationsContainerPlugin({ settings, logging }),
astroScriptsPageSSRPlugin({ config: astroConfig }), astroScriptsPageSSRPlugin({ settings }),
], ],
publicDir: fileURLToPath(astroConfig.publicDir), publicDir: fileURLToPath(settings.config.publicDir),
root: fileURLToPath(astroConfig.root), root: fileURLToPath(settings.config.root),
envPrefix: 'PUBLIC_', envPrefix: 'PUBLIC_',
define: { define: {
'import.meta.env.SITE': astroConfig.site ? `'${astroConfig.site}'` : 'undefined', 'import.meta.env.SITE': settings.config.site ? `'${settings.config.site}'` : 'undefined',
}, },
server: { server: {
hmr: hmr:
@ -110,7 +110,7 @@ export async function createVite(
}, },
}, },
css: { css: {
postcss: astroConfig.style.postcss || {}, postcss: settings.config.style.postcss || {},
}, },
resolve: { resolve: {
alias: [ alias: [
@ -129,7 +129,7 @@ export async function createVite(
conditions: ['astro'], conditions: ['astro'],
}, },
ssr: { ssr: {
noExternal: [...getSsrNoExternalDeps(astroConfig.root), ...thirdPartyAstroPackages], noExternal: [...getSsrNoExternalDeps(settings.config.root), ...thirdPartyAstroPackages],
}, },
}; };
@ -140,7 +140,7 @@ export async function createVite(
// 3. integration-provided vite config, via the `config:setup` hook // 3. integration-provided vite config, via the `config:setup` hook
// 4. command vite config, passed as the argument to this function // 4. command vite config, passed as the argument to this function
let result = commonConfig; let result = commonConfig;
result = vite.mergeConfig(result, astroConfig.vite || {}); result = vite.mergeConfig(result, settings.config.vite || {});
result = vite.mergeConfig(result, commandConfig); result = vite.mergeConfig(result, commandConfig);
if (result.plugins) { if (result.plugins) {
sortPlugins(result.plugins); sortPlugins(result.plugins);
@ -175,8 +175,8 @@ function sortPlugins(pluginOptions: vite.PluginOption[]) {
// Scans `projectRoot` for third-party Astro packages that could export an `.astro` file // Scans `projectRoot` for third-party Astro packages that could export an `.astro` file
// `.astro` files need to be built by Vite, so these should use `noExternal` // `.astro` files need to be built by Vite, so these should use `noExternal`
async function getAstroPackages({ root }: AstroConfig): Promise<string[]> { async function getAstroPackages(settings: AstroSettings): Promise<string[]> {
const { astroPackages } = new DependencyWalker(root); const { astroPackages } = new DependencyWalker(settings.config.root);
return astroPackages; return astroPackages;
} }

View file

@ -2,7 +2,7 @@ import type { AstroTelemetry } from '@astrojs/telemetry';
import type { AddressInfo } from 'net'; import type { AddressInfo } from 'net';
import { performance } from 'perf_hooks'; import { performance } from 'perf_hooks';
import * as vite from 'vite'; import * as vite from 'vite';
import type { AstroConfig } from '../../@types/astro'; import type { AstroSettings } from '../../@types/astro';
import { import {
runHookConfigDone, runHookConfigDone,
runHookConfigSetup, runHookConfigSetup,
@ -28,17 +28,17 @@ export interface DevServer {
} }
/** `astro dev` */ /** `astro dev` */
export default async function dev(config: AstroConfig, options: DevOptions): Promise<DevServer> { export default async function dev(settings: AstroSettings, options: DevOptions): Promise<DevServer> {
const devStart = performance.now(); const devStart = performance.now();
applyPolyfill(); applyPolyfill();
await options.telemetry.record([]); await options.telemetry.record([]);
config = await runHookConfigSetup({ config, command: 'dev', logging: options.logging }); settings = await runHookConfigSetup({ settings, command: 'dev', logging: options.logging });
const { host, port } = config.server; const { host, port } = settings.config.server;
const { isRestart = false } = options; const { isRestart = false } = options;
// The client entrypoint for renderers. Since these are imported dynamically // The client entrypoint for renderers. Since these are imported dynamically
// we need to tell Vite to preoptimize them. // we need to tell Vite to preoptimize them.
const rendererClientEntries = config._ctx.renderers const rendererClientEntries = settings.renderers
.map((r) => r.clientEntrypoint) .map((r) => r.clientEntrypoint)
.filter(Boolean) as string[]; .filter(Boolean) as string[];
@ -50,21 +50,21 @@ export default async function dev(config: AstroConfig, options: DevOptions): Pro
include: rendererClientEntries, include: rendererClientEntries,
}, },
}, },
{ astroConfig: config, logging: options.logging, mode: 'dev' } { settings, logging: options.logging, mode: 'dev' }
); );
await runHookConfigDone({ config, logging: options.logging }); await runHookConfigDone({ settings, logging: options.logging });
const viteServer = await vite.createServer(viteConfig); const viteServer = await vite.createServer(viteConfig);
runHookServerSetup({ config, server: viteServer, logging: options.logging }); runHookServerSetup({ config: settings.config, server: viteServer, logging: options.logging });
await viteServer.listen(port); await viteServer.listen(port);
const devServerAddressInfo = viteServer.httpServer!.address() as AddressInfo; const devServerAddressInfo = viteServer.httpServer!.address() as AddressInfo;
const site = config.site ? new URL(config.base, config.site) : undefined; const site = settings.config.site ? new URL(settings.config.base, settings.config.site) : undefined;
info( info(
options.logging, options.logging,
null, null,
msg.devStart({ msg.devStart({
startupTime: performance.now() - devStart, startupTime: performance.now() - devStart,
config, config: settings.config,
devServerAddressInfo, devServerAddressInfo,
site, site,
https: !!viteConfig.server?.https, https: !!viteConfig.server?.https,
@ -80,7 +80,7 @@ export default async function dev(config: AstroConfig, options: DevOptions): Pro
warn(options.logging, null, msg.fsStrictWarning()); warn(options.logging, null, msg.fsStrictWarning());
} }
await runHookServerStart({ config, address: devServerAddressInfo, logging: options.logging }); await runHookServerStart({ config: settings.config, address: devServerAddressInfo, logging: options.logging });
return { return {
address: devServerAddressInfo, address: devServerAddressInfo,
@ -89,7 +89,7 @@ export default async function dev(config: AstroConfig, options: DevOptions): Pro
}, },
stop: async () => { stop: async () => {
await viteServer.close(); await viteServer.close();
await runHookServerDone({ config, logging: options.logging }); await runHookServerDone({ config: settings.config, logging: options.logging });
}, },
}; };
} }

View file

@ -7,6 +7,6 @@ export async function call(ssrOpts: SSROptions) {
const [, mod] = await preload(ssrOpts); const [, mod] = await preload(ssrOpts);
return await callEndpoint(mod as unknown as EndpointHandler, { return await callEndpoint(mod as unknown as EndpointHandler, {
...ssrOpts, ...ssrOpts,
ssr: ssrOpts.astroConfig.output === 'server', ssr: ssrOpts.settings.config.output === 'server',
}); });
} }

View file

@ -1,6 +1,6 @@
import type { AstroTelemetry } from '@astrojs/telemetry'; import type { AstroTelemetry } from '@astrojs/telemetry';
import type { AddressInfo } from 'net'; import type { AddressInfo } from 'net';
import type { AstroConfig } from '../../@types/astro'; import type { AstroSettings } from '../../@types/astro';
import type { LogOptions } from '../logger/core'; import type { LogOptions } from '../logger/core';
import fs from 'fs'; import fs from 'fs';
@ -30,20 +30,20 @@ const HAS_FILE_EXTENSION_REGEXP = /^.*\.[^\\]+$/;
/** The primary dev action */ /** The primary dev action */
export default async function preview( export default async function preview(
config: AstroConfig, settings: AstroSettings,
{ logging }: PreviewOptions { logging }: PreviewOptions
): Promise<PreviewServer> { ): Promise<PreviewServer> {
if (config.output === 'server') { if (settings.config.output === 'server') {
throw new Error( throw new Error(
`[preview] 'output: server' not supported. Use your deploy platform's preview command directly instead, if one exists. (ex: 'netlify dev', 'vercel dev', 'wrangler', etc.)` `[preview] 'output: server' not supported. Use your deploy platform's preview command directly instead, if one exists. (ex: 'netlify dev', 'vercel dev', 'wrangler', etc.)`
); );
} }
const startServerTime = performance.now(); const startServerTime = performance.now();
const defaultOrigin = 'http://localhost'; const defaultOrigin = 'http://localhost';
const trailingSlash = config.trailingSlash; const trailingSlash = settings.config.trailingSlash;
/** Base request URL. */ /** Base request URL. */
let baseURL = new URL(config.base, new URL(config.site || '/', defaultOrigin)); let baseURL = new URL(settings.config.base, new URL(settings.config.site || '/', defaultOrigin));
const staticFileServer = sirv(fileURLToPath(config.outDir), { const staticFileServer = sirv(fileURLToPath(settings.config.outDir), {
dev: true, dev: true,
etag: true, etag: true,
maxAge: 0, maxAge: 0,
@ -84,7 +84,7 @@ export default async function preview(
// HACK: rewrite req.url so that sirv finds the file // HACK: rewrite req.url so that sirv finds the file
req.url = '/' + req.url?.replace(baseURL.pathname, ''); req.url = '/' + req.url?.replace(baseURL.pathname, '');
staticFileServer(req, res, () => { staticFileServer(req, res, () => {
const errorPagePath = fileURLToPath(config.outDir + '/404.html'); const errorPagePath = fileURLToPath(settings.config.outDir + '/404.html');
if (fs.existsSync(errorPagePath)) { if (fs.existsSync(errorPagePath)) {
res.statusCode = 404; res.statusCode = 404;
res.setHeader('Content-Type', 'text/html;charset=utf-8'); res.setHeader('Content-Type', 'text/html;charset=utf-8');
@ -100,8 +100,8 @@ export default async function preview(
} }
}); });
let { port } = config.server; let { port } = settings.config.server;
const host = getResolvedHostForHttpServer(config.server.host); const host = getResolvedHostForHttpServer(settings.config.server.host);
let httpServer: http.Server; let httpServer: http.Server;
@ -119,7 +119,7 @@ export default async function preview(
null, null,
msg.devStart({ msg.devStart({
startupTime: performance.now() - timerStart, startupTime: performance.now() - timerStart,
config, config: settings.config,
devServerAddressInfo, devServerAddressInfo,
https: false, https: false,
site: baseURL, site: baseURL,

View file

@ -1,7 +1,7 @@
import { fileURLToPath } from 'url'; import { fileURLToPath } from 'url';
import type { ViteDevServer } from 'vite'; import type { ViteDevServer } from 'vite';
import type { import type {
AstroConfig, AstroSettings,
AstroRenderer, AstroRenderer,
ComponentInstance, ComponentInstance,
RouteData, RouteData,
@ -20,8 +20,8 @@ import { resolveClientDevPath } from './resolve.js';
import { getScriptsForURL } from './scripts.js'; import { getScriptsForURL } from './scripts.js';
export interface SSROptions { export interface SSROptions {
/** an instance of the AstroConfig */ /** an instance of the AstroSettings */
astroConfig: AstroConfig; settings: AstroSettings;
/** location of file on disk */ /** location of file on disk */
filePath: URL; filePath: URL;
/** logging options */ /** logging options */
@ -58,18 +58,18 @@ async function loadRenderer(
export async function loadRenderers( export async function loadRenderers(
viteServer: ViteDevServer, viteServer: ViteDevServer,
astroConfig: AstroConfig settings: AstroSettings
): Promise<SSRLoadedRenderer[]> { ): Promise<SSRLoadedRenderer[]> {
return Promise.all(astroConfig._ctx.renderers.map((r) => loadRenderer(viteServer, r))); return Promise.all(settings.renderers.map((r) => loadRenderer(viteServer, r)));
} }
export async function preload({ export async function preload({
astroConfig, settings,
filePath, filePath,
viteServer, viteServer,
}: Pick<SSROptions, 'astroConfig' | 'filePath' | 'viteServer'>): Promise<ComponentPreload> { }: Pick<SSROptions, 'settings' | 'filePath' | 'viteServer'>): Promise<ComponentPreload> {
// Important: This needs to happen first, in case a renderer provides polyfills. // Important: This needs to happen first, in case a renderer provides polyfills.
const renderers = await loadRenderers(viteServer, astroConfig); const renderers = await loadRenderers(viteServer, settings);
// Load the module from the Vite SSR Runtime. // Load the module from the Vite SSR Runtime.
const mod = (await viteServer.ssrLoadModule(fileURLToPath(filePath))) as ComponentInstance; const mod = (await viteServer.ssrLoadModule(fileURLToPath(filePath))) as ComponentInstance;
if (viteServer.config.mode === 'development' || !mod?.$$metadata) { if (viteServer.config.mode === 'development' || !mod?.$$metadata) {
@ -92,7 +92,7 @@ export async function render(
ssrOpts: SSROptions ssrOpts: SSROptions
): Promise<Response> { ): Promise<Response> {
const { const {
astroConfig, settings,
filePath, filePath,
logging, logging,
mode, mode,
@ -104,10 +104,10 @@ export async function render(
viteServer, viteServer,
} = ssrOpts; } = ssrOpts;
// Add hoisted script tags // Add hoisted script tags
const scripts = await getScriptsForURL(filePath, astroConfig, viteServer); const scripts = await getScriptsForURL(filePath, viteServer);
// Inject HMR scripts // Inject HMR scripts
if (isPage(filePath, astroConfig) && mode === 'development') { if (isPage(filePath, settings) && mode === 'development') {
scripts.add({ scripts.add({
props: { type: 'module', src: '/@vite/client' }, props: { type: 'module', src: '/@vite/client' },
children: '', children: '',
@ -122,13 +122,13 @@ export async function render(
} }
// TODO: We should allow adding generic HTML elements to the head, not just scripts // TODO: We should allow adding generic HTML elements to the head, not just scripts
for (const script of astroConfig._ctx.scripts) { for (const script of settings.scripts) {
if (script.stage === 'head-inline') { if (script.stage === 'head-inline') {
scripts.add({ scripts.add({
props: {}, props: {},
children: script.content, children: script.content,
}); });
} else if (script.stage === 'page' && isPage(filePath, astroConfig)) { } else if (script.stage === 'page' && isPage(filePath, settings)) {
scripts.add({ scripts.add({
props: { type: 'module', src: `/@id/${PAGE_SCRIPT_ID}` }, props: { type: 'module', src: `/@id/${PAGE_SCRIPT_ID}` },
children: '', children: '',
@ -167,13 +167,13 @@ export async function render(
}); });
let response = await coreRender({ let response = await coreRender({
adapterName: astroConfig.adapter?.name, adapterName: settings.config.adapter?.name,
links, links,
styles, styles,
logging, logging,
markdown: { markdown: {
...astroConfig.markdown, ...settings.config.markdown,
isAstroFlavoredMd: astroConfig.legacy.astroFlavoredMarkdown, isAstroFlavoredMd: settings.config.legacy.astroFlavoredMarkdown,
}, },
mod, mod,
mode, mode,
@ -191,8 +191,8 @@ export async function render(
request, request,
route, route,
routeCache, routeCache,
site: astroConfig.site ? new URL(astroConfig.base, astroConfig.site).toString() : undefined, site: settings.config.site ? new URL(settings.config.base, settings.config.site).toString() : undefined,
ssr: astroConfig.output === 'server', ssr: settings.config.output === 'server',
streaming: true, streaming: true,
}); });

View file

@ -10,19 +10,17 @@ import { crawlGraph } from './vite.js';
export async function getScriptsForURL( export async function getScriptsForURL(
filePath: URL, filePath: URL,
astroConfig: AstroConfig,
viteServer: vite.ViteDevServer viteServer: vite.ViteDevServer
): Promise<Set<SSRElement>> { ): Promise<Set<SSRElement>> {
const elements = new Set<SSRElement>(); const elements = new Set<SSRElement>();
const rootID = viteID(filePath); const rootID = viteID(filePath);
let rootProjectFolder = slash(fileURLToPath(astroConfig.root));
const modInfo = viteServer.pluginContainer.getModuleInfo(rootID); const modInfo = viteServer.pluginContainer.getModuleInfo(rootID);
addHoistedScripts(elements, modInfo, rootProjectFolder); addHoistedScripts(elements, modInfo);
for await (const moduleNode of crawlGraph(viteServer, rootID, true)) { for await (const moduleNode of crawlGraph(viteServer, rootID, true)) {
const id = moduleNode.id; const id = moduleNode.id;
if (id) { if (id) {
const info = viteServer.pluginContainer.getModuleInfo(id); const info = viteServer.pluginContainer.getModuleInfo(id);
addHoistedScripts(elements, info, rootProjectFolder); addHoistedScripts(elements, info);
} }
} }
@ -32,7 +30,6 @@ export async function getScriptsForURL(
function addHoistedScripts( function addHoistedScripts(
set: Set<SSRElement>, set: Set<SSRElement>,
info: ModuleInfo | null, info: ModuleInfo | null,
rootProjectFolder: string
) { ) {
if (!info?.meta?.astro) { if (!info?.meta?.astro) {
return; return;

View file

@ -1,5 +1,6 @@
import type { import type {
AstroConfig, AstroConfig,
AstroSettings,
InjectedRoute, InjectedRoute,
ManifestData, ManifestData,
RouteData, RouteData,
@ -190,7 +191,7 @@ function injectedRouteToItem(
/** Create manifest of all static routes */ /** Create manifest of all static routes */
export function createRouteManifest( export function createRouteManifest(
{ config, cwd }: { config: AstroConfig; cwd?: string }, { settings, cwd }: { settings: AstroSettings; cwd?: string },
logging: LogOptions logging: LogOptions
): ManifestData { ): ManifestData {
const components: string[] = []; const components: string[] = [];
@ -198,7 +199,7 @@ export function createRouteManifest(
const validPageExtensions: Set<string> = new Set([ const validPageExtensions: Set<string> = new Set([
'.astro', '.astro',
'.md', '.md',
...config._ctx.pageExtensions, ...settings.pageExtensions,
]); ]);
const validEndpointExtensions: Set<string> = new Set(['.js', '.ts']); const validEndpointExtensions: Set<string> = new Set(['.js', '.ts']);
@ -206,7 +207,7 @@ export function createRouteManifest(
let items: Item[] = []; let items: Item[] = [];
fs.readdirSync(dir).forEach((basename) => { fs.readdirSync(dir).forEach((basename) => {
const resolved = path.join(dir, basename); const resolved = path.join(dir, basename);
const file = slash(path.relative(cwd || fileURLToPath(config.root), resolved)); const file = slash(path.relative(cwd || fileURLToPath(settings.config.root), resolved));
const isDir = fs.statSync(resolved).isDirectory(); const isDir = fs.statSync(resolved).isDirectory();
const ext = path.extname(basename); const ext = path.extname(basename);
@ -283,7 +284,7 @@ export function createRouteManifest(
} else { } else {
components.push(item.file); components.push(item.file);
const component = item.file; const component = item.file;
const trailingSlash = item.isPage ? config.trailingSlash : 'never'; const trailingSlash = item.isPage ? settings.config.trailingSlash : 'never';
const pattern = getPattern(segments, trailingSlash); const pattern = getPattern(segments, trailingSlash);
const generate = getRouteGenerator(segments, trailingSlash); const generate = getRouteGenerator(segments, trailingSlash);
const pathname = segments.every((segment) => segment.length === 1 && !segment[0].dynamic) const pathname = segments.every((segment) => segment.length === 1 && !segment[0].dynamic)
@ -307,17 +308,18 @@ export function createRouteManifest(
}); });
} }
const { config } = settings;
const pages = resolvePages(config); const pages = resolvePages(config);
if (fs.existsSync(pages)) { if (fs.existsSync(pages)) {
walk(fileURLToPath(pages), [], []); walk(fileURLToPath(pages), [], []);
} else if (config?._ctx?.injectedRoutes?.length === 0) { } else if (settings.injectedRoutes.length === 0) {
const pagesDirRootRelative = pages.href.slice(config.root.href.length); const pagesDirRootRelative = pages.href.slice(settings.config.root.href.length);
warn(logging, 'astro', `Missing pages directory: ${pagesDirRootRelative}`); warn(logging, 'astro', `Missing pages directory: ${pagesDirRootRelative}`);
} }
config?._ctx?.injectedRoutes settings.injectedRoutes
?.sort((a, b) => ?.sort((a, b) =>
// sort injected routes in the same way as user-defined routes // sort injected routes in the same way as user-defined routes
comparator(injectedRouteToItem({ config, cwd }, a), injectedRouteToItem({ config, cwd }, b)) comparator(injectedRouteToItem({ config, cwd }, a), injectedRouteToItem({ config, cwd }, b))

View file

@ -5,7 +5,7 @@ import resolve from 'resolve';
import slash from 'slash'; import slash from 'slash';
import { fileURLToPath, pathToFileURL } from 'url'; import { fileURLToPath, pathToFileURL } from 'url';
import type { ErrorPayload, ViteDevServer } from 'vite'; import type { ErrorPayload, ViteDevServer } from 'vite';
import type { AstroConfig, RouteType } from '../@types/astro'; import type { AstroConfig, AstroSettings, RouteType } from '../@types/astro';
import { prependForwardSlash, removeTrailingForwardSlash } from './path.js'; import { prependForwardSlash, removeTrailingForwardSlash } from './path.js';
// process.env.PACKAGE_VERSION is injected when we build and publish the astro package. // process.env.PACKAGE_VERSION is injected when we build and publish the astro package.
@ -172,21 +172,21 @@ function isPublicRoute(file: URL, config: AstroConfig): boolean {
return true; return true;
} }
function endsWithPageExt(file: URL, config: AstroConfig): boolean { function endsWithPageExt(file: URL, settings: AstroSettings): boolean {
for (const ext of config._ctx.pageExtensions) { for (const ext of settings.pageExtensions) {
if (file.toString().endsWith(ext)) return true; if (file.toString().endsWith(ext)) return true;
} }
return false; return false;
} }
export function isPage(file: URL, config: AstroConfig): boolean { export function isPage(file: URL, settings: AstroSettings): boolean {
if (!isInPagesDir(file, config)) return false; if (!isInPagesDir(file, settings.config)) return false;
if (!isPublicRoute(file, config)) return false; if (!isPublicRoute(file, settings.config)) return false;
return endsWithPageExt(file, config); return endsWithPageExt(file, settings);
} }
export function isModeServerWithNoAdapter(config: AstroConfig): boolean { export function isModeServerWithNoAdapter(settings: AstroSettings): boolean {
return config.output === 'server' && !config._ctx.adapter; return settings.config.output === 'server' && !settings.adapter;
} }
export function relativeToSrcDir(config: AstroConfig, idOrUrl: URL | string) { export function relativeToSrcDir(config: AstroConfig, idOrUrl: URL | string) {

View file

@ -3,6 +3,7 @@ import type { AddressInfo } from 'net';
import type { ViteDevServer } from 'vite'; import type { ViteDevServer } from 'vite';
import { import {
AstroConfig, AstroConfig,
AstroSettings,
AstroRenderer, AstroRenderer,
BuildConfig, BuildConfig,
HookParameters, HookParameters,
@ -10,7 +11,7 @@ import {
} from '../@types/astro.js'; } from '../@types/astro.js';
import type { SerializedSSRManifest } from '../core/app/types'; import type { SerializedSSRManifest } from '../core/app/types';
import type { PageBuildData } from '../core/build/types'; import type { PageBuildData } from '../core/build/types';
import { mergeConfig } from '../core/config.js'; import { mergeConfig } from '../core/config/config.js';
import type { ViteConfigWithSSR } from '../core/create-vite.js'; import type { ViteConfigWithSSR } from '../core/create-vite.js';
import { info, LogOptions } from '../core/logger/core.js'; import { info, LogOptions } from '../core/logger/core.js';
@ -34,21 +35,22 @@ async function withTakingALongTimeMsg<T>({
} }
export async function runHookConfigSetup({ export async function runHookConfigSetup({
config: _config, settings,
command, command,
logging, logging,
}: { }: {
config: AstroConfig; settings: AstroSettings;
command: 'dev' | 'build'; command: 'dev' | 'build';
logging: LogOptions; logging: LogOptions;
}): Promise<AstroConfig> { }): Promise<AstroSettings> {
// An adapter is an integration, so if one is provided push it. // An adapter is an integration, so if one is provided push it.
if (_config.adapter) { if (settings.config.adapter) {
_config.integrations.push(_config.adapter); settings.config.integrations.push(settings.config.adapter);
} }
let updatedConfig: AstroConfig = { ..._config }; let updatedConfig: AstroConfig = { ...settings.config };
for (const integration of _config.integrations) { let updatedSettings: AstroSettings = { ...settings, config: updatedConfig };
for (const integration of settings.config.integrations) {
/** /**
* By making integration hooks optional, Astro can now ignore null or undefined Integrations * By making integration hooks optional, Astro can now ignore null or undefined Integrations
* instead of giving an internal error most people can't read * instead of giving an internal error most people can't read
@ -74,22 +76,22 @@ export async function runHookConfigSetup({
throw new Error(`Renderer ${bold(renderer.name)} does not provide a serverEntrypoint.`); throw new Error(`Renderer ${bold(renderer.name)} does not provide a serverEntrypoint.`);
} }
updatedConfig._ctx.renderers.push(renderer); updatedSettings.renderers.push(renderer);
}, },
injectScript: (stage, content) => { injectScript: (stage, content) => {
updatedConfig._ctx.scripts.push({ stage, content }); updatedSettings.scripts.push({ stage, content });
}, },
updateConfig: (newConfig) => { updateConfig: (newConfig) => {
updatedConfig = mergeConfig(updatedConfig, newConfig) as AstroConfig; updatedConfig = mergeConfig(updatedConfig, newConfig) as AstroConfig;
}, },
injectRoute: (injectRoute) => { injectRoute: (injectRoute) => {
updatedConfig._ctx.injectedRoutes.push(injectRoute); updatedSettings.injectedRoutes.push(injectRoute);
}, },
}; };
// Semi-private `addPageExtension` hook // Semi-private `addPageExtension` hook
function addPageExtension(...input: (string | string[])[]) { function addPageExtension(...input: (string | string[])[]) {
const exts = (input.flat(Infinity) as string[]).map((ext) => `.${ext.replace(/^\./, '')}`); const exts = (input.flat(Infinity) as string[]).map((ext) => `.${ext.replace(/^\./, '')}`);
updatedConfig._ctx.pageExtensions.push(...exts); updatedSettings.pageExtensions.push(...exts);
} }
Object.defineProperty(hooks, 'addPageExtension', { Object.defineProperty(hooks, 'addPageExtension', {
value: addPageExtension, value: addPageExtension,
@ -103,29 +105,31 @@ export async function runHookConfigSetup({
}); });
} }
} }
return updatedConfig;
updatedSettings.config = updatedConfig;
return updatedSettings;
} }
export async function runHookConfigDone({ export async function runHookConfigDone({
config, settings,
logging, logging,
}: { }: {
config: AstroConfig; settings: AstroSettings;
logging: LogOptions; logging: LogOptions;
}) { }) {
for (const integration of config.integrations) { for (const integration of settings.config.integrations) {
if (integration?.hooks?.['astro:config:done']) { if (integration?.hooks?.['astro:config:done']) {
await withTakingALongTimeMsg({ await withTakingALongTimeMsg({
name: integration.name, name: integration.name,
hookResult: integration.hooks['astro:config:done']({ hookResult: integration.hooks['astro:config:done']({
config, config: settings.config,
setAdapter(adapter) { setAdapter(adapter) {
if (config._ctx.adapter && config._ctx.adapter.name !== adapter.name) { if (settings.adapter && settings.adapter.name !== adapter.name) {
throw new Error( throw new Error(
`Integration "${integration.name}" conflicts with "${config._ctx.adapter.name}". You can only configure one deployment integration.` `Integration "${integration.name}" conflicts with "${settings.adapter.name}". You can only configure one deployment integration.`
); );
} }
config._ctx.adapter = adapter; settings.adapter = adapter;
}, },
}), }),
logging, logging,

View file

@ -3,18 +3,18 @@ import type { ArrowFunctionExpressionKind, CallExpressionKind } from 'ast-types/
import type { NodePath } from 'ast-types/lib/node-path'; import type { NodePath } from 'ast-types/lib/node-path';
import { parse, print, types, visit } from 'recast'; import { parse, print, types, visit } from 'recast';
import type { Plugin } from 'vite'; import type { Plugin } from 'vite';
import type { AstroConfig } from '../@types/astro'; import type { AstroSettings } from '../@types/astro';
// Check for `Astro.glob()`. Be very forgiving of whitespace. False positives are okay. // Check for `Astro.glob()`. Be very forgiving of whitespace. False positives are okay.
const ASTRO_GLOB_REGEX = /Astro2?\s*\.\s*glob\s*\(/; const ASTRO_GLOB_REGEX = /Astro2?\s*\.\s*glob\s*\(/;
interface AstroPluginOptions { interface AstroPluginOptions {
config: AstroConfig; settings: AstroSettings;
} }
// esbuild transforms the component-scoped Astro into Astro2, so need to check both. // esbuild transforms the component-scoped Astro into Astro2, so need to check both.
const validAstroGlobalNames = new Set(['Astro', 'Astro2']); const validAstroGlobalNames = new Set(['Astro', 'Astro2']);
export default function astro({ config }: AstroPluginOptions): Plugin { export default function astro(_opts: AstroPluginOptions): Plugin {
return { return {
name: 'astro:postprocess', name: 'astro:postprocess',
async transform(code, id) { async transform(code, id) {

View file

@ -1,7 +1,7 @@
import type http from 'http'; import type http from 'http';
import mime from 'mime'; import mime from 'mime';
import type * as vite from 'vite'; import type * as vite from 'vite';
import type { AstroConfig, ManifestData } from '../@types/astro'; import type { AstroSettings, ManifestData } from '../@types/astro';
import type { SSROptions } from '../core/render/dev/index'; import type { SSROptions } from '../core/render/dev/index';
import { Readable } from 'stream'; import { Readable } from 'stream';
@ -24,7 +24,7 @@ import { resolvePages } from '../core/util.js';
import notFoundTemplate, { subpathNotUsedTemplate } from '../template/4xx.js'; import notFoundTemplate, { subpathNotUsedTemplate } from '../template/4xx.js';
interface AstroPluginOptions { interface AstroPluginOptions {
config: AstroConfig; settings: AstroSettings;
logging: LogOptions; logging: LogOptions;
} }
@ -94,7 +94,7 @@ async function writeSSRResult(webResponse: Response, res: http.ServerResponse) {
async function handle404Response( async function handle404Response(
origin: string, origin: string,
config: AstroConfig, settings: AstroSettings,
req: http.IncomingMessage, req: http.IncomingMessage,
res: http.ServerResponse res: http.ServerResponse
) { ) {
@ -129,7 +129,7 @@ async function handle500Response(
} }
} }
function getCustom404Route(config: AstroConfig, manifest: ManifestData) { function getCustom404Route({ config }: AstroSettings, manifest: ManifestData) {
// For Windows compat, use relative page paths to match the 404 route // For Windows compat, use relative page paths to match the 404 route
const relPages = resolvePages(config).href.replace(config.root.href, ''); const relPages = resolvePages(config).href.replace(config.root.href, '');
const pattern = new RegExp(`${appendForwardSlash(relPages)}404.(astro|md)`); const pattern = new RegExp(`${appendForwardSlash(relPages)}404.(astro|md)`);
@ -141,9 +141,10 @@ function log404(logging: LogOptions, pathname: string) {
} }
export function baseMiddleware( export function baseMiddleware(
config: AstroConfig, settings: AstroSettings,
logging: LogOptions logging: LogOptions
): vite.Connect.NextHandleFunction { ): vite.Connect.NextHandleFunction {
const { config } = settings;
const site = config.site ? new URL(config.base, config.site) : undefined; const site = config.site ? new URL(config.base, config.site) : undefined;
const devRoot = site ? site.pathname : '/'; const devRoot = site ? site.pathname : '/';
@ -184,13 +185,13 @@ async function matchRoute(
viteServer: vite.ViteDevServer, viteServer: vite.ViteDevServer,
logging: LogOptions, logging: LogOptions,
manifest: ManifestData, manifest: ManifestData,
config: AstroConfig settings: AstroSettings
) { ) {
const matches = matchAllRoutes(pathname, manifest); const matches = matchAllRoutes(pathname, manifest);
for await (const maybeRoute of matches) { for await (const maybeRoute of matches) {
const filePath = new URL(`./${maybeRoute.component}`, config.root); const filePath = new URL(`./${maybeRoute.component}`, settings.config.root);
const preloadedComponent = await preload({ astroConfig: config, filePath, viteServer }); const preloadedComponent = await preload({ settings, filePath, viteServer });
const [, mod] = preloadedComponent; const [, mod] = preloadedComponent;
// attempt to get static paths // attempt to get static paths
// if this fails, we have a bad URL match! // if this fails, we have a bad URL match!
@ -200,7 +201,7 @@ async function matchRoute(
routeCache, routeCache,
pathname: pathname, pathname: pathname,
logging, logging,
ssr: config.output === 'server', ssr: settings.config.output === 'server',
}); });
if (paramsAndPropsRes !== GetParamsAndPropsError.NoMatchingStaticPath) { if (paramsAndPropsRes !== GetParamsAndPropsError.NoMatchingStaticPath) {
@ -222,11 +223,11 @@ async function matchRoute(
} }
log404(logging, pathname); log404(logging, pathname);
const custom404 = getCustom404Route(config, manifest); const custom404 = getCustom404Route(settings, manifest);
if (custom404) { if (custom404) {
const filePath = new URL(`./${custom404.component}`, config.root); const filePath = new URL(`./${custom404.component}`, settings.config.root);
const preloadedComponent = await preload({ astroConfig: config, filePath, viteServer }); const preloadedComponent = await preload({ settings, filePath, viteServer });
const [, mod] = preloadedComponent; const [, mod] = preloadedComponent;
return { return {
@ -246,10 +247,11 @@ async function handleRequest(
viteServer: vite.ViteDevServer, viteServer: vite.ViteDevServer,
logging: LogOptions, logging: LogOptions,
manifest: ManifestData, manifest: ManifestData,
config: AstroConfig, settings: AstroSettings,
req: http.IncomingMessage, req: http.IncomingMessage,
res: http.ServerResponse res: http.ServerResponse
) { ) {
const { config } = settings;
const origin = `${viteServer.config.server.https ? 'https' : 'http'}://${req.headers.host}`; const origin = `${viteServer.config.server.https ? 'https' : 'http'}://${req.headers.host}`;
const buildingToSSR = config.output === 'server'; const buildingToSSR = config.output === 'server';
// Ignore `.html` extensions and `index.html` in request URLS to ensure that // Ignore `.html` extensions and `index.html` in request URLS to ensure that
@ -292,7 +294,7 @@ async function handleRequest(
viteServer, viteServer,
logging, logging,
manifest, manifest,
config settings
); );
filePath = matchedRoute?.filePath; filePath = matchedRoute?.filePath;
@ -306,7 +308,7 @@ async function handleRequest(
viteServer, viteServer,
manifest, manifest,
logging, logging,
config, settings,
req, req,
res res
); );
@ -328,14 +330,15 @@ async function handleRoute(
viteServer: vite.ViteDevServer, viteServer: vite.ViteDevServer,
manifest: ManifestData, manifest: ManifestData,
logging: LogOptions, logging: LogOptions,
config: AstroConfig, settings: AstroSettings,
req: http.IncomingMessage, req: http.IncomingMessage,
res: http.ServerResponse res: http.ServerResponse
): Promise<void> { ): Promise<void> {
if (!matchedRoute) { if (!matchedRoute) {
return handle404Response(origin, config, req, res); return handle404Response(origin, settings, req, res);
} }
const { config } = settings;
const filePath: URL | undefined = matchedRoute.filePath; const filePath: URL | undefined = matchedRoute.filePath;
const { route, preloadedComponent, mod } = matchedRoute; const { route, preloadedComponent, mod } = matchedRoute;
const buildingToSSR = config.output === 'server'; const buildingToSSR = config.output === 'server';
@ -363,7 +366,7 @@ async function handleRoute(
}); });
const options: SSROptions = { const options: SSROptions = {
astroConfig: config, settings,
filePath, filePath,
logging, logging,
mode: 'development', mode: 'development',
@ -386,7 +389,7 @@ async function handleRoute(
viteServer, viteServer,
logging, logging,
manifest, manifest,
config settings
); );
return handleRoute( return handleRoute(
fourOhFourRoute, fourOhFourRoute,
@ -398,7 +401,7 @@ async function handleRoute(
viteServer, viteServer,
manifest, manifest,
logging, logging,
config, settings,
req, req,
res res
); );
@ -423,17 +426,17 @@ async function handleRoute(
} }
} }
export default function createPlugin({ config, logging }: AstroPluginOptions): vite.Plugin { export default function createPlugin({ settings, logging }: AstroPluginOptions): vite.Plugin {
return { return {
name: 'astro:server', name: 'astro:server',
configureServer(viteServer) { configureServer(viteServer) {
let routeCache = new RouteCache(logging); let routeCache = new RouteCache(logging);
let manifest: ManifestData = createRouteManifest({ config: config }, logging); let manifest: ManifestData = createRouteManifest({ settings }, logging);
/** rebuild the route cache + manifest, as needed. */ /** rebuild the route cache + manifest, as needed. */
function rebuildManifest(needsManifestRebuild: boolean, file: string) { function rebuildManifest(needsManifestRebuild: boolean, file: string) {
routeCache.clearAll(); routeCache.clearAll();
if (needsManifestRebuild) { if (needsManifestRebuild) {
manifest = createRouteManifest({ config: config }, logging); manifest = createRouteManifest({ settings }, logging);
} }
} }
// Rebuild route manifest on file change, if needed. // Rebuild route manifest on file change, if needed.
@ -442,17 +445,17 @@ export default function createPlugin({ config, logging }: AstroPluginOptions): v
viteServer.watcher.on('change', rebuildManifest.bind(null, false)); viteServer.watcher.on('change', rebuildManifest.bind(null, false));
return () => { return () => {
// Push this middleware to the front of the stack so that it can intercept responses. // Push this middleware to the front of the stack so that it can intercept responses.
if (config.base !== '/') { if (settings.config.base !== '/') {
viteServer.middlewares.stack.unshift({ viteServer.middlewares.stack.unshift({
route: '', route: '',
handle: baseMiddleware(config, logging), handle: baseMiddleware(settings, logging),
}); });
} }
viteServer.middlewares.use(async (req, res) => { viteServer.middlewares.use(async (req, res) => {
if (!req.url || !req.method) { if (!req.url || !req.method) {
throw new Error('Incomplete request'); throw new Error('Incomplete request');
} }
handleRequest(routeCache, viteServer, logging, manifest, config, req, res); handleRequest(routeCache, viteServer, logging, manifest, settings, req, res);
}); });
}; };
}, },

View file

@ -1,6 +1,6 @@
import type { PluginContext, SourceDescription } from 'rollup'; import type { PluginContext, SourceDescription } from 'rollup';
import type * as vite from 'vite'; import type * as vite from 'vite';
import type { AstroConfig } from '../@types/astro'; import type { AstroSettings } from '../@types/astro';
import type { LogOptions } from '../core/logger/core.js'; import type { LogOptions } from '../core/logger/core.js';
import type { ViteStyleTransformer } from '../vite-style-transform'; import type { ViteStyleTransformer } from '../vite-style-transform';
import type { PluginMetadata as AstroPluginMetadata } from './types'; import type { PluginMetadata as AstroPluginMetadata } from './types';
@ -22,12 +22,13 @@ import { parseAstroRequest, ParsedRequestResult } from './query.js';
const FRONTMATTER_PARSE_REGEXP = /^\-\-\-(.*)^\-\-\-/ms; const FRONTMATTER_PARSE_REGEXP = /^\-\-\-(.*)^\-\-\-/ms;
interface AstroPluginOptions { interface AstroPluginOptions {
config: AstroConfig; settings: AstroSettings;
logging: LogOptions; logging: LogOptions;
} }
/** Transform .astro files for Vite */ /** Transform .astro files for Vite */
export default function astro({ config, logging }: AstroPluginOptions): vite.Plugin { export default function astro({ settings, logging }: AstroPluginOptions): vite.Plugin {
const { config } = settings;
function normalizeFilename(filename: string) { function normalizeFilename(filename: string) {
if (filename.startsWith('/@fs')) { if (filename.startsWith('/@fs')) {
filename = filename.slice('/@fs'.length); filename = filename.slice('/@fs'.length);

View file

@ -1,5 +1,5 @@
import * as path from 'path'; import * as path from 'path';
import type { AstroConfig } from '../@types/astro'; import type { AstroSettings } from '../@types/astro';
import type * as vite from 'vite'; import type * as vite from 'vite';
@ -13,10 +13,10 @@ export declare interface Alias {
const normalize = (pathname: string) => String(pathname).split(path.sep).join(path.posix.sep); const normalize = (pathname: string) => String(pathname).split(path.sep).join(path.posix.sep);
/** Returns a list of compiled aliases. */ /** Returns a list of compiled aliases. */
const getConfigAlias = (astroConfig: AstroConfig): Alias[] | null => { const getConfigAlias = (settings: AstroSettings): Alias[] | null => {
/** Closest tsconfig.json or jsconfig.json */ /** Closest tsconfig.json or jsconfig.json */
const config = astroConfig._ctx.tsConfig; const config = settings.tsConfig;
const configPath = astroConfig._ctx.tsConfigPath; const configPath = settings.tsConfigPath;
// if no config was found, return null // if no config was found, return null
if (!config || !configPath) return null; if (!config || !configPath) return null;
@ -77,12 +77,13 @@ const getConfigAlias = (astroConfig: AstroConfig): Alias[] | null => {
/** Returns a Vite plugin used to alias pathes from tsconfig.json and jsconfig.json. */ /** Returns a Vite plugin used to alias pathes from tsconfig.json and jsconfig.json. */
export default function configAliasVitePlugin({ export default function configAliasVitePlugin({
config: astroConfig, settings,
}: { }: {
config: AstroConfig; settings: AstroSettings;
}): vite.PluginOption { }): vite.PluginOption {
const { config } = settings;
/** Aliases from the tsconfig.json or jsconfig.json configuration. */ /** Aliases from the tsconfig.json or jsconfig.json configuration. */
const configAlias = getConfigAlias(astroConfig); const configAlias = getConfigAlias(settings);
// if no config alias was found, bypass this plugin // if no config alias was found, bypass this plugin
if (!configAlias) return {} as vite.PluginOption; if (!configAlias) return {} as vite.PluginOption;

View file

@ -2,10 +2,10 @@ import MagicString from 'magic-string';
import { fileURLToPath } from 'url'; import { fileURLToPath } from 'url';
import type * as vite from 'vite'; import type * as vite from 'vite';
import { loadEnv } from 'vite'; import { loadEnv } from 'vite';
import type { AstroConfig } from '../@types/astro'; import type { AstroConfig, AstroSettings } from '../@types/astro';
interface EnvPluginOptions { interface EnvPluginOptions {
config: AstroConfig; settings: AstroSettings;
} }
function getPrivateEnv(viteConfig: vite.ResolvedConfig, astroConfig: AstroConfig) { function getPrivateEnv(viteConfig: vite.ResolvedConfig, astroConfig: AstroConfig) {
@ -51,12 +51,13 @@ function getReferencedPrivateKeys(source: string, privateEnv: Record<string, any
} }
export default function envVitePlugin({ export default function envVitePlugin({
config: astroConfig, settings,
}: EnvPluginOptions): vite.PluginOption { }: EnvPluginOptions): vite.PluginOption {
let privateEnv: Record<string, any> | null; let privateEnv: Record<string, any> | null;
let config: vite.ResolvedConfig; let config: vite.ResolvedConfig;
let replacements: Record<string, string>; let replacements: Record<string, string>;
let pattern: RegExp | undefined; let pattern: RegExp | undefined;
const { config: astroConfig } = settings;
return { return {
name: 'astro:vite-plugin-env', name: 'astro:vite-plugin-env',
enforce: 'pre', enforce: 'pre',

View file

@ -1,20 +1,20 @@
import { Plugin as VitePlugin } from 'vite'; import { Plugin as VitePlugin } from 'vite';
import { AstroConfig } from '../@types/astro.js'; import { AstroSettings } from '../@types/astro.js';
import { LogOptions } from '../core/logger/core.js'; import { LogOptions } from '../core/logger/core.js';
import { runHookServerSetup } from '../integrations/index.js'; import { runHookServerSetup } from '../integrations/index.js';
/** Connect Astro integrations into Vite, as needed. */ /** Connect Astro integrations into Vite, as needed. */
export default function astroIntegrationsContainerPlugin({ export default function astroIntegrationsContainerPlugin({
config, settings,
logging, logging,
}: { }: {
config: AstroConfig; settings: AstroSettings;
logging: LogOptions; logging: LogOptions;
}): VitePlugin { }): VitePlugin {
return { return {
name: 'astro:integration-container', name: 'astro:integration-container',
configureServer(server) { configureServer(server) {
runHookServerSetup({ config, server, logging }); runHookServerSetup({ config: settings.config, server, logging });
}, },
}; };
} }

View file

@ -1,7 +1,7 @@
import type { TransformResult } from 'rollup'; import type { TransformResult } from 'rollup';
import type { TsConfigJson } from 'tsconfig-resolver'; import type { TsConfigJson } from 'tsconfig-resolver';
import type { Plugin, ResolvedConfig } from 'vite'; import type { Plugin, ResolvedConfig } from 'vite';
import type { AstroConfig, AstroRenderer } from '../@types/astro'; import type { AstroSettings, AstroRenderer } from '../@types/astro';
import type { LogOptions } from '../core/logger/core.js'; import type { LogOptions } from '../core/logger/core.js';
import type { PluginMetadata } from '../vite-plugin-astro/types'; import type { PluginMetadata } from '../vite-plugin-astro/types';
@ -152,12 +152,12 @@ async function transformJSX({
} }
interface AstroPluginJSXOptions { interface AstroPluginJSXOptions {
config: AstroConfig; settings: AstroSettings;
logging: LogOptions; logging: LogOptions;
} }
/** Use Astro config to allow for alternate or multiple JSX renderers (by default Vite will assume React) */ /** Use Astro config to allow for alternate or multiple JSX renderers (by default Vite will assume React) */
export default function jsx({ config, logging }: AstroPluginJSXOptions): Plugin { export default function jsx({ settings, logging }: AstroPluginJSXOptions): Plugin {
let viteConfig: ResolvedConfig; let viteConfig: ResolvedConfig;
const jsxRenderers = new Map<string, AstroRenderer>(); const jsxRenderers = new Map<string, AstroRenderer>();
const jsxRenderersIntegrationOnly = new Map<string, AstroRenderer>(); const jsxRenderersIntegrationOnly = new Map<string, AstroRenderer>();
@ -172,7 +172,7 @@ export default function jsx({ config, logging }: AstroPluginJSXOptions): Plugin
enforce: 'pre', // run transforms before other plugins enforce: 'pre', // run transforms before other plugins
async configResolved(resolvedConfig) { async configResolved(resolvedConfig) {
viteConfig = resolvedConfig; viteConfig = resolvedConfig;
const possibleRenderers = collectJSXRenderers(config._ctx.renderers); const possibleRenderers = collectJSXRenderers(settings.renderers);
for (const [importSource, renderer] of possibleRenderers) { for (const [importSource, renderer] of possibleRenderers) {
jsxRenderers.set(importSource, renderer); jsxRenderers.set(importSource, renderer);
if (importSource === 'astro') { if (importSource === 'astro') {
@ -230,7 +230,7 @@ export default function jsx({ config, logging }: AstroPluginJSXOptions): Plugin
// Check the tsconfig // Check the tsconfig
if (!importSource) { if (!importSource) {
const compilerOptions = config._ctx.tsConfig?.compilerOptions; const compilerOptions = settings.tsConfig?.compilerOptions;
importSource = (compilerOptions as FixedCompilerOptions | undefined)?.jsxImportSource; importSource = (compilerOptions as FixedCompilerOptions | undefined)?.jsxImportSource;
} }

View file

@ -5,7 +5,7 @@ import fs from 'fs';
import matter from 'gray-matter'; import matter from 'gray-matter';
import { fileURLToPath } from 'url'; import { fileURLToPath } from 'url';
import type { Plugin, ViteDevServer } from 'vite'; import type { Plugin, ViteDevServer } from 'vite';
import type { AstroConfig } from '../@types/astro'; import type { AstroSettings } from '../@types/astro';
import { pagesVirtualModuleId } from '../core/app/index.js'; import { pagesVirtualModuleId } from '../core/app/index.js';
import { cachedCompilation, CompileProps } from '../core/compile/index.js'; import { cachedCompilation, CompileProps } from '../core/compile/index.js';
import { collectErrorMetadata } from '../core/errors.js'; import { collectErrorMetadata } from '../core/errors.js';
@ -19,7 +19,7 @@ import {
} from '../vite-style-transform/index.js'; } from '../vite-style-transform/index.js';
interface AstroPluginOptions { interface AstroPluginOptions {
config: AstroConfig; settings: AstroSettings;
logging: LogOptions; logging: LogOptions;
} }
@ -38,7 +38,8 @@ function safeMatter(source: string, id: string) {
// TODO: Clean up some of the shared logic between this Markdown plugin and the Astro plugin. // TODO: Clean up some of the shared logic between this Markdown plugin and the Astro plugin.
// Both end up connecting a `load()` hook to the Astro compiler, and share some copy-paste // Both end up connecting a `load()` hook to the Astro compiler, and share some copy-paste
// logic in how that is done. // logic in how that is done.
export default function markdown({ config, logging }: AstroPluginOptions): Plugin { export default function markdown({ settings }: AstroPluginOptions): Plugin {
const { config } = settings;
function normalizeFilename(filename: string) { function normalizeFilename(filename: string) {
if (filename.startsWith('/@fs')) { if (filename.startsWith('/@fs')) {
filename = filename.slice('/@fs'.length); filename = filename.slice('/@fs'.length);

View file

@ -2,7 +2,7 @@ import { renderMarkdown } from '@astrojs/markdown-remark';
import fs from 'fs'; import fs from 'fs';
import matter from 'gray-matter'; import matter from 'gray-matter';
import type { Plugin } from 'vite'; import type { Plugin } from 'vite';
import type { AstroConfig } from '../@types/astro'; import type { AstroSettings } from '../@types/astro';
import { collectErrorMetadata } from '../core/errors.js'; import { collectErrorMetadata } from '../core/errors.js';
import type { LogOptions } from '../core/logger/core.js'; import type { LogOptions } from '../core/logger/core.js';
import { warn } from '../core/logger/core.js'; import { warn } from '../core/logger/core.js';
@ -10,7 +10,7 @@ import type { PluginMetadata } from '../vite-plugin-astro/types.js';
import { getFileInfo, safelyGetAstroData } from '../vite-plugin-utils/index.js'; import { getFileInfo, safelyGetAstroData } from '../vite-plugin-utils/index.js';
interface AstroPluginOptions { interface AstroPluginOptions {
config: AstroConfig; settings: AstroSettings;
logging: LogOptions; logging: LogOptions;
} }
@ -23,7 +23,7 @@ function safeMatter(source: string, id: string) {
} }
} }
export default function markdown({ config, logging }: AstroPluginOptions): Plugin { export default function markdown({ settings, logging }: AstroPluginOptions): Plugin {
return { return {
enforce: 'pre', enforce: 'pre',
name: 'astro:markdown', name: 'astro:markdown',
@ -33,11 +33,11 @@ export default function markdown({ config, logging }: AstroPluginOptions): Plugi
// to escape "import.meta.env" ourselves. // to escape "import.meta.env" ourselves.
async load(id) { async load(id) {
if (id.endsWith('.md')) { if (id.endsWith('.md')) {
const { fileId, fileUrl } = getFileInfo(id, config); const { fileId, fileUrl } = getFileInfo(id, settings.config);
const rawFile = await fs.promises.readFile(fileId, 'utf-8'); const rawFile = await fs.promises.readFile(fileId, 'utf-8');
const raw = safeMatter(rawFile, id); const raw = safeMatter(rawFile, id);
const renderResult = await renderMarkdown(raw.content, { const renderResult = await renderMarkdown(raw.content, {
...config.markdown, ...settings.config.markdown,
fileURL: new URL(`file://${fileId}`), fileURL: new URL(`file://${fileId}`),
isAstroFlavoredMd: false, isAstroFlavoredMd: false,
} as any); } as any);

View file

@ -1,5 +1,5 @@
import { ConfigEnv, Plugin as VitePlugin } from 'vite'; import { ConfigEnv, Plugin as VitePlugin } from 'vite';
import { AstroConfig, InjectedScriptStage } from '../@types/astro.js'; import { AstroSettings, InjectedScriptStage } from '../@types/astro.js';
// NOTE: We can't use the virtual "\0" ID convention because we need to // NOTE: We can't use the virtual "\0" ID convention because we need to
// inject these as ESM imports into actual code, where they would not // inject these as ESM imports into actual code, where they would not
@ -11,7 +11,7 @@ export const BEFORE_HYDRATION_SCRIPT_ID = `${SCRIPT_ID_PREFIX}${
export const PAGE_SCRIPT_ID = `${SCRIPT_ID_PREFIX}${'page' as InjectedScriptStage}.js`; export const PAGE_SCRIPT_ID = `${SCRIPT_ID_PREFIX}${'page' as InjectedScriptStage}.js`;
export const PAGE_SSR_SCRIPT_ID = `${SCRIPT_ID_PREFIX}${'page-ssr' as InjectedScriptStage}.js`; export const PAGE_SSR_SCRIPT_ID = `${SCRIPT_ID_PREFIX}${'page-ssr' as InjectedScriptStage}.js`;
export default function astroScriptsPlugin({ config }: { config: AstroConfig }): VitePlugin { export default function astroScriptsPlugin({ settings }: { settings: AstroSettings }): VitePlugin {
let env: ConfigEnv | undefined = undefined; let env: ConfigEnv | undefined = undefined;
return { return {
name: 'astro:scripts', name: 'astro:scripts',
@ -29,19 +29,19 @@ export default function astroScriptsPlugin({ config }: { config: AstroConfig }):
async load(id) { async load(id) {
if (id === BEFORE_HYDRATION_SCRIPT_ID) { if (id === BEFORE_HYDRATION_SCRIPT_ID) {
return config._ctx.scripts return settings.scripts
.filter((s) => s.stage === 'before-hydration') .filter((s) => s.stage === 'before-hydration')
.map((s) => s.content) .map((s) => s.content)
.join('\n'); .join('\n');
} }
if (id === PAGE_SCRIPT_ID) { if (id === PAGE_SCRIPT_ID) {
return config._ctx.scripts return settings.scripts
.filter((s) => s.stage === 'page') .filter((s) => s.stage === 'page')
.map((s) => s.content) .map((s) => s.content)
.join('\n'); .join('\n');
} }
if (id === PAGE_SSR_SCRIPT_ID) { if (id === PAGE_SSR_SCRIPT_ID) {
return config._ctx.scripts return settings.scripts
.filter((s) => s.stage === 'page-ssr') .filter((s) => s.stage === 'page-ssr')
.map((s) => s.content) .map((s) => s.content)
.join('\n'); .join('\n');
@ -49,7 +49,7 @@ export default function astroScriptsPlugin({ config }: { config: AstroConfig }):
return null; return null;
}, },
buildStart(options) { buildStart(options) {
const hasHydrationScripts = config._ctx.scripts.some((s) => s.stage === 'before-hydration'); const hasHydrationScripts = settings.scripts.some((s) => s.stage === 'before-hydration');
if (hasHydrationScripts && env?.command === 'build' && !env?.ssrBuild) { if (hasHydrationScripts && env?.command === 'build' && !env?.ssrBuild) {
this.emitFile({ this.emitFile({
type: 'chunk', type: 'chunk',

View file

@ -1,17 +1,17 @@
import { Plugin as VitePlugin } from 'vite'; import { Plugin as VitePlugin } from 'vite';
import { AstroConfig } from '../@types/astro.js'; import { AstroSettings } from '../@types/astro.js';
import { PAGE_SSR_SCRIPT_ID } from './index.js'; import { PAGE_SSR_SCRIPT_ID } from './index.js';
import ancestor from 'common-ancestor-path'; import ancestor from 'common-ancestor-path';
import MagicString from 'magic-string'; import MagicString from 'magic-string';
import { isPage } from '../core/util.js'; import { isPage } from '../core/util.js';
export default function astroScriptsPostPlugin({ config }: { config: AstroConfig }): VitePlugin { export default function astroScriptsPostPlugin({ settings }: { settings: AstroSettings }): VitePlugin {
function normalizeFilename(filename: string) { function normalizeFilename(filename: string) {
if (filename.startsWith('/@fs')) { if (filename.startsWith('/@fs')) {
filename = filename.slice('/@fs'.length); filename = filename.slice('/@fs'.length);
} else if (filename.startsWith('/') && !ancestor(filename, config.root.pathname)) { } else if (filename.startsWith('/') && !ancestor(filename, settings.config.root.pathname)) {
filename = new URL('.' + filename, config.root).pathname; filename = new URL('.' + filename, settings.config.root).pathname;
} }
return filename; return filename;
} }
@ -23,7 +23,7 @@ export default function astroScriptsPostPlugin({ config }: { config: AstroConfig
transform(this, code, id, options) { transform(this, code, id, options) {
if (!options?.ssr) return; if (!options?.ssr) return;
const hasInjectedScript = config._ctx.scripts.some((s) => s.stage === 'page-ssr'); const hasInjectedScript = settings.scripts.some((s) => s.stage === 'page-ssr');
if (!hasInjectedScript) return; if (!hasInjectedScript) return;
const filename = normalizeFilename(id); const filename = normalizeFilename(id);
@ -35,7 +35,7 @@ export default function astroScriptsPostPlugin({ config }: { config: AstroConfig
return; return;
} }
const fileIsPage = isPage(fileURL, config); const fileIsPage = isPage(fileURL, settings);
if (!fileIsPage) return; if (!fileIsPage) return;
const s = new MagicString(code, { filename }); const s = new MagicString(code, { filename });

View file

@ -3,7 +3,7 @@ import { load as cheerioLoad } from 'cheerio';
import { loadFixture } from './test-utils.js'; import { loadFixture } from './test-utils.js';
import testAdapter from './test-adapter.js'; import testAdapter from './test-adapter.js';
describe('AstroConfig - config.mode', () => { describe('AstroConfig - config.output', () => {
describe(`output: 'server'`, () => { describe(`output: 'server'`, () => {
describe('deploy config provided', () => { describe('deploy config provided', () => {
/** @type {import('./test-utils').Fixture} */ /** @type {import('./test-utils').Fixture} */
@ -58,7 +58,7 @@ describe('AstroConfig - config.mode', () => {
}); });
describe(`output: 'static'`, () => { describe(`output: 'static'`, () => {
describe('Deploy config omitted', () => { describe('Output config omitted', () => {
/** @type {import('./test-utils').Fixture} */ /** @type {import('./test-utils').Fixture} */
let fixture; let fixture;

View file

@ -1,126 +0,0 @@
import { expect } from 'chai';
import { loadFixture, cliServerLogSetup } from './test-utils.js';
import { fileURLToPath } from 'url';
import { isIPv4 } from 'net';
describe('config', () => {
let hostFixture;
let portFixture;
before(async () => {
[hostFixture, portFixture] = await Promise.all([
loadFixture({
root: './fixtures/config-host/',
server: {
host: true,
},
}),
loadFixture({
root: './fixtures/config-host/',
server: {
port: 5006,
},
}),
]);
});
describe('host', () => {
it('can be specified in astro.config.mjs', async () => {
expect(hostFixture.config.server.host).to.equal(true);
});
it('can be specified via --host flag', async () => {
const projectRootURL = new URL('./fixtures/astro-basic/', import.meta.url);
const { network } = await cliServerLogSetup([
'--root',
fileURLToPath(projectRootURL),
'--host',
]);
const networkURL = new URL(network);
expect(isIPv4(networkURL.hostname)).to.be.equal(
true,
`Expected network URL to respect --host flag`
);
});
});
describe('path', () => {
it('can be passed via --config', async () => {
const projectRootURL = new URL('./fixtures/astro-basic/', import.meta.url);
const configFileURL = new URL('./fixtures/config-path/config/my-config.mjs', import.meta.url);
const { network } = await cliServerLogSetup([
'--root',
fileURLToPath(projectRootURL),
'--config',
configFileURL.pathname,
]);
const networkURL = new URL(network);
expect(isIPv4(networkURL.hostname)).to.be.equal(
true,
`Expected network URL to respect --host flag`
);
});
});
describe('relative path', () => {
it('can be passed via relative --config', async () => {
const projectRootURL = new URL('./fixtures/astro-basic/', import.meta.url);
const configFileURL = 'my-config.mjs';
const { local } = await cliServerLogSetup([
'--root',
fileURLToPath(projectRootURL),
'--config',
configFileURL,
]);
const localURL = new URL(local);
expect(localURL.port).to.equal('8080');
});
});
describe('relative path with leading ./', () => {
it('can be passed via relative --config', async () => {
const projectRootURL = new URL('./fixtures/astro-basic/', import.meta.url);
const configFileURL = './my-config.mjs';
const { local } = await cliServerLogSetup([
'--root',
fileURLToPath(projectRootURL),
'--config',
configFileURL,
]);
const localURL = new URL(local);
expect(localURL.port).to.equal('8080');
});
});
describe('incorrect path', () => {
it('fails and exits when config does not exist', async () => {
const projectRootURL = new URL('./fixtures/astro-basic/', import.meta.url);
const configFileURL = './does-not-exist.mjs';
let exit = 0;
try {
await cliServerLogSetup([
'--root',
fileURLToPath(projectRootURL),
'--config',
configFileURL,
]);
} catch (e) {
if (e.message.includes('Unable to resolve --config')) {
exit = 1;
}
}
expect(exit).to.equal(1, 'Throws helpful error message when --config does not exist');
});
});
describe('port', () => {
it('can be specified in astro.config.mjs', async () => {
expect(portFixture.config.server.port).to.deep.equal(5006);
});
});
});

View file

@ -2,7 +2,8 @@ import { execa } from 'execa';
import { polyfill } from '@astrojs/webapi'; import { polyfill } from '@astrojs/webapi';
import fs from 'fs'; import fs from 'fs';
import { fileURLToPath } from 'url'; import { fileURLToPath } from 'url';
import { loadConfig } from '../dist/core/config.js'; import { loadConfig } from '../dist/core/config/config.js';
import { createSettings, loadTSConfig } from '../dist/core/config/index.js';
import dev from '../dist/core/dev/index.js'; import dev from '../dist/core/dev/index.js';
import build from '../dist/core/build/index.js'; import build from '../dist/core/build/index.js';
import preview from '../dist/core/preview/index.js'; import preview from '../dist/core/preview/index.js';
@ -18,7 +19,7 @@ polyfill(globalThis, {
/** /**
* @typedef {import('node-fetch').Response} Response * @typedef {import('node-fetch').Response} Response
* @typedef {import('../src/core/dev/index').DevServer} DevServer * @typedef {import('../src/core/dev/index').DedvServer} DevServer
* @typedef {import('../src/@types/astro').AstroConfig} AstroConfig * @typedef {import('../src/@types/astro').AstroConfig} AstroConfig
* @typedef {import('../src/core/preview/index').PreviewServer} PreviewServer * @typedef {import('../src/core/preview/index').PreviewServer} PreviewServer
* @typedef {import('../src/core/app/index').App} App * @typedef {import('../src/core/app/index').App} App
@ -39,6 +40,12 @@ polyfill(globalThis, {
* @property {() => Promise<void>} onNextChange * @property {() => Promise<void>} onNextChange
*/ */
/** @type {import('../src/core/logger/core').LogOptions} */
export const defaultLogging = {
dest: nodeLogDestination,
level: 'error',
};
/** /**
* Load Astro fixture * Load Astro fixture
* @param {AstroConfig} inlineConfig Astro config partial (note: must specify `root`) * @param {AstroConfig} inlineConfig Astro config partial (note: must specify `root`)
@ -75,10 +82,7 @@ export async function loadFixture(inlineConfig) {
} }
/** @type {import('../src/core/logger/core').LogOptions} */ /** @type {import('../src/core/logger/core').LogOptions} */
const logging = { const logging = defaultLogging;
dest: nodeLogDestination,
level: 'error',
};
// Load the config. // Load the config.
let config = await loadConfig({ cwd: fileURLToPath(cwd), logging }); let config = await loadConfig({ cwd: fileURLToPath(cwd), logging });
@ -91,10 +95,16 @@ export async function loadFixture(inlineConfig) {
if (inlineConfig.base && !inlineConfig.base.endsWith('/')) { if (inlineConfig.base && !inlineConfig.base.endsWith('/')) {
config.base = inlineConfig.base + '/'; config.base = inlineConfig.base + '/';
} }
let tsconfig = loadTSConfig(fileURLToPath(cwd));
let settings = createSettings({
config,
tsConfig: tsconfig?.config,
tsConfigPath: tsconfig?.path
});
if (config.integrations.find((integration) => integration.name === '@astrojs/mdx')) { if (config.integrations.find((integration) => integration.name === '@astrojs/mdx')) {
// Enable default JSX integration. It needs to come first, so unshift rather than push! // Enable default JSX integration. It needs to come first, so unshift rather than push!
const { default: jsxRenderer } = await import('astro/jsx/renderer.js'); const { default: jsxRenderer } = await import('astro/jsx/renderer.js');
config._ctx.renderers.unshift(jsxRenderer); settings.renderers.unshift(jsxRenderer);
} }
/** @type {import('@astrojs/telemetry').AstroTelemetry} */ /** @type {import('@astrojs/telemetry').AstroTelemetry} */
@ -133,9 +143,9 @@ export async function loadFixture(inlineConfig) {
let devServer; let devServer;
return { return {
build: (opts = {}) => build(config, { logging, telemetry, ...opts }), build: (opts = {}) => build(settings, { logging, telemetry, ...opts }),
startDevServer: async (opts = {}) => { startDevServer: async (opts = {}) => {
devServer = await dev(config, { logging, telemetry, ...opts }); devServer = await dev(settings, { logging, telemetry, ...opts });
config.server.port = devServer.address.port; // update port config.server.port = devServer.address.port; // update port
return devServer; return devServer;
}, },
@ -143,7 +153,7 @@ export async function loadFixture(inlineConfig) {
resolveUrl, resolveUrl,
fetch: (url, init) => fetch(resolveUrl(url), init), fetch: (url, init) => fetch(resolveUrl(url), init),
preview: async (opts = {}) => { preview: async (opts = {}) => {
const previewServer = await preview(config, { logging, telemetry, ...opts }); const previewServer = await preview(settings, { logging, telemetry, ...opts });
return previewServer; return previewServer;
}, },
readFile: (filePath, encoding) => readFile: (filePath, encoding) =>

View file

@ -0,0 +1,71 @@
import { expect } from 'chai';
import { fileURLToPath } from 'url';
import { defaultLogging as logging } from '../../test-utils.js';
import { openConfig } from '../../../dist/core/config/index.js';
const cwd = fileURLToPath(new URL('../../fixtures/config-host/', import.meta.url));
describe('config.server', () => {
function openConfigWithFlags(flags) {
return openConfig({
cwd: flags.root || cwd,
flags,
cmd: 'dev',
logging
});
}
describe('host', () => {
it('can be specified via --host flag', async () => {
const projectRootURL = new URL('../../fixtures/astro-basic/', import.meta.url);
const { astroConfig } = await openConfigWithFlags({
root: fileURLToPath(projectRootURL),
host: true
});
expect(astroConfig.server.host).to.equal(true);
});
});
describe('config', () => {
describe('relative path', () => {
it('can be passed via relative --config', async () => {
const projectRootURL = new URL('../../fixtures/astro-basic/', import.meta.url);
const configFileURL = 'my-config.mjs';
const { astroConfig } = await openConfigWithFlags({
root: fileURLToPath(projectRootURL),
config: configFileURL
});
expect(astroConfig.server.port).to.equal(8080);
});
});
describe('relative path with leading ./', () => {
it('can be passed via relative --config', async () => {
const projectRootURL = new URL('../../fixtures/astro-basic/', import.meta.url);
const configFileURL = './my-config.mjs';
const { astroConfig } = await openConfigWithFlags({
root: fileURLToPath(projectRootURL),
config: configFileURL
});
expect(astroConfig.server.port).to.equal(8080);
});
});
describe('incorrect path', () => {
it('fails and exits when config does not exist', async () => {
const projectRootURL = new URL('../../fixtures/astro-basic/', import.meta.url);
const configFileURL = './does-not-exist.mjs';
try {
await openConfigWithFlags({
root: fileURLToPath(projectRootURL),
config: configFileURL
});
expect(false).to.equal(true, 'this should not have resolved');
} catch(err) {
expect(err.message).to.match(/Unable to resolve/);
}
});
});
});
})

View file

@ -1,8 +1,8 @@
import { expect } from 'chai'; import { expect } from 'chai';
import { z } from 'zod'; import { z } from 'zod';
import stripAnsi from 'strip-ansi'; import stripAnsi from 'strip-ansi';
import { formatConfigErrorMessage } from '../dist/core/messages.js'; import { formatConfigErrorMessage } from '../../../dist/core/messages.js';
import { validateConfig } from '../dist/core/config.js'; import { validateConfig } from '../../../dist/core/config/index.js';
describe('Config Validation', () => { describe('Config Validation', () => {
it('empty user config is valid', async () => { it('empty user config is valid', async () => {