From f3a81d82f6ab4516cb86bf6b5e3eb01cb3ba39fb Mon Sep 17 00:00:00 2001 From: Matthew Phillips Date: Fri, 16 Sep 2022 11:29:17 -0400 Subject: [PATCH] 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 --- .changeset/new-llamas-wash.md | 5 + packages/astro/src/@types/astro.ts | 29 +- packages/astro/src/cli/check/index.ts | 6 +- packages/astro/src/cli/index.ts | 30 +- packages/astro/src/core/add/index.ts | 2 +- packages/astro/src/core/build/generate.ts | 63 ++-- packages/astro/src/core/build/index.ts | 46 +-- packages/astro/src/core/build/page-data.ts | 8 +- packages/astro/src/core/build/static-build.ts | 52 ++- packages/astro/src/core/build/types.ts | 4 +- .../astro/src/core/build/vite-plugin-css.ts | 7 +- .../core/build/vite-plugin-hoisted-scripts.ts | 8 +- .../astro/src/core/build/vite-plugin-pages.ts | 4 +- .../astro/src/core/build/vite-plugin-ssr.ts | 22 +- .../astro/src/core/{ => config}/config.ts | 298 +----------------- packages/astro/src/core/config/index.ts | 20 ++ packages/astro/src/core/config/schema.ts | 271 ++++++++++++++++ packages/astro/src/core/config/settings.ts | 31 ++ packages/astro/src/core/config/tsconfig.ts | 11 + packages/astro/src/core/create-vite.ts | 50 +-- packages/astro/src/core/dev/index.ts | 24 +- packages/astro/src/core/endpoint/dev/index.ts | 2 +- packages/astro/src/core/preview/index.ts | 20 +- packages/astro/src/core/render/dev/index.ts | 36 +-- packages/astro/src/core/render/dev/scripts.ts | 7 +- .../astro/src/core/routing/manifest/create.ts | 16 +- packages/astro/src/core/util.ts | 18 +- packages/astro/src/integrations/index.ts | 46 +-- .../vite-plugin-astro-postprocess/index.ts | 6 +- .../src/vite-plugin-astro-server/index.ts | 55 ++-- packages/astro/src/vite-plugin-astro/index.ts | 7 +- .../src/vite-plugin-config-alias/index.ts | 15 +- packages/astro/src/vite-plugin-env/index.ts | 7 +- .../index.ts | 8 +- packages/astro/src/vite-plugin-jsx/index.ts | 10 +- .../src/vite-plugin-markdown-legacy/index.ts | 7 +- .../astro/src/vite-plugin-markdown/index.ts | 10 +- .../astro/src/vite-plugin-scripts/index.ts | 12 +- .../astro/src/vite-plugin-scripts/page-ssr.ts | 12 +- packages/astro/test/config-mode.test.js | 4 +- packages/astro/test/config.test.js | 126 -------- packages/astro/test/test-utils.js | 30 +- .../test/units/config/config-server.test.js | 71 +++++ .../config}/config-validate.test.js | 4 +- 44 files changed, 776 insertions(+), 744 deletions(-) create mode 100644 .changeset/new-llamas-wash.md rename packages/astro/src/core/{ => config}/config.ts (55%) create mode 100644 packages/astro/src/core/config/index.ts create mode 100644 packages/astro/src/core/config/schema.ts create mode 100644 packages/astro/src/core/config/settings.ts create mode 100644 packages/astro/src/core/config/tsconfig.ts delete mode 100644 packages/astro/test/config.test.js create mode 100644 packages/astro/test/units/config/config-server.test.js rename packages/astro/test/{ => units/config}/config-validate.test.js (95%) diff --git a/.changeset/new-llamas-wash.md b/.changeset/new-llamas-wash.md new file mode 100644 index 000000000..09575ff13 --- /dev/null +++ b/.changeset/new-llamas-wash.md @@ -0,0 +1,5 @@ +--- +'astro': patch +--- + +Internal refactor diff --git a/packages/astro/src/@types/astro.ts b/packages/astro/src/@types/astro.ts index 817467a30..7589534c7 100644 --- a/packages/astro/src/@types/astro.ts +++ b/packages/astro/src/@types/astro.ts @@ -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 { // 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 = ( diff --git a/packages/astro/src/cli/check/index.ts b/packages/astro/src/cli/check/index.ts index 79571de0f..60e6b7a97 100644 --- a/packages/astro/src/cli/check/index.ts +++ b/packages/astro/src/cli/check/index.ts @@ -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(); diff --git a/packages/astro/src/cli/index.ts b/packages/astro/src/cli/index.ts index 622505700..064a2b5b3 100644 --- a/packages/astro/src/cli/index.ts +++ b/packages/astro/src/cli/index.ts @@ -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 } } diff --git a/packages/astro/src/core/add/index.ts b/packages/astro/src/core/add/index.ts index c655dab5d..70a766e07 100644 --- a/packages/astro/src/core/add/index.ts +++ b/packages/astro/src/core/add/index.ts @@ -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'; diff --git a/packages/astro/src/core/build/generate.ts b/packages/astro/src/core/build/generate.ts index 6a3fe5035..a73d48945 100644 --- a/packages/astro/src/core/build/generate.ts +++ b/packages/astro/src/core/build/generate.ts @@ -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(); @@ -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'); diff --git a/packages/astro/src/core/build/index.ts b/packages/astro/src/core/build/index.ts index 829637ddd..7a3b9760f 100644 --- a/packages/astro/src/core/build/index.ts +++ b/packages/astro/src/core/build/index.ts @@ -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 { +export default async function build(settings: AstroSettings, options: BuildOptions): Promise { 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; - 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, }); } } diff --git a/packages/astro/src/core/build/page-data.ts b/packages/astro/src/core/build/page-data.ts index 509d1ae20..ed4bf4399 100644 --- a/packages/astro/src/core/build/page-data.ts +++ b/packages/astro/src/core/build/page-data.ts @@ -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 { - const { astroConfig, manifest } = opts; + const { settings, manifest } = opts; const assets: Record = {}; 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', diff --git a/packages/astro/src/core/build/static-build.ts b/packages/astro/src/core/build/static-build.ts index 0e91dc548..9afa3ed32 100644 --- a/packages/astro/src/core/build/static-build.ts +++ b/packages/astro/src/core/build/static-build.ts @@ -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) { - 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 ) { - 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); diff --git a/packages/astro/src/core/build/types.ts b/packages/astro/src/core/build/types.ts index 480af85d0..b8beacbb9 100644 --- a/packages/astro/src/core/build/types.ts +++ b/packages/astro/src/core/build/types.ts @@ -1,5 +1,5 @@ import type { - AstroConfig, + AstroSettings, BuildConfig, ComponentInstance, ManifestData, @@ -26,7 +26,7 @@ export type AllPagesData = Record; /** Options for the static build */ export interface StaticBuildOptions { allPages: AllPagesData; - astroConfig: AstroConfig; + settings: AstroSettings; buildConfig: BuildConfig; logging: LogOptions; manifest: ManifestData; diff --git a/packages/astro/src/core/build/vite-plugin-css.ts b/packages/astro/src/core/build/vite-plugin-css.ts index 4d77f6f15..1ef7202d5 100644 --- a/packages/astro/src/core/build/vite-plugin-css.ts +++ b/packages/astro/src/core/build/vite-plugin-css.ts @@ -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, diff --git a/packages/astro/src/core/build/vite-plugin-hoisted-scripts.ts b/packages/astro/src/core/build/vite-plugin-hoisted-scripts.ts index 9ab7ece45..23230035c 100644 --- a/packages/astro/src/core/build/vite-plugin-hoisted-scripts.ts +++ b/packages/astro/src/core/build/vite-plugin-hoisted-scripts.ts @@ -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) { diff --git a/packages/astro/src/core/build/vite-plugin-pages.ts b/packages/astro/src/core/build/vite-plugin-pages.ts index ceaf87eeb..7b81ba398 100644 --- a/packages/astro/src/core/build/vite-plugin-pages.ts +++ b/packages/astro/src/core/build/vite-plugin-pages.ts @@ -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. diff --git a/packages/astro/src/core/build/vite-plugin-ssr.ts b/packages/astro/src/core/build/vite-plugin-ssr.ts index 3f8387f13..e2e6ff326 100644 --- a/packages/astro/src/core/build/vite-plugin-ssr.ts +++ b/packages/astro/src/core/build/vite-plugin-ssr.ts @@ -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: [], diff --git a/packages/astro/src/core/config.ts b/packages/astro/src/core/config/config.ts similarity index 55% rename from packages/astro/src/core/config.ts rename to packages/astro/src/core/config/config.ts index 58650613e..2c60257f4 100644 --- a/packages/astro/src/core/config.ts +++ b/packages/astro/src/core/config/config.ts @@ -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 { - 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().array().default([]), - theme: z - .enum(BUNDLED_THEMES as [Theme, ...Theme[]]) - .or(z.custom()) - .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((data) => typeof data === 'function'), - z.tuple([z.custom((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((data) => typeof data === 'function'), - z.tuple([z.custom((data) => typeof data === 'function'), z.any()]), - ]) - .array() - .default(ASTRO_CONFIG_DEFAULTS.markdown.rehypePlugins), - remarkRehype: z - .custom((data) => data instanceof Object && !Array.isArray(data)) - .optional() - .default(ASTRO_CONFIG_DEFAULTS.markdown.remarkRehype), - extendDefaultPlugins: z.boolean().default(false), - }) - .default({}), - vite: z - .custom((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 diff --git a/packages/astro/src/core/config/index.ts b/packages/astro/src/core/config/index.ts new file mode 100644 index 000000000..b22083c4a --- /dev/null +++ b/packages/astro/src/core/config/index.ts @@ -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'; diff --git a/packages/astro/src/core/config/schema.ts b/packages/astro/src/core/config/schema.ts new file mode 100644 index 000000000..9bcfd15b3 --- /dev/null +++ b/packages/astro/src/core/config/schema.ts @@ -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().array().default([]), + theme: z + .enum(BUNDLED_THEMES as [Theme, ...Theme[]]) + .or(z.custom()) + .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((data) => typeof data === 'function'), + z.tuple([z.custom((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((data) => typeof data === 'function'), + z.tuple([z.custom((data) => typeof data === 'function'), z.any()]), + ]) + .array() + .default(ASTRO_CONFIG_DEFAULTS.markdown.rehypePlugins), + remarkRehype: z + .custom((data) => data instanceof Object && !Array.isArray(data)) + .optional() + .default(ASTRO_CONFIG_DEFAULTS.markdown.remarkRehype), + extendDefaultPlugins: z.boolean().default(false), + }) + .default({}), + vite: z + .custom((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 { + 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; +} diff --git a/packages/astro/src/core/config/settings.ts b/packages/astro/src/core/config/settings.ts new file mode 100644 index 000000000..57af38618 --- /dev/null +++ b/packages/astro/src/core/config/settings.ts @@ -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: [], + }; +} diff --git a/packages/astro/src/core/config/tsconfig.ts b/packages/astro/src/core/config/tsconfig.ts new file mode 100644 index 000000000..ddfa72300 --- /dev/null +++ b/packages/astro/src/core/config/tsconfig.ts @@ -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; +} diff --git a/packages/astro/src/core/create-vite.ts b/packages/astro/src/core/create-vite.ts index 8ee301344..9c4757f44 100644 --- a/packages/astro/src/core/create-vite.ts +++ b/packages/astro/src/core/create-vite.ts @@ -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 { - 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 { - const { astroPackages } = new DependencyWalker(root); +async function getAstroPackages(settings: AstroSettings): Promise { + const { astroPackages } = new DependencyWalker(settings.config.root); return astroPackages; } diff --git a/packages/astro/src/core/dev/index.ts b/packages/astro/src/core/dev/index.ts index e8069db90..0466d826b 100644 --- a/packages/astro/src/core/dev/index.ts +++ b/packages/astro/src/core/dev/index.ts @@ -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 { +export default async function dev(settings: AstroSettings, options: DevOptions): Promise { 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 }); }, }; } diff --git a/packages/astro/src/core/endpoint/dev/index.ts b/packages/astro/src/core/endpoint/dev/index.ts index 342c9a4f4..b2f16225b 100644 --- a/packages/astro/src/core/endpoint/dev/index.ts +++ b/packages/astro/src/core/endpoint/dev/index.ts @@ -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', }); } diff --git a/packages/astro/src/core/preview/index.ts b/packages/astro/src/core/preview/index.ts index 04d5af61f..1d81df989 100644 --- a/packages/astro/src/core/preview/index.ts +++ b/packages/astro/src/core/preview/index.ts @@ -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 { - 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, diff --git a/packages/astro/src/core/render/dev/index.ts b/packages/astro/src/core/render/dev/index.ts index 672af88c2..3003d6417 100644 --- a/packages/astro/src/core/render/dev/index.ts +++ b/packages/astro/src/core/render/dev/index.ts @@ -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 { - 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): Promise { +}: Pick): Promise { // 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 { 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, }); diff --git a/packages/astro/src/core/render/dev/scripts.ts b/packages/astro/src/core/render/dev/scripts.ts index 67e9bd1e4..1297a8a92 100644 --- a/packages/astro/src/core/render/dev/scripts.ts +++ b/packages/astro/src/core/render/dev/scripts.ts @@ -10,19 +10,17 @@ import { crawlGraph } from './vite.js'; export async function getScriptsForURL( filePath: URL, - astroConfig: AstroConfig, viteServer: vite.ViteDevServer ): Promise> { const elements = new Set(); 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, info: ModuleInfo | null, - rootProjectFolder: string ) { if (!info?.meta?.astro) { return; diff --git a/packages/astro/src/core/routing/manifest/create.ts b/packages/astro/src/core/routing/manifest/create.ts index 0efd29796..f2bcd9b4b 100644 --- a/packages/astro/src/core/routing/manifest/create.ts +++ b/packages/astro/src/core/routing/manifest/create.ts @@ -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 = new Set([ '.astro', '.md', - ...config._ctx.pageExtensions, + ...settings.pageExtensions, ]); const validEndpointExtensions: Set = 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)) diff --git a/packages/astro/src/core/util.ts b/packages/astro/src/core/util.ts index 0abc9b40b..17fbc41a6 100644 --- a/packages/astro/src/core/util.ts +++ b/packages/astro/src/core/util.ts @@ -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) { diff --git a/packages/astro/src/integrations/index.ts b/packages/astro/src/integrations/index.ts index 68d2a23ae..aec6fa881 100644 --- a/packages/astro/src/integrations/index.ts +++ b/packages/astro/src/integrations/index.ts @@ -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({ } export async function runHookConfigSetup({ - config: _config, + settings, command, logging, }: { - config: AstroConfig; + settings: AstroSettings; command: 'dev' | 'build'; logging: LogOptions; -}): Promise { +}): Promise { // 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, diff --git a/packages/astro/src/vite-plugin-astro-postprocess/index.ts b/packages/astro/src/vite-plugin-astro-postprocess/index.ts index 9d1fb460b..f9ef9281c 100644 --- a/packages/astro/src/vite-plugin-astro-postprocess/index.ts +++ b/packages/astro/src/vite-plugin-astro-postprocess/index.ts @@ -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) { diff --git a/packages/astro/src/vite-plugin-astro-server/index.ts b/packages/astro/src/vite-plugin-astro-server/index.ts index 188b39f70..72f2ce9ba 100644 --- a/packages/astro/src/vite-plugin-astro-server/index.ts +++ b/packages/astro/src/vite-plugin-astro-server/index.ts @@ -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 { 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); }); }; }, diff --git a/packages/astro/src/vite-plugin-astro/index.ts b/packages/astro/src/vite-plugin-astro/index.ts index 5d1bf2c18..20fa69053 100644 --- a/packages/astro/src/vite-plugin-astro/index.ts +++ b/packages/astro/src/vite-plugin-astro/index.ts @@ -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); diff --git a/packages/astro/src/vite-plugin-config-alias/index.ts b/packages/astro/src/vite-plugin-config-alias/index.ts index 4a9923eda..aa4f564b7 100644 --- a/packages/astro/src/vite-plugin-config-alias/index.ts +++ b/packages/astro/src/vite-plugin-config-alias/index.ts @@ -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; diff --git a/packages/astro/src/vite-plugin-env/index.ts b/packages/astro/src/vite-plugin-env/index.ts index f513771db..2eeccc886 100644 --- a/packages/astro/src/vite-plugin-env/index.ts +++ b/packages/astro/src/vite-plugin-env/index.ts @@ -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 | null; let config: vite.ResolvedConfig; let replacements: Record; let pattern: RegExp | undefined; + const { config: astroConfig } = settings; return { name: 'astro:vite-plugin-env', enforce: 'pre', diff --git a/packages/astro/src/vite-plugin-integrations-container/index.ts b/packages/astro/src/vite-plugin-integrations-container/index.ts index f386cd055..a7967092e 100644 --- a/packages/astro/src/vite-plugin-integrations-container/index.ts +++ b/packages/astro/src/vite-plugin-integrations-container/index.ts @@ -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 }); }, }; } diff --git a/packages/astro/src/vite-plugin-jsx/index.ts b/packages/astro/src/vite-plugin-jsx/index.ts index de9eb3d94..ca0885343 100644 --- a/packages/astro/src/vite-plugin-jsx/index.ts +++ b/packages/astro/src/vite-plugin-jsx/index.ts @@ -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(); const jsxRenderersIntegrationOnly = new Map(); @@ -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; } diff --git a/packages/astro/src/vite-plugin-markdown-legacy/index.ts b/packages/astro/src/vite-plugin-markdown-legacy/index.ts index dd7259e2c..52ec713fb 100644 --- a/packages/astro/src/vite-plugin-markdown-legacy/index.ts +++ b/packages/astro/src/vite-plugin-markdown-legacy/index.ts @@ -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); diff --git a/packages/astro/src/vite-plugin-markdown/index.ts b/packages/astro/src/vite-plugin-markdown/index.ts index 6d6c1dd4b..e40fbf959 100644 --- a/packages/astro/src/vite-plugin-markdown/index.ts +++ b/packages/astro/src/vite-plugin-markdown/index.ts @@ -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); diff --git a/packages/astro/src/vite-plugin-scripts/index.ts b/packages/astro/src/vite-plugin-scripts/index.ts index d055cf59d..00cbe690f 100644 --- a/packages/astro/src/vite-plugin-scripts/index.ts +++ b/packages/astro/src/vite-plugin-scripts/index.ts @@ -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', diff --git a/packages/astro/src/vite-plugin-scripts/page-ssr.ts b/packages/astro/src/vite-plugin-scripts/page-ssr.ts index 9aa3425f5..4307b9d42 100644 --- a/packages/astro/src/vite-plugin-scripts/page-ssr.ts +++ b/packages/astro/src/vite-plugin-scripts/page-ssr.ts @@ -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 }); diff --git a/packages/astro/test/config-mode.test.js b/packages/astro/test/config-mode.test.js index aeb09ad2f..76c45ba48 100644 --- a/packages/astro/test/config-mode.test.js +++ b/packages/astro/test/config-mode.test.js @@ -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; diff --git a/packages/astro/test/config.test.js b/packages/astro/test/config.test.js deleted file mode 100644 index bd03b89bc..000000000 --- a/packages/astro/test/config.test.js +++ /dev/null @@ -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); - }); - }); -}); diff --git a/packages/astro/test/test-utils.js b/packages/astro/test/test-utils.js index e1e4f73e7..b53e1acca 100644 --- a/packages/astro/test/test-utils.js +++ b/packages/astro/test/test-utils.js @@ -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} 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) => diff --git a/packages/astro/test/units/config/config-server.test.js b/packages/astro/test/units/config/config-server.test.js new file mode 100644 index 000000000..cdfe3d35d --- /dev/null +++ b/packages/astro/test/units/config/config-server.test.js @@ -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/); + } + }); + }); + }); +}) diff --git a/packages/astro/test/config-validate.test.js b/packages/astro/test/units/config/config-validate.test.js similarity index 95% rename from packages/astro/test/config-validate.test.js rename to packages/astro/test/units/config/config-validate.test.js index 6fefed9e4..fa7418c56 100644 --- a/packages/astro/test/config-validate.test.js +++ b/packages/astro/test/units/config/config-validate.test.js @@ -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 () => {