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

View file

@ -1,6 +1,6 @@
/* eslint-disable no-console */
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 * as fs from 'fs';
@ -16,10 +16,10 @@ interface Result {
hints: number;
}
export async function check(astroConfig: AstroConfig) {
export async function check(settings: AstroSettings) {
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();

View file

@ -7,7 +7,7 @@ import yargs from 'yargs-parser';
import { z } from 'zod';
import add from '../core/add/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 { collectErrorMetadata } from '../core/errors.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,
flags,
cmd,
@ -159,8 +159,14 @@ async function runCommand(cmd: string, flags: yargs.Arguments) {
await handleConfigError(e, { cwd: root, flags, logging });
return {} as any;
});
if (!astroConfig) return;
telemetry.record(event.eventCliSession(cmd, userConfig, flags));
if (!initialAstroConfig) return;
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:
// 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) {
case 'dev': {
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;
const configFlag = resolveFlags(flags).config;
const configFlagPath = configFlag
@ -199,7 +205,13 @@ async function runCommand(cmd: string, flags: yargs.Arguments) {
isConfigReload: true,
});
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 startDevServer({ isRestart: true });
} catch (e) {
@ -220,16 +232,16 @@ async function runCommand(cmd: string, flags: yargs.Arguments) {
}
case 'build': {
return await build(astroConfig, { logging, telemetry });
return await build(settings, { logging, telemetry });
}
case 'check': {
const ret = await check(astroConfig);
const ret = await check(settings);
return process.exit(ret);
}
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
}
}

View file

@ -10,7 +10,7 @@ import preferredPM from 'preferred-pm';
import prompts from 'prompts';
import { fileURLToPath, pathToFileURL } from 'url';
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 * as msg 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 type {
AstroConfig,
AstroSettings,
ComponentInstance,
EndpointHandler,
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 (
// Drafts are disabled
!astroConfig.markdown.drafts &&
!settings.config.markdown.drafts &&
// This is a draft post
'frontmatter' in pageModule &&
(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.
// ie, src/pages/index.astro instead of /Users/name..../src/pages/index.astro
export function rootRelativeFacadeId(facadeId: string, astroConfig: AstroConfig): string {
return facadeId.slice(fileURLToPath(astroConfig.root).length);
export function rootRelativeFacadeId(facadeId: string, settings: AstroSettings): string {
return facadeId.slice(fileURLToPath(settings.config.root).length);
}
// Determines of a Rollup chunk is an entrypoint page.
export function chunkIsPage(
astroConfig: AstroConfig,
settings: AstroSettings,
output: OutputAsset | OutputChunk,
internals: BuildInternals
) {
@ -90,7 +91,7 @@ export function chunkIsPage(
const chunk = output as OutputChunk;
if (chunk.facadeModuleId) {
const facadeToEntryId = prependForwardSlash(
rootRelativeFacadeId(chunk.facadeModuleId, astroConfig)
rootRelativeFacadeId(chunk.facadeModuleId, settings)
);
return internals.entrySpecifierToBundleMap.has(facadeToEntryId);
}
@ -101,9 +102,9 @@ export async function generatePages(opts: StaticBuildOptions, internals: BuildIn
const timer = performance.now();
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 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 ssrEntry = await import(ssrEntryURL.toString());
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}`);
return;
}
@ -163,7 +164,7 @@ async function generatePage(
const timeEnd = performance.now();
const timeChange = getTimeStat(timeStart, timeEnd);
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 ? '└─' : '├─';
info(opts.logging, null, ` ${cyan(lineIcon)} ${dim(filePath)} ${dim(timeIncrease)}`);
}
@ -186,7 +187,7 @@ async function getPathsForRoute(
route: pageData.route,
isValidate: false,
logging: opts.logging,
ssr: opts.astroConfig.output === 'server',
ssr: opts.settings.config.output === 'server',
})
.then((_result) => {
const label = _result.staticPaths.length === 1 ? 'page' : 'pages';
@ -262,8 +263,8 @@ function shouldAppendForwardSlash(
}
function addPageName(pathname: string, opts: StaticBuildOptions): void {
const trailingSlash = opts.astroConfig.trailingSlash;
const buildFormat = opts.astroConfig.build.format;
const trailingSlash = opts.settings.config.trailingSlash;
const buildFormat = opts.settings.config.build.format;
const pageName = shouldAppendForwardSlash(trailingSlash, buildFormat)
? pathname.replace(/\/?$/, '/').replace(/^\//, '')
: pathname.replace(/^\//, '');
@ -303,7 +304,7 @@ async function generatePath(
opts: StaticBuildOptions,
gopts: GeneratePathOptions
) {
const { astroConfig, logging, origin, routeCache } = opts;
const { settings, logging, origin, routeCache } = opts;
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.
@ -316,18 +317,18 @@ async function generatePath(
// 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.
const site =
astroConfig.base !== '/'
? joinPaths(astroConfig.site?.toString() || 'http://localhost/', astroConfig.base)
: astroConfig.site;
settings.config.base !== '/'
? joinPaths(settings.config.site?.toString() || 'http://localhost/', settings.config.base)
: settings.config.site;
const links = createLinkStylesheetElementSet(linkIds, 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);
if (typeof hashedFilePath !== 'string') {
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({
props: { type: 'module', src },
children: '',
@ -335,7 +336,7 @@ async function generatePath(
}
// 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') {
scripts.add({
props: {},
@ -344,12 +345,12 @@ async function generatePath(
}
}
const ssr = opts.astroConfig.output === 'server';
const ssr = settings.config.output === 'server';
const url = getUrlForPath(
pathname,
opts.astroConfig.base,
opts.settings.config.base,
origin,
opts.astroConfig.build.format,
opts.settings.config.build.format,
pageData.route.type
);
const options: RenderOptions = {
@ -357,8 +358,8 @@ async function generatePath(
links,
logging,
markdown: {
...astroConfig.markdown,
isAstroFlavoredMd: astroConfig.legacy.astroFlavoredMarkdown,
...settings.config.markdown,
isAstroFlavoredMd: settings.config.legacy.astroFlavoredMarkdown,
},
mod,
mode: opts.mode,
@ -376,14 +377,14 @@ async function generatePath(
}
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 }),
route: pageData.route,
routeCache,
site: astroConfig.site
? new URL(astroConfig.base, astroConfig.site).toString()
: astroConfig.site,
site: settings.config.site
? new URL(settings.config.base, settings.config.site).toString()
: settings.config.site,
ssr,
streaming: true,
};
@ -409,8 +410,8 @@ async function generatePath(
body = await response.text();
}
const outFolder = getOutFolder(astroConfig, pathname, pageData.route.type);
const outFile = getOutFile(astroConfig, outFolder, pathname, pageData.route.type);
const outFolder = getOutFolder(settings.config, pathname, pageData.route.type);
const outFile = getOutFile(settings.config, outFolder, pathname, pageData.route.type);
pageData.route.distURL = outFile;
await fs.promises.mkdir(outFolder, { recursive: true });
await fs.promises.writeFile(outFile, body, encoding ?? 'utf-8');

View file

@ -1,5 +1,5 @@
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 fs from 'fs';
@ -28,14 +28,14 @@ export interface BuildOptions {
}
/** `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();
const builder = new AstroBuilder(config, options);
const builder = new AstroBuilder(settings, options);
await builder.run();
}
class AstroBuilder {
private config: AstroConfig;
private settings: AstroSettings;
private logging: LogOptions;
private mode: RuntimeMode = 'production';
private origin: string;
@ -43,16 +43,16 @@ class AstroBuilder {
private manifest: ManifestData;
private timer: Record<string, number>;
constructor(config: AstroConfig, options: BuildOptions) {
constructor(settings: AstroSettings, options: BuildOptions) {
if (options.mode) {
this.mode = options.mode;
}
this.config = config;
this.settings = settings;
this.logging = options.logging;
this.routeCache = new RouteCache(this.logging);
this.origin = config.site
? new URL(config.site).origin
: `http://localhost:${config.server.port}`;
this.origin = settings.config.site
? new URL(settings.config.site).origin
: `http://localhost:${settings.config.server.port}`;
this.manifest = { routes: [] };
this.timer = {};
}
@ -62,8 +62,8 @@ class AstroBuilder {
debug('build', 'Initial setup...');
const { logging } = this;
this.timer.init = performance.now();
this.config = await runHookConfigSetup({ config: this.config, command: 'build', logging });
this.manifest = createRouteManifest({ config: this.config }, this.logging);
this.settings = await runHookConfigSetup({ settings: this.settings, command: 'build', logging });
this.manifest = createRouteManifest({ settings: this.settings }, this.logging);
const viteConfig = await createVite(
{
@ -73,29 +73,29 @@ class AstroBuilder {
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 };
}
/** Run the build logic. build() is marked private because usage should go through ".run()" */
private async build({ viteConfig }: { viteConfig: ViteConfigWithSSR }) {
const buildConfig: BuildConfig = {
client: new URL('./client/', this.config.outDir),
server: new URL('./server/', this.config.outDir),
client: new URL('./client/', this.settings.config.outDir),
server: new URL('./server/', this.settings.config.outDir),
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)}`);
if (this.config._ctx.adapter) {
info(this.logging, 'build', `deploy adapter: ${colors.green(this.config._ctx.adapter.name)}`);
info(this.logging, 'build', `output target: ${colors.green(this.settings.config.output)}`);
if (this.settings.adapter) {
info(this.logging, 'build', `deploy adapter: ${colors.green(this.settings.adapter.name)}`);
}
info(this.logging, 'build', 'Collecting build info...');
this.timer.loadStart = performance.now();
const { assets, allPages } = await collectPagesData({
astroConfig: this.config,
settings: this.settings,
logging: this.logging,
manifest: this.manifest,
});
@ -116,7 +116,7 @@ class AstroBuilder {
await staticBuild({
allPages,
astroConfig: this.config,
settings: this.settings,
logging: this.logging,
manifest: this.manifest,
mode: this.mode,
@ -140,7 +140,7 @@ class AstroBuilder {
// You're done! Time to clean up.
await runHookBuildDone({
config: this.config,
config: this.settings.config,
buildConfig,
pages: pageNames,
routes: Object.values(allPages).map((pd) => pd.route),
@ -152,7 +152,7 @@ class AstroBuilder {
logging: this.logging,
timeStart: this.timer.init,
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 { info } from '../logger/core.js';
import type { AllPagesData } from './types';
@ -7,7 +7,7 @@ import * as colors from 'kleur/colors';
import { debug } from '../logger/core.js';
export interface CollectPagesDataOptions {
astroConfig: AstroConfig;
settings: AstroSettings;
logging: LogOptions;
manifest: ManifestData;
}
@ -21,7 +21,7 @@ export interface CollectPagesDataResult {
export async function collectPagesData(
opts: CollectPagesDataOptions
): Promise<CollectPagesDataResult> {
const { astroConfig, manifest } = opts;
const { settings, manifest } = opts;
const assets: Record<string, string> = {};
const allPages: AllPagesData = {};
@ -58,7 +58,7 @@ export async function collectPagesData(
};
clearInterval(routeCollectionLogTimeout);
if (astroConfig.output === 'static') {
if (settings.config.output === 'static') {
const html = `${route.pathname}`.replace(/\/?$/, '/index.html');
debug(
'build',

View file

@ -23,10 +23,10 @@ import { vitePluginPages } from './vite-plugin-pages.js';
import { injectManifest, vitePluginSSR } from './vite-plugin-ssr.js';
export async function staticBuild(opts: StaticBuildOptions) {
const { allPages, astroConfig } = opts;
const { allPages, settings } = opts;
// Verify this app is buildable.
if (isModeServerWithNoAdapter(opts.astroConfig)) {
if (isModeServerWithNoAdapter(opts.settings)) {
throw new Error(`Cannot use \`output: 'server'\` without an adapter.
Install and configure the appropriate server adapter for your final deployment.
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();
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);
// 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
// but because we are running 2 vite builds in parallel, that would cause a race
// 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.)
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);
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)
.filter((a) => typeof a === 'string') as string[];
@ -87,7 +87,7 @@ Learn more: https://docs.astro.build/en/guides/server-side-rendering/
...internals.discoveredScripts,
]);
if (astroConfig._ctx.scripts.some((script) => script.stage === 'page')) {
if (settings.scripts.some((script) => script.stage === 'page')) {
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);
timer.generate = performance.now();
if (astroConfig.output === 'static') {
if (settings.config.output === 'static') {
await generatePages(opts, internals);
await cleanSsrOutput(opts);
} 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>) {
const { astroConfig, viteConfig } = opts;
const ssr = astroConfig.output === 'server';
const out = ssr ? opts.buildConfig.server : getOutDirWithinCwd(astroConfig.outDir);
const { settings, viteConfig } = opts;
const ssr = settings.config.output === 'server';
const out = ssr ? opts.buildConfig.server : getOutDirWithinCwd(settings.config.outDir);
const viteBuildConfig: ViteConfigWithSSR = {
...viteConfig,
@ -148,21 +148,20 @@ async function ssrBuild(opts: StaticBuildOptions, internals: BuildInternals, inp
buildOptions: opts,
internals,
target: 'server',
astroConfig,
}),
...(viteConfig.plugins || []),
// SSR needs to be last
opts.astroConfig.output === 'server' &&
vitePluginSSR(internals, opts.astroConfig._ctx.adapter!),
settings.config.output === 'server' &&
vitePluginSSR(internals, settings.adapter!),
vitePluginAnalyzer(internals),
],
publicDir: ssr ? false : viteConfig.publicDir,
envPrefix: 'PUBLIC_',
base: astroConfig.base,
base: settings.config.base,
};
await runHookBuildSetup({
config: astroConfig,
config: settings.config,
pages: internals.pagesByComponent,
vite: viteBuildConfig,
target: 'server',
@ -177,16 +176,16 @@ async function clientBuild(
internals: BuildInternals,
input: Set<string>
) {
const { astroConfig, viteConfig } = opts;
const { settings, viteConfig } = opts;
const timer = performance.now();
const ssr = astroConfig.output === 'server';
const out = ssr ? opts.buildConfig.client : astroConfig.outDir;
const ssr = settings.config.output === 'server';
const out = ssr ? opts.buildConfig.client : settings.config.outDir;
// Nothing to do if there is no client-side JS.
if (!input.size) {
// If SSR, copy public over
if (ssr) {
await copyFiles(astroConfig.publicDir, out);
await copyFiles(settings.config.publicDir, out);
}
return null;
@ -219,21 +218,20 @@ async function clientBuild(
},
plugins: [
vitePluginInternals(input, internals),
vitePluginHoistedScripts(astroConfig, internals),
vitePluginHoistedScripts(settings, internals),
rollupPluginAstroBuildCSS({
buildOptions: opts,
internals,
target: 'client',
astroConfig,
}),
...(viteConfig.plugins || []),
],
envPrefix: 'PUBLIC_',
base: astroConfig.base,
base: settings.config.base,
} as ViteConfigWithSSR;
await runHookBuildSetup({
config: astroConfig,
config: settings.config,
pages: internals.pagesByComponent,
vite: viteBuildConfig,
target: 'client',
@ -246,9 +244,9 @@ async function clientBuild(
}
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
if (out.toString() !== opts.astroConfig.outDir.toString()) {
if (out.toString() !== opts.settings.config.outDir.toString()) {
await fs.promises.rm(out, { recursive: true });
return;
}
@ -284,7 +282,7 @@ async function copyFiles(fromFolder: URL, toFolder: URL) {
async function ssrMoveAssets(opts: StaticBuildOptions) {
info(opts.logging, 'build', 'Rearranging server assets...');
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 serverAssets = new URL('./assets/', serverRoot);
const clientAssets = new URL('./assets/', clientRoot);

View file

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

View file

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

View file

@ -1,5 +1,5 @@
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 { viteID } from '../util.js';
import { getPageDataByViteID } from './internal.js';
@ -9,7 +9,7 @@ function virtualHoistedEntry(id: string) {
}
export function vitePluginHoistedScripts(
astroConfig: AstroConfig,
settings: AstroSettings,
internals: BuildInternals
): VitePlugin {
return {
@ -40,7 +40,7 @@ export function vitePluginHoistedScripts(
},
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.
// 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 pages = internals.hoistedScriptIdToPagesMap.get(facadeId)!;
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);
if (pageInfo) {
if (canBeInlined) {

View file

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

View file

@ -103,7 +103,7 @@ export async function injectManifest(buildOpts: StaticBuildOptions, internals: B
const staticFiles = internals.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 code = chunk.code;
@ -120,11 +120,11 @@ function buildManifest(
internals: BuildInternals,
staticFiles: string[]
): SerializedSSRManifest {
const { astroConfig } = opts;
const { settings } = opts;
const routes: SerializedRouteInfo[] = [];
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]);
}
@ -133,7 +133,7 @@ function buildManifest(
if (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] });
}
@ -142,11 +142,11 @@ function buildManifest(
links: sortedCSS(pageData),
scripts: [
...scripts,
...astroConfig._ctx.scripts
...settings.scripts
.filter((script) => script.stage === 'head-inline')
.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 = {
adapterName: opts.astroConfig._ctx.adapter!.name,
adapterName: opts.settings.adapter!.name,
routes,
site: astroConfig.site,
base: astroConfig.base,
site: settings.config.site,
base: settings.config.base,
markdown: {
...astroConfig.markdown,
isAstroFlavoredMd: astroConfig.legacy.astroFlavoredMarkdown,
...settings.config.markdown,
isAstroFlavoredMd: settings.config.legacy.astroFlavoredMarkdown,
},
pageMap: null as any,
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 { 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 loadTypeScript from '@proload/plugin-tsm';
import * as colors from 'kleur/colors';
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 * as vite from 'vite';
import { mergeConfig as mergeViteConfig } from 'vite';
import { z } from 'zod';
import jsxRenderer from '../jsx/renderer.js';
import { LogOptions } from './logger/core.js';
import { appendForwardSlash, prependForwardSlash, trimSlashes } from './path.js';
import { arraify, isObject } from './util.js';
import { LogOptions } from '../logger/core.js';
import { arraify, isObject } from '../util.js';
import { createRelativeSchema } from './schema.js';
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([
'projectRoot',
'src',
@ -97,146 +27,6 @@ export const LEGACY_ASTRO_CONFIG_KEYS = new Set([
'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 */
export async function validateConfig(
@ -294,72 +84,10 @@ export async function validateConfig(
}
/* eslint-enable no-console */
// 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({}),
});
const tsconfig = loadTSConfig(root);
const AstroConfigRelativeSchema = createRelativeSchema(cmd, fileProtocolRoot);
// First-Pass Validation
const result = {
...(await AstroConfigRelativeSchema.parseAsync(userConfig)),
_ctx: {
pageExtensions: ['.astro', '.md', '.html'],
tsConfig: tsconfig?.config,
tsConfigPath: tsconfig?.path,
scripts: [],
renderers: [jsxRenderer],
injectedRoutes: [],
adapter: undefined,
},
};
const result = await AstroConfigRelativeSchema.parseAsync(userConfig);
// If successful, return the result as a verified AstroConfig object.
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
* @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 fs from 'fs';
@ -25,7 +25,7 @@ import { resolveDependency } from './util.js';
export type ViteConfigWithSSR = vite.InlineConfig & { ssr?: vite.SSROptions };
interface CreateViteOptions {
astroConfig: AstroConfig;
settings: AstroSettings;
logging: LogOptions;
mode: 'dev' | 'build' | string;
}
@ -59,12 +59,12 @@ function getSsrNoExternalDeps(projectRoot: URL): string[] {
/** Return a common starting point for all Vite actions */
export async function createVite(
commandConfig: ViteConfigWithSSR,
{ astroConfig, logging, mode }: CreateViteOptions
{ settings, logging, mode }: CreateViteOptions
): Promise<ViteConfigWithSSR> {
const thirdPartyAstroPackages = await getAstroPackages(astroConfig);
const thirdPartyAstroPackages = await getAstroPackages(settings);
// Start with the Vite configuration that Astro core needs
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
logLevel: 'warn', // log warnings and errors only
appType: 'custom',
@ -73,27 +73,27 @@ export async function createVite(
exclude: ['node-fetch'],
},
plugins: [
configAliasVitePlugin({ config: astroConfig }),
astroVitePlugin({ config: astroConfig, logging }),
astroScriptsPlugin({ config: astroConfig }),
configAliasVitePlugin({ settings }),
astroVitePlugin({ settings, logging }),
astroScriptsPlugin({ settings }),
// 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.
mode !== 'build' && astroViteServerPlugin({ config: astroConfig, logging }),
envVitePlugin({ config: astroConfig }),
astroConfig.legacy.astroFlavoredMarkdown
? legacyMarkdownVitePlugin({ config: astroConfig, logging })
: markdownVitePlugin({ config: astroConfig, logging }),
mode !== 'build' && astroViteServerPlugin({ settings, logging }),
envVitePlugin({ settings }),
settings.config.legacy.astroFlavoredMarkdown
? legacyMarkdownVitePlugin({ settings, logging })
: markdownVitePlugin({ settings, logging }),
htmlVitePlugin(),
jsxVitePlugin({ config: astroConfig, logging }),
astroPostprocessVitePlugin({ config: astroConfig }),
astroIntegrationsContainerPlugin({ config: astroConfig, logging }),
astroScriptsPageSSRPlugin({ config: astroConfig }),
jsxVitePlugin({ settings, logging }),
astroPostprocessVitePlugin({ settings }),
astroIntegrationsContainerPlugin({ settings, logging }),
astroScriptsPageSSRPlugin({ settings }),
],
publicDir: fileURLToPath(astroConfig.publicDir),
root: fileURLToPath(astroConfig.root),
publicDir: fileURLToPath(settings.config.publicDir),
root: fileURLToPath(settings.config.root),
envPrefix: 'PUBLIC_',
define: {
'import.meta.env.SITE': astroConfig.site ? `'${astroConfig.site}'` : 'undefined',
'import.meta.env.SITE': settings.config.site ? `'${settings.config.site}'` : 'undefined',
},
server: {
hmr:
@ -110,7 +110,7 @@ export async function createVite(
},
},
css: {
postcss: astroConfig.style.postcss || {},
postcss: settings.config.style.postcss || {},
},
resolve: {
alias: [
@ -129,7 +129,7 @@ export async function createVite(
conditions: ['astro'],
},
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
// 4. command vite config, passed as the argument to this function
let result = commonConfig;
result = vite.mergeConfig(result, astroConfig.vite || {});
result = vite.mergeConfig(result, settings.config.vite || {});
result = vite.mergeConfig(result, commandConfig);
if (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
// `.astro` files need to be built by Vite, so these should use `noExternal`
async function getAstroPackages({ root }: AstroConfig): Promise<string[]> {
const { astroPackages } = new DependencyWalker(root);
async function getAstroPackages(settings: AstroSettings): Promise<string[]> {
const { astroPackages } = new DependencyWalker(settings.config.root);
return astroPackages;
}

View file

@ -2,7 +2,7 @@ import type { AstroTelemetry } from '@astrojs/telemetry';
import type { AddressInfo } from 'net';
import { performance } from 'perf_hooks';
import * as vite from 'vite';
import type { AstroConfig } from '../../@types/astro';
import type { AstroSettings } from '../../@types/astro';
import {
runHookConfigDone,
runHookConfigSetup,
@ -28,17 +28,17 @@ export interface DevServer {
}
/** `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();
applyPolyfill();
await options.telemetry.record([]);
config = await runHookConfigSetup({ config, command: 'dev', logging: options.logging });
const { host, port } = config.server;
settings = await runHookConfigSetup({ settings, command: 'dev', logging: options.logging });
const { host, port } = settings.config.server;
const { isRestart = false } = options;
// The client entrypoint for renderers. Since these are imported dynamically
// we need to tell Vite to preoptimize them.
const rendererClientEntries = config._ctx.renderers
const rendererClientEntries = settings.renderers
.map((r) => r.clientEntrypoint)
.filter(Boolean) as string[];
@ -50,21 +50,21 @@ export default async function dev(config: AstroConfig, options: DevOptions): Pro
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);
runHookServerSetup({ config, server: viteServer, logging: options.logging });
runHookServerSetup({ config: settings.config, server: viteServer, logging: options.logging });
await viteServer.listen(port);
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(
options.logging,
null,
msg.devStart({
startupTime: performance.now() - devStart,
config,
config: settings.config,
devServerAddressInfo,
site,
https: !!viteConfig.server?.https,
@ -80,7 +80,7 @@ export default async function dev(config: AstroConfig, options: DevOptions): Pro
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 {
address: devServerAddressInfo,
@ -89,7 +89,7 @@ export default async function dev(config: AstroConfig, options: DevOptions): Pro
},
stop: async () => {
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);
return await callEndpoint(mod as unknown as EndpointHandler, {
...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 { AddressInfo } from 'net';
import type { AstroConfig } from '../../@types/astro';
import type { AstroSettings } from '../../@types/astro';
import type { LogOptions } from '../logger/core';
import fs from 'fs';
@ -30,20 +30,20 @@ const HAS_FILE_EXTENSION_REGEXP = /^.*\.[^\\]+$/;
/** The primary dev action */
export default async function preview(
config: AstroConfig,
settings: AstroSettings,
{ logging }: PreviewOptions
): Promise<PreviewServer> {
if (config.output === 'server') {
if (settings.config.output === 'server') {
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.)`
);
}
const startServerTime = performance.now();
const defaultOrigin = 'http://localhost';
const trailingSlash = config.trailingSlash;
const trailingSlash = settings.config.trailingSlash;
/** Base request URL. */
let baseURL = new URL(config.base, new URL(config.site || '/', defaultOrigin));
const staticFileServer = sirv(fileURLToPath(config.outDir), {
let baseURL = new URL(settings.config.base, new URL(settings.config.site || '/', defaultOrigin));
const staticFileServer = sirv(fileURLToPath(settings.config.outDir), {
dev: true,
etag: true,
maxAge: 0,
@ -84,7 +84,7 @@ export default async function preview(
// HACK: rewrite req.url so that sirv finds the file
req.url = '/' + req.url?.replace(baseURL.pathname, '');
staticFileServer(req, res, () => {
const errorPagePath = fileURLToPath(config.outDir + '/404.html');
const errorPagePath = fileURLToPath(settings.config.outDir + '/404.html');
if (fs.existsSync(errorPagePath)) {
res.statusCode = 404;
res.setHeader('Content-Type', 'text/html;charset=utf-8');
@ -100,8 +100,8 @@ export default async function preview(
}
});
let { port } = config.server;
const host = getResolvedHostForHttpServer(config.server.host);
let { port } = settings.config.server;
const host = getResolvedHostForHttpServer(settings.config.server.host);
let httpServer: http.Server;
@ -119,7 +119,7 @@ export default async function preview(
null,
msg.devStart({
startupTime: performance.now() - timerStart,
config,
config: settings.config,
devServerAddressInfo,
https: false,
site: baseURL,

View file

@ -1,7 +1,7 @@
import { fileURLToPath } from 'url';
import type { ViteDevServer } from 'vite';
import type {
AstroConfig,
AstroSettings,
AstroRenderer,
ComponentInstance,
RouteData,
@ -20,8 +20,8 @@ import { resolveClientDevPath } from './resolve.js';
import { getScriptsForURL } from './scripts.js';
export interface SSROptions {
/** an instance of the AstroConfig */
astroConfig: AstroConfig;
/** an instance of the AstroSettings */
settings: AstroSettings;
/** location of file on disk */
filePath: URL;
/** logging options */
@ -58,18 +58,18 @@ async function loadRenderer(
export async function loadRenderers(
viteServer: ViteDevServer,
astroConfig: AstroConfig
settings: AstroSettings
): 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({
astroConfig,
settings,
filePath,
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.
const renderers = await loadRenderers(viteServer, astroConfig);
const renderers = await loadRenderers(viteServer, settings);
// Load the module from the Vite SSR Runtime.
const mod = (await viteServer.ssrLoadModule(fileURLToPath(filePath))) as ComponentInstance;
if (viteServer.config.mode === 'development' || !mod?.$$metadata) {
@ -92,7 +92,7 @@ export async function render(
ssrOpts: SSROptions
): Promise<Response> {
const {
astroConfig,
settings,
filePath,
logging,
mode,
@ -104,10 +104,10 @@ export async function render(
viteServer,
} = ssrOpts;
// Add hoisted script tags
const scripts = await getScriptsForURL(filePath, astroConfig, viteServer);
const scripts = await getScriptsForURL(filePath, viteServer);
// Inject HMR scripts
if (isPage(filePath, astroConfig) && mode === 'development') {
if (isPage(filePath, settings) && mode === 'development') {
scripts.add({
props: { type: 'module', src: '/@vite/client' },
children: '',
@ -122,13 +122,13 @@ export async function render(
}
// 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') {
scripts.add({
props: {},
children: script.content,
});
} else if (script.stage === 'page' && isPage(filePath, astroConfig)) {
} else if (script.stage === 'page' && isPage(filePath, settings)) {
scripts.add({
props: { type: 'module', src: `/@id/${PAGE_SCRIPT_ID}` },
children: '',
@ -167,13 +167,13 @@ export async function render(
});
let response = await coreRender({
adapterName: astroConfig.adapter?.name,
adapterName: settings.config.adapter?.name,
links,
styles,
logging,
markdown: {
...astroConfig.markdown,
isAstroFlavoredMd: astroConfig.legacy.astroFlavoredMarkdown,
...settings.config.markdown,
isAstroFlavoredMd: settings.config.legacy.astroFlavoredMarkdown,
},
mod,
mode,
@ -191,8 +191,8 @@ export async function render(
request,
route,
routeCache,
site: astroConfig.site ? new URL(astroConfig.base, astroConfig.site).toString() : undefined,
ssr: astroConfig.output === 'server',
site: settings.config.site ? new URL(settings.config.base, settings.config.site).toString() : undefined,
ssr: settings.config.output === 'server',
streaming: true,
});

View file

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

View file

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

View file

@ -5,7 +5,7 @@ import resolve from 'resolve';
import slash from 'slash';
import { fileURLToPath, pathToFileURL } from 'url';
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';
// 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;
}
function endsWithPageExt(file: URL, config: AstroConfig): boolean {
for (const ext of config._ctx.pageExtensions) {
function endsWithPageExt(file: URL, settings: AstroSettings): boolean {
for (const ext of settings.pageExtensions) {
if (file.toString().endsWith(ext)) return true;
}
return false;
}
export function isPage(file: URL, config: AstroConfig): boolean {
if (!isInPagesDir(file, config)) return false;
if (!isPublicRoute(file, config)) return false;
return endsWithPageExt(file, config);
export function isPage(file: URL, settings: AstroSettings): boolean {
if (!isInPagesDir(file, settings.config)) return false;
if (!isPublicRoute(file, settings.config)) return false;
return endsWithPageExt(file, settings);
}
export function isModeServerWithNoAdapter(config: AstroConfig): boolean {
return config.output === 'server' && !config._ctx.adapter;
export function isModeServerWithNoAdapter(settings: AstroSettings): boolean {
return settings.config.output === 'server' && !settings.adapter;
}
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 {
AstroConfig,
AstroSettings,
AstroRenderer,
BuildConfig,
HookParameters,
@ -10,7 +11,7 @@ import {
} from '../@types/astro.js';
import type { SerializedSSRManifest } from '../core/app/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 { info, LogOptions } from '../core/logger/core.js';
@ -34,21 +35,22 @@ async function withTakingALongTimeMsg<T>({
}
export async function runHookConfigSetup({
config: _config,
settings,
command,
logging,
}: {
config: AstroConfig;
settings: AstroSettings;
command: 'dev' | 'build';
logging: LogOptions;
}): Promise<AstroConfig> {
}): Promise<AstroSettings> {
// An adapter is an integration, so if one is provided push it.
if (_config.adapter) {
_config.integrations.push(_config.adapter);
if (settings.config.adapter) {
settings.config.integrations.push(settings.config.adapter);
}
let updatedConfig: AstroConfig = { ..._config };
for (const integration of _config.integrations) {
let updatedConfig: AstroConfig = { ...settings.config };
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
* 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.`);
}
updatedConfig._ctx.renderers.push(renderer);
updatedSettings.renderers.push(renderer);
},
injectScript: (stage, content) => {
updatedConfig._ctx.scripts.push({ stage, content });
updatedSettings.scripts.push({ stage, content });
},
updateConfig: (newConfig) => {
updatedConfig = mergeConfig(updatedConfig, newConfig) as AstroConfig;
},
injectRoute: (injectRoute) => {
updatedConfig._ctx.injectedRoutes.push(injectRoute);
updatedSettings.injectedRoutes.push(injectRoute);
},
};
// Semi-private `addPageExtension` hook
function addPageExtension(...input: (string | string[])[]) {
const exts = (input.flat(Infinity) as string[]).map((ext) => `.${ext.replace(/^\./, '')}`);
updatedConfig._ctx.pageExtensions.push(...exts);
updatedSettings.pageExtensions.push(...exts);
}
Object.defineProperty(hooks, 'addPageExtension', {
value: addPageExtension,
@ -103,29 +105,31 @@ export async function runHookConfigSetup({
});
}
}
return updatedConfig;
updatedSettings.config = updatedConfig;
return updatedSettings;
}
export async function runHookConfigDone({
config,
settings,
logging,
}: {
config: AstroConfig;
settings: AstroSettings;
logging: LogOptions;
}) {
for (const integration of config.integrations) {
for (const integration of settings.config.integrations) {
if (integration?.hooks?.['astro:config:done']) {
await withTakingALongTimeMsg({
name: integration.name,
hookResult: integration.hooks['astro:config:done']({
config,
config: settings.config,
setAdapter(adapter) {
if (config._ctx.adapter && config._ctx.adapter.name !== adapter.name) {
if (settings.adapter && settings.adapter.name !== adapter.name) {
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,

View file

@ -3,18 +3,18 @@ import type { ArrowFunctionExpressionKind, CallExpressionKind } from 'ast-types/
import type { NodePath } from 'ast-types/lib/node-path';
import { parse, print, types, visit } from 'recast';
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.
const ASTRO_GLOB_REGEX = /Astro2?\s*\.\s*glob\s*\(/;
interface AstroPluginOptions {
config: AstroConfig;
settings: AstroSettings;
}
// esbuild transforms the component-scoped Astro into Astro2, so need to check both.
const validAstroGlobalNames = new Set(['Astro', 'Astro2']);
export default function astro({ config }: AstroPluginOptions): Plugin {
export default function astro(_opts: AstroPluginOptions): Plugin {
return {
name: 'astro:postprocess',
async transform(code, id) {

View file

@ -1,7 +1,7 @@
import type http from 'http';
import mime from 'mime';
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 { Readable } from 'stream';
@ -24,7 +24,7 @@ import { resolvePages } from '../core/util.js';
import notFoundTemplate, { subpathNotUsedTemplate } from '../template/4xx.js';
interface AstroPluginOptions {
config: AstroConfig;
settings: AstroSettings;
logging: LogOptions;
}
@ -94,7 +94,7 @@ async function writeSSRResult(webResponse: Response, res: http.ServerResponse) {
async function handle404Response(
origin: string,
config: AstroConfig,
settings: AstroSettings,
req: http.IncomingMessage,
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
const relPages = resolvePages(config).href.replace(config.root.href, '');
const pattern = new RegExp(`${appendForwardSlash(relPages)}404.(astro|md)`);
@ -141,9 +141,10 @@ function log404(logging: LogOptions, pathname: string) {
}
export function baseMiddleware(
config: AstroConfig,
settings: AstroSettings,
logging: LogOptions
): vite.Connect.NextHandleFunction {
const { config } = settings;
const site = config.site ? new URL(config.base, config.site) : undefined;
const devRoot = site ? site.pathname : '/';
@ -184,13 +185,13 @@ async function matchRoute(
viteServer: vite.ViteDevServer,
logging: LogOptions,
manifest: ManifestData,
config: AstroConfig
settings: AstroSettings
) {
const matches = matchAllRoutes(pathname, manifest);
for await (const maybeRoute of matches) {
const filePath = new URL(`./${maybeRoute.component}`, config.root);
const preloadedComponent = await preload({ astroConfig: config, filePath, viteServer });
const filePath = new URL(`./${maybeRoute.component}`, settings.config.root);
const preloadedComponent = await preload({ settings, filePath, viteServer });
const [, mod] = preloadedComponent;
// attempt to get static paths
// if this fails, we have a bad URL match!
@ -200,7 +201,7 @@ async function matchRoute(
routeCache,
pathname: pathname,
logging,
ssr: config.output === 'server',
ssr: settings.config.output === 'server',
});
if (paramsAndPropsRes !== GetParamsAndPropsError.NoMatchingStaticPath) {
@ -222,11 +223,11 @@ async function matchRoute(
}
log404(logging, pathname);
const custom404 = getCustom404Route(config, manifest);
const custom404 = getCustom404Route(settings, manifest);
if (custom404) {
const filePath = new URL(`./${custom404.component}`, config.root);
const preloadedComponent = await preload({ astroConfig: config, filePath, viteServer });
const filePath = new URL(`./${custom404.component}`, settings.config.root);
const preloadedComponent = await preload({ settings, filePath, viteServer });
const [, mod] = preloadedComponent;
return {
@ -246,10 +247,11 @@ async function handleRequest(
viteServer: vite.ViteDevServer,
logging: LogOptions,
manifest: ManifestData,
config: AstroConfig,
settings: AstroSettings,
req: http.IncomingMessage,
res: http.ServerResponse
) {
const { config } = settings;
const origin = `${viteServer.config.server.https ? 'https' : 'http'}://${req.headers.host}`;
const buildingToSSR = config.output === 'server';
// Ignore `.html` extensions and `index.html` in request URLS to ensure that
@ -292,7 +294,7 @@ async function handleRequest(
viteServer,
logging,
manifest,
config
settings
);
filePath = matchedRoute?.filePath;
@ -306,7 +308,7 @@ async function handleRequest(
viteServer,
manifest,
logging,
config,
settings,
req,
res
);
@ -328,14 +330,15 @@ async function handleRoute(
viteServer: vite.ViteDevServer,
manifest: ManifestData,
logging: LogOptions,
config: AstroConfig,
settings: AstroSettings,
req: http.IncomingMessage,
res: http.ServerResponse
): Promise<void> {
if (!matchedRoute) {
return handle404Response(origin, config, req, res);
return handle404Response(origin, settings, req, res);
}
const { config } = settings;
const filePath: URL | undefined = matchedRoute.filePath;
const { route, preloadedComponent, mod } = matchedRoute;
const buildingToSSR = config.output === 'server';
@ -363,7 +366,7 @@ async function handleRoute(
});
const options: SSROptions = {
astroConfig: config,
settings,
filePath,
logging,
mode: 'development',
@ -386,7 +389,7 @@ async function handleRoute(
viteServer,
logging,
manifest,
config
settings
);
return handleRoute(
fourOhFourRoute,
@ -398,7 +401,7 @@ async function handleRoute(
viteServer,
manifest,
logging,
config,
settings,
req,
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 {
name: 'astro:server',
configureServer(viteServer) {
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. */
function rebuildManifest(needsManifestRebuild: boolean, file: string) {
routeCache.clearAll();
if (needsManifestRebuild) {
manifest = createRouteManifest({ config: config }, logging);
manifest = createRouteManifest({ settings }, logging);
}
}
// 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));
return () => {
// 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({
route: '',
handle: baseMiddleware(config, logging),
handle: baseMiddleware(settings, logging),
});
}
viteServer.middlewares.use(async (req, res) => {
if (!req.url || !req.method) {
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 * 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 { ViteStyleTransformer } from '../vite-style-transform';
import type { PluginMetadata as AstroPluginMetadata } from './types';
@ -22,12 +22,13 @@ import { parseAstroRequest, ParsedRequestResult } from './query.js';
const FRONTMATTER_PARSE_REGEXP = /^\-\-\-(.*)^\-\-\-/ms;
interface AstroPluginOptions {
config: AstroConfig;
settings: AstroSettings;
logging: LogOptions;
}
/** 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) {
if (filename.startsWith('/@fs')) {
filename = filename.slice('/@fs'.length);

View file

@ -1,5 +1,5 @@
import * as path from 'path';
import type { AstroConfig } from '../@types/astro';
import type { AstroSettings } from '../@types/astro';
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);
/** Returns a list of compiled aliases. */
const getConfigAlias = (astroConfig: AstroConfig): Alias[] | null => {
const getConfigAlias = (settings: AstroSettings): Alias[] | null => {
/** Closest tsconfig.json or jsconfig.json */
const config = astroConfig._ctx.tsConfig;
const configPath = astroConfig._ctx.tsConfigPath;
const config = settings.tsConfig;
const configPath = settings.tsConfigPath;
// if no config was found, 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. */
export default function configAliasVitePlugin({
config: astroConfig,
settings,
}: {
config: AstroConfig;
settings: AstroSettings;
}): vite.PluginOption {
const { config } = settings;
/** 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 (!configAlias) return {} as vite.PluginOption;

View file

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

View file

@ -1,20 +1,20 @@
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 { runHookServerSetup } from '../integrations/index.js';
/** Connect Astro integrations into Vite, as needed. */
export default function astroIntegrationsContainerPlugin({
config,
settings,
logging,
}: {
config: AstroConfig;
settings: AstroSettings;
logging: LogOptions;
}): VitePlugin {
return {
name: 'astro:integration-container',
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 { TsConfigJson } from 'tsconfig-resolver';
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 { PluginMetadata } from '../vite-plugin-astro/types';
@ -152,12 +152,12 @@ async function transformJSX({
}
interface AstroPluginJSXOptions {
config: AstroConfig;
settings: AstroSettings;
logging: LogOptions;
}
/** 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;
const jsxRenderers = 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
async configResolved(resolvedConfig) {
viteConfig = resolvedConfig;
const possibleRenderers = collectJSXRenderers(config._ctx.renderers);
const possibleRenderers = collectJSXRenderers(settings.renderers);
for (const [importSource, renderer] of possibleRenderers) {
jsxRenderers.set(importSource, renderer);
if (importSource === 'astro') {
@ -230,7 +230,7 @@ export default function jsx({ config, logging }: AstroPluginJSXOptions): Plugin
// Check the tsconfig
if (!importSource) {
const compilerOptions = config._ctx.tsConfig?.compilerOptions;
const compilerOptions = settings.tsConfig?.compilerOptions;
importSource = (compilerOptions as FixedCompilerOptions | undefined)?.jsxImportSource;
}

View file

@ -5,7 +5,7 @@ import fs from 'fs';
import matter from 'gray-matter';
import { fileURLToPath } from 'url';
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 { cachedCompilation, CompileProps } from '../core/compile/index.js';
import { collectErrorMetadata } from '../core/errors.js';
@ -19,7 +19,7 @@ import {
} from '../vite-style-transform/index.js';
interface AstroPluginOptions {
config: AstroConfig;
settings: AstroSettings;
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.
// Both end up connecting a `load()` hook to the Astro compiler, and share some copy-paste
// 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) {
if (filename.startsWith('/@fs')) {
filename = filename.slice('/@fs'.length);

View file

@ -2,7 +2,7 @@ import { renderMarkdown } from '@astrojs/markdown-remark';
import fs from 'fs';
import matter from 'gray-matter';
import type { Plugin } from 'vite';
import type { AstroConfig } from '../@types/astro';
import type { AstroSettings } from '../@types/astro';
import { collectErrorMetadata } from '../core/errors.js';
import type { LogOptions } 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';
interface AstroPluginOptions {
config: AstroConfig;
settings: AstroSettings;
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 {
enforce: 'pre',
name: 'astro:markdown',
@ -33,11 +33,11 @@ export default function markdown({ config, logging }: AstroPluginOptions): Plugi
// to escape "import.meta.env" ourselves.
async load(id) {
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 raw = safeMatter(rawFile, id);
const renderResult = await renderMarkdown(raw.content, {
...config.markdown,
...settings.config.markdown,
fileURL: new URL(`file://${fileId}`),
isAstroFlavoredMd: false,
} as any);

View file

@ -1,5 +1,5 @@
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
// 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_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;
return {
name: 'astro:scripts',
@ -29,19 +29,19 @@ export default function astroScriptsPlugin({ config }: { config: AstroConfig }):
async load(id) {
if (id === BEFORE_HYDRATION_SCRIPT_ID) {
return config._ctx.scripts
return settings.scripts
.filter((s) => s.stage === 'before-hydration')
.map((s) => s.content)
.join('\n');
}
if (id === PAGE_SCRIPT_ID) {
return config._ctx.scripts
return settings.scripts
.filter((s) => s.stage === 'page')
.map((s) => s.content)
.join('\n');
}
if (id === PAGE_SSR_SCRIPT_ID) {
return config._ctx.scripts
return settings.scripts
.filter((s) => s.stage === 'page-ssr')
.map((s) => s.content)
.join('\n');
@ -49,7 +49,7 @@ export default function astroScriptsPlugin({ config }: { config: AstroConfig }):
return null;
},
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) {
this.emitFile({
type: 'chunk',

View file

@ -1,17 +1,17 @@
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 ancestor from 'common-ancestor-path';
import MagicString from 'magic-string';
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) {
if (filename.startsWith('/@fs')) {
filename = filename.slice('/@fs'.length);
} else if (filename.startsWith('/') && !ancestor(filename, config.root.pathname)) {
filename = new URL('.' + filename, config.root).pathname;
} else if (filename.startsWith('/') && !ancestor(filename, settings.config.root.pathname)) {
filename = new URL('.' + filename, settings.config.root).pathname;
}
return filename;
}
@ -23,7 +23,7 @@ export default function astroScriptsPostPlugin({ config }: { config: AstroConfig
transform(this, code, id, options) {
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;
const filename = normalizeFilename(id);
@ -35,7 +35,7 @@ export default function astroScriptsPostPlugin({ config }: { config: AstroConfig
return;
}
const fileIsPage = isPage(fileURL, config);
const fileIsPage = isPage(fileURL, settings);
if (!fileIsPage) return;
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 testAdapter from './test-adapter.js';
describe('AstroConfig - config.mode', () => {
describe('AstroConfig - config.output', () => {
describe(`output: 'server'`, () => {
describe('deploy config provided', () => {
/** @type {import('./test-utils').Fixture} */
@ -58,7 +58,7 @@ describe('AstroConfig - config.mode', () => {
});
describe(`output: 'static'`, () => {
describe('Deploy config omitted', () => {
describe('Output config omitted', () => {
/** @type {import('./test-utils').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 fs from 'fs';
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 build from '../dist/core/build/index.js';
import preview from '../dist/core/preview/index.js';
@ -18,7 +19,7 @@ polyfill(globalThis, {
/**
* @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/core/preview/index').PreviewServer} PreviewServer
* @typedef {import('../src/core/app/index').App} App
@ -39,6 +40,12 @@ polyfill(globalThis, {
* @property {() => Promise<void>} onNextChange
*/
/** @type {import('../src/core/logger/core').LogOptions} */
export const defaultLogging = {
dest: nodeLogDestination,
level: 'error',
};
/**
* Load Astro fixture
* @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} */
const logging = {
dest: nodeLogDestination,
level: 'error',
};
const logging = defaultLogging;
// Load the config.
let config = await loadConfig({ cwd: fileURLToPath(cwd), logging });
@ -91,10 +95,16 @@ export async function loadFixture(inlineConfig) {
if (inlineConfig.base && !inlineConfig.base.endsWith('/')) {
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')) {
// Enable default JSX integration. It needs to come first, so unshift rather than push!
const { default: jsxRenderer } = await import('astro/jsx/renderer.js');
config._ctx.renderers.unshift(jsxRenderer);
settings.renderers.unshift(jsxRenderer);
}
/** @type {import('@astrojs/telemetry').AstroTelemetry} */
@ -133,9 +143,9 @@ export async function loadFixture(inlineConfig) {
let devServer;
return {
build: (opts = {}) => build(config, { logging, telemetry, ...opts }),
build: (opts = {}) => build(settings, { logging, telemetry, ...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
return devServer;
},
@ -143,7 +153,7 @@ export async function loadFixture(inlineConfig) {
resolveUrl,
fetch: (url, init) => fetch(resolveUrl(url), init),
preview: async (opts = {}) => {
const previewServer = await preview(config, { logging, telemetry, ...opts });
const previewServer = await preview(settings, { logging, telemetry, ...opts });
return previewServer;
},
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 { z } from 'zod';
import stripAnsi from 'strip-ansi';
import { formatConfigErrorMessage } from '../dist/core/messages.js';
import { validateConfig } from '../dist/core/config.js';
import { formatConfigErrorMessage } from '../../../dist/core/messages.js';
import { validateConfig } from '../../../dist/core/config/index.js';
describe('Config Validation', () => {
it('empty user config is valid', async () => {