diff --git a/.changeset/famous-coins-destroy.md b/.changeset/famous-coins-destroy.md new file mode 100644 index 000000000000..f47932b19b61 --- /dev/null +++ b/.changeset/famous-coins-destroy.md @@ -0,0 +1,33 @@ +--- +'astro': minor +'@astrojs/cloudflare': minor +'@astrojs/deno': minor +'@astrojs/image': minor +'@astrojs/netlify': minor +'@astrojs/node': minor +'@astrojs/sitemap': minor +'@astrojs/vercel': minor +--- + +New `output` configuration option + +This change introduces a new "output target" configuration option (`output`). Setting the output target lets you decide the format of your final build, either: + +* `"static"` (default): A static site. Your final build will be a collection of static assets (HTML, CSS, JS) that you can deploy to any static site host. +* `"server"`: A dynamic server application. Your final build will be an application that will run in a hosted server environment, generating HTML dynamically for different requests. + +If `output` is omitted from your config, the default value `"static"` will be used. + +When using the `"server"` output target, you must also include a runtime adapter via the `adapter` configuration. An adapter will *adapt* your final build to run on the deployed platform of your choice (Netlify, Vercel, Node.js, Deno, etc). + +To migrate: No action is required for most users. If you currently define an `adapter`, you will need to also add `output: 'server'` to your config file to make it explicit that you are building a server. Here is an example of what that change would look like for someone deploying to Netlify: + +```diff +import { defineConfig } from 'astro/config'; +import netlify from '@astrojs/netlify/functions'; + +export default defineConfig({ + adapter: netlify(), ++ output: 'server', +}); +``` \ No newline at end of file diff --git a/examples/ssr/astro.config.mjs b/examples/ssr/astro.config.mjs index 5794b07f1da4..b859914ac3ab 100644 --- a/examples/ssr/astro.config.mjs +++ b/examples/ssr/astro.config.mjs @@ -4,6 +4,7 @@ import node from '@astrojs/node'; // https://astro.build/config export default defineConfig({ + output: 'server', adapter: node(), integrations: [svelte()], }); diff --git a/packages/astro/src/@types/astro.ts b/packages/astro/src/@types/astro.ts index de3ad9954d3f..dc437fe36e8b 100644 --- a/packages/astro/src/@types/astro.ts +++ b/packages/astro/src/@types/astro.ts @@ -84,7 +84,6 @@ export interface BuildConfig { client: URL; server: URL; serverEntry: string; - staticMode: boolean | undefined; } /** @@ -424,6 +423,50 @@ export interface AstroUserConfig { */ trailingSlash?: 'always' | 'never' | 'ignore'; + /** + * @docs + * @name adapter + * @typeraw {AstroIntegration} + * @see output + * @description + * + * Deploy to your favorite server, serverless, or edge host with build adapters. Import one of our first-party adapters for [Netlify](https://docs.astro.build/en/guides/deploy/netlify/#adapter-for-ssredge), [Vercel](https://docs.astro.build/en/guides/deploy/vercel/#adapter-for-ssr), and more to engage Astro SSR. + * + * [See our Server-side Rendering guide](https://docs.astro.build/en/guides/server-side-rendering/) for more on SSR, and [our deployment guides](https://docs.astro.build/en/guides/deploy/) for a complete list of hosts. + * + * ```js + * import netlify from '@astrojs/netlify/functions'; + * { + * // Example: Build for Netlify serverless deployment + * adapter: netlify(), + * } + * ``` + */ + adapter?: AstroIntegration; + + /** + * @docs + * @name output + * @type {('static' | 'server')} + * @default `'static'` + * @see adapter + * @description + * + * Specifies the output target for builds. + * + * - 'static' - Building a static site to be deploy to any static host. + * - 'server' - Building an app to be deployed to a host supporting SSR (server-side rendering). + * + * ```js + * import { defineConfig } from 'astro/config'; + * + * export default defineConfig({ + * output: 'static' + * }) + * ``` + */ + output?: 'static' | 'server'; + /** * @docs * @kind heading @@ -606,26 +649,6 @@ export interface AstroUserConfig { rehypePlugins?: RehypePlugins; }; - /** - * @docs - * @kind heading - * @name Adapter - * @description - * - * Deploy to your favorite server, serverless, or edge host with build adapters. Import one of our first-party adapters for [Netlify](https://docs.astro.build/en/guides/deploy/netlify/#adapter-for-ssredge), [Vercel](https://docs.astro.build/en/guides/deploy/vercel/#adapter-for-ssr), and more to engage Astro SSR. - * - * [See our Server-side Rendering guide](https://docs.astro.build/en/guides/server-side-rendering/) for more on SSR, and [our deployment guides](https://docs.astro.build/en/guides/deploy/) for a complete list of hosts. - * - * ```js - * import netlify from '@astrojs/netlify/functions'; - * { - * // Example: Build for Netlify serverless deployment - * adapter: netlify(), - * } - * ``` - */ - adapter?: AstroIntegration; - /** * @docs * @kind heading @@ -747,7 +770,7 @@ 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[]; - adapter?: AstroIntegration; + // Private: // We have a need to pass context based on configured state, // that is different from the user-exposed configuration. diff --git a/packages/astro/src/adapter-ssg/index.ts b/packages/astro/src/adapter-ssg/index.ts deleted file mode 100644 index 703289d8fc0a..000000000000 --- a/packages/astro/src/adapter-ssg/index.ts +++ /dev/null @@ -1,23 +0,0 @@ -import type { AstroAdapter, AstroIntegration } from '../@types/astro'; - -export function getAdapter(): AstroAdapter { - return { - name: '@astrojs/ssg', - // This one has no server entrypoint and is mostly just an integration - //serverEntrypoint: '@astrojs/ssg/server.js', - }; -} - -export default function createIntegration(): AstroIntegration { - return { - name: '@astrojs/ssg', - hooks: { - 'astro:config:done': ({ setAdapter }) => { - setAdapter(getAdapter()); - }, - 'astro:build:start': ({ buildConfig }) => { - buildConfig.staticMode = true; - }, - }, - }; -} diff --git a/packages/astro/src/cli/index.ts b/packages/astro/src/cli/index.ts index 27aa7db304a0..a9e3ebe8e0fd 100644 --- a/packages/astro/src/cli/index.ts +++ b/packages/astro/src/cli/index.ts @@ -132,7 +132,7 @@ async function runCommand(cmd: string, flags: yargs.Arguments) { } } - let { astroConfig, userConfig, userConfigPath } = await openConfig({ cwd: root, flags, cmd }); + let { astroConfig, userConfig, userConfigPath } = await openConfig({ cwd: root, flags, cmd, logging }); telemetry.record(event.eventCliSession(cmd, userConfig, flags)); // Common CLI Commands: @@ -154,7 +154,7 @@ async function runCommand(cmd: string, flags: yargs.Arguments) { watcher.on('add', async function restartServerOnNewConfigFile(addedFile: string) { // if there was not a config before, attempt to resolve if (!userConfigPath && addedFile.includes('astro.config')) { - const addedConfig = await openConfig({ cwd: root, flags, cmd }); + const addedConfig = await openConfig({ cwd: root, flags, cmd, logging }); if (addedConfig.userConfigPath) { info(logging, 'astro', 'Astro config detected. Restarting server...'); astroConfig = addedConfig.astroConfig; diff --git a/packages/astro/src/core/add/index.ts b/packages/astro/src/core/add/index.ts index fe75c42c71d6..7790a71f01fc 100644 --- a/packages/astro/src/core/add/index.ts +++ b/packages/astro/src/core/add/index.ts @@ -66,7 +66,7 @@ export default async function add(names: string[], { cwd, flags, logging, teleme ['--yes', 'Accept all prompts.'], ['--help', 'Show this help message.'], ], - 'Example: Add a UI Framework': [ + 'Recommended: UI Frameworks': [ ['react', 'astro add react'], ['preact', 'astro add preact'], ['vue', 'astro add vue'], @@ -74,7 +74,13 @@ export default async function add(names: string[], { cwd, flags, logging, teleme ['solid-js', 'astro add solid-js'], ['lit', 'astro add lit'], ], - 'Example: Add an Integration': [ + 'Recommended: Hosting': [ + ['netlify', 'astro add netlify'], + ['vercel', 'astro add vercel'], + ['cloudflare', 'astro add cloudflare'], + ['deno', 'astro add deno'], + ], + 'Recommended: Integrations': [ ['tailwind', 'astro add tailwind'], ['partytown', 'astro add partytown'], ['sitemap', 'astro add sitemap'], @@ -85,9 +91,7 @@ export default async function add(names: string[], { cwd, flags, logging, teleme ['deno', 'astro add deno'], ], }, - description: `Check out the full integration catalog: ${cyan( - 'https://astro.build/integrations' - )}`, + description: `For more integrations, check out: ${cyan('https://astro.build/integrations')}`, }); return; } diff --git a/packages/astro/src/core/build/generate.ts b/packages/astro/src/core/build/generate.ts index 55cd4e8c174f..876736e76367 100644 --- a/packages/astro/src/core/build/generate.ts +++ b/packages/astro/src/core/build/generate.ts @@ -18,7 +18,7 @@ import { debug, info } from '../logger/core.js'; import { render } from '../render/core.js'; import { createLinkStylesheetElementSet, createModuleScriptsSet } from '../render/ssr-element.js'; import { createRequest } from '../request.js'; -import { getOutputFilename, isBuildingToSSR } from '../util.js'; +import { getOutputFilename } from '../util.js'; import { getOutFile, getOutFolder } from './common.js'; import { eachPageData, getPageDataByComponent } from './internal.js'; import type { PageBuildData, SingleFileBuiltModule, StaticBuildOptions } from './types'; @@ -97,7 +97,7 @@ export async function generatePages( const timer = performance.now(); info(opts.logging, null, `\n${bgGreen(black(' generating static routes '))}`); - const ssr = isBuildingToSSR(opts.astroConfig); + const ssr = opts.astroConfig.output === 'server'; const serverEntry = opts.buildConfig.serverEntry; const outFolder = ssr ? opts.buildConfig.server : opts.astroConfig.outDir; const ssrEntryURL = new URL('./' + serverEntry + `?time=${Date.now()}`, outFolder); @@ -207,7 +207,7 @@ async function generatePath( } } - const ssr = isBuildingToSSR(opts.astroConfig); + const ssr = opts.astroConfig.output === 'server'; const url = new URL(opts.astroConfig.base + removeLeadingForwardSlash(pathname), origin); const options: RenderOptions = { adapterName: undefined, diff --git a/packages/astro/src/core/build/index.ts b/packages/astro/src/core/build/index.ts index f35e2d12f6a9..41b18556a30c 100644 --- a/packages/astro/src/core/build/index.ts +++ b/packages/astro/src/core/build/index.ts @@ -1,5 +1,11 @@ import type { AstroTelemetry } from '@astrojs/telemetry'; -import type { AstroConfig, BuildConfig, ManifestData, RuntimeMode } from '../../@types/astro'; +import type { + AstroAdapter, + AstroConfig, + BuildConfig, + ManifestData, + RuntimeMode, +} from '../../@types/astro'; import type { LogOptions } from '../logger/core'; import fs from 'fs'; @@ -18,7 +24,7 @@ import { debug, info, levels, timerMessage } from '../logger/core.js'; import { apply as applyPolyfill } from '../polyfill.js'; import { RouteCache } from '../render/route-cache.js'; import { createRouteManifest } from '../routing/index.js'; -import { createSafeError, isBuildingToSSR } from '../util.js'; +import { createSafeError } from '../util.js'; import { collectPagesData } from './page-data.js'; import { staticBuild } from './static-build.js'; import { getTimeStat } from './util.js'; @@ -98,11 +104,14 @@ class AstroBuilder { client: new URL('./client/', this.config.outDir), server: new URL('./server/', this.config.outDir), serverEntry: 'entry.mjs', - staticMode: undefined, }; await runHookBuildStart({ config: this.config, buildConfig }); - info(this.logging, 'build', 'Collecting build information...'); + 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', 'Collecting build info...'); this.timer.loadStart = performance.now(); const { assets, allPages } = await collectPagesData({ astroConfig: this.config, @@ -111,7 +120,7 @@ class AstroBuilder { origin, routeCache: this.routeCache, viteServer, - ssr: isBuildingToSSR(this.config), + ssr: this.config.output === 'server', }); debug('build', timerMessage('All pages loaded', this.timer.loadStart)); @@ -168,12 +177,11 @@ class AstroBuilder { }); if (this.logging.level && levels[this.logging.level] <= levels['info']) { - const buildMode = isBuildingToSSR(this.config) ? 'ssr' : 'static'; await this.printStats({ logging: this.logging, timeStart: this.timer.init, pageCount: pageNames.length, - buildMode, + buildMode: this.config.output, }); } } @@ -198,7 +206,7 @@ class AstroBuilder { logging: LogOptions; timeStart: number; pageCount: number; - buildMode: 'static' | 'ssr'; + buildMode: 'static' | 'server'; }) { const total = getTimeStat(timeStart, performance.now()); diff --git a/packages/astro/src/core/build/page-data.ts b/packages/astro/src/core/build/page-data.ts index 23fb0283da29..74c1e45aa995 100644 --- a/packages/astro/src/core/build/page-data.ts +++ b/packages/astro/src/core/build/page-data.ts @@ -10,7 +10,6 @@ import { debug } from '../logger/core.js'; import { removeTrailingForwardSlash } from '../path.js'; import { callGetStaticPaths, RouteCache, RouteCacheEntry } from '../render/route-cache.js'; import { matchRoute } from '../routing/match.js'; -import { isBuildingToSSR } from '../util.js'; export interface CollectPagesDataOptions { astroConfig: AstroConfig; @@ -36,9 +35,6 @@ export async function collectPagesData( const assets: Record = {}; const allPages: AllPagesData = {}; const builtPaths = new Set(); - - const buildMode = isBuildingToSSR(astroConfig) ? 'ssr' : 'static'; - const dataCollectionLogTimeout = setInterval(() => { info(opts.logging, 'build', 'The data collection step may take longer for larger projects...'); clearInterval(dataCollectionLogTimeout); @@ -72,7 +68,7 @@ export async function collectPagesData( }; clearInterval(routeCollectionLogTimeout); - if (buildMode === 'static') { + if (astroConfig.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 33acb71618fe..931285eeee2a 100644 --- a/packages/astro/src/core/build/static-build.ts +++ b/packages/astro/src/core/build/static-build.ts @@ -6,12 +6,11 @@ import { fileURLToPath } from 'url'; import * as vite from 'vite'; import { BuildInternals, createBuildInternals } from '../../core/build/internal.js'; import { prependForwardSlash } from '../../core/path.js'; -import { emptyDir, removeDir } from '../../core/util.js'; +import { emptyDir, removeDir, isModeServerWithNoAdapter } from '../../core/util.js'; import { runHookBuildSetup } from '../../integrations/index.js'; import { rollupPluginAstroBuildCSS } from '../../vite-plugin-build-css/index.js'; import type { ViteConfigWithSSR } from '../create-vite'; import { info } from '../logger/core.js'; -import { isBuildingToSSR } from '../util.js'; import { generatePages } from './generate.js'; import { trackPageData } from './internal.js'; import type { PageBuildData, StaticBuildOptions } from './types'; @@ -25,6 +24,21 @@ import { injectManifest, vitePluginSSR } from './vite-plugin-ssr.js'; export async function staticBuild(opts: StaticBuildOptions) { const { allPages, astroConfig } = opts; + // Verify this app is buildable. + if(isModeServerWithNoAdapter(opts.astroConfig)) { + throw new Error(`Cannot use \`output: 'server'\` without an adapter. +Install and configure the appropriate server adapter for your final deployment. +Example: + + // astro.config.js + import netlify from '@astrojs/netlify'; + export default { + output: 'server', + adapter: netlify(), + } +`) + } + // The pages to be built for rendering purposes. const pageInput = new Set(); @@ -60,9 +74,7 @@ export async function staticBuild(opts: StaticBuildOptions) { info( opts.logging, 'build', - isBuildingToSSR(astroConfig) - ? 'Building SSR entrypoints...' - : 'Building entrypoints for prerendering...' + `Building ${astroConfig.output} entrypoints...` ); const ssrResult = (await ssrBuild(opts, internals, pageInput)) as RollupOutput; info(opts.logging, 'build', dim(`Completed in ${getTimeStat(timer.ssr, performance.now())}.`)); @@ -83,7 +95,7 @@ export async function staticBuild(opts: StaticBuildOptions) { await clientBuild(opts, internals, clientInput); timer.generate = performance.now(); - if (opts.buildConfig.staticMode) { + if (astroConfig.output === 'static') { try { await generatePages(ssrResult, opts, internals, facadeIdToPageDataMap); } finally { @@ -100,7 +112,7 @@ export async function staticBuild(opts: StaticBuildOptions) { async function ssrBuild(opts: StaticBuildOptions, internals: BuildInternals, input: Set) { const { astroConfig, viteConfig } = opts; - const ssr = isBuildingToSSR(astroConfig); + const ssr = astroConfig.output === 'server'; const out = ssr ? opts.buildConfig.server : astroConfig.outDir; const viteBuildConfig: ViteConfigWithSSR = { @@ -144,7 +156,7 @@ async function ssrBuild(opts: StaticBuildOptions, internals: BuildInternals, inp }), ...(viteConfig.plugins || []), // SSR needs to be last - isBuildingToSSR(opts.astroConfig) && vitePluginSSR(internals, opts.astroConfig._ctx.adapter!), + opts.astroConfig.output === 'server' && vitePluginSSR(internals, opts.astroConfig._ctx.adapter!), vitePluginAnalyzer(opts.astroConfig, internals), ], publicDir: ssr ? false : viteConfig.publicDir, @@ -174,7 +186,7 @@ async function clientBuild( ) { const { astroConfig, viteConfig } = opts; const timer = performance.now(); - const ssr = isBuildingToSSR(astroConfig); + const ssr = astroConfig.output === 'server'; const out = ssr ? opts.buildConfig.client : astroConfig.outDir; // Nothing to do if there is no client-side JS. @@ -275,7 +287,7 @@ async function copyFiles(fromFolder: URL, toFolder: URL) { async function ssrMoveAssets(opts: StaticBuildOptions) { info(opts.logging, 'build', 'Rearranging server assets...'); - const serverRoot = opts.buildConfig.staticMode + const serverRoot = opts.astroConfig.output === 'static' ? opts.buildConfig.client : opts.buildConfig.server; const clientRoot = opts.buildConfig.client; diff --git a/packages/astro/src/core/build/vite-plugin-pages.ts b/packages/astro/src/core/build/vite-plugin-pages.ts index 1158d69be42c..ceaf87eebfe3 100644 --- a/packages/astro/src/core/build/vite-plugin-pages.ts +++ b/packages/astro/src/core/build/vite-plugin-pages.ts @@ -1,6 +1,5 @@ import type { Plugin as VitePlugin } from 'vite'; import { pagesVirtualModuleId, resolvedPagesVirtualModuleId } from '../app/index.js'; -import { isBuildingToSSR } from '../util.js'; import { addRollupInput } from './add-rollup-input.js'; import type { BuildInternals } from './internal.js'; import { eachPageData } from './internal.js'; @@ -11,7 +10,7 @@ export function vitePluginPages(opts: StaticBuildOptions, internals: BuildIntern name: '@astro/plugin-build-pages', options(options) { - if (!isBuildingToSSR(opts.astroConfig)) { + if (opts.astroConfig.output === 'static') { return addRollupInput(options, [pagesVirtualModuleId]); } }, diff --git a/packages/astro/src/core/config.ts b/packages/astro/src/core/config.ts index ff6b1557c83e..86cf10a5459f 100644 --- a/packages/astro/src/core/config.ts +++ b/packages/astro/src/core/config.ts @@ -15,6 +15,7 @@ import { mergeConfig as mergeViteConfig } from 'vite'; import { z } from 'zod'; import { appendForwardSlash, prependForwardSlash, trimSlashes } from './path.js'; import { arraify, isObject } from './util.js'; +import { LogOptions, warn } from './logger/core.js'; load.use([loadTypeScript]); @@ -92,7 +93,6 @@ export const LEGACY_ASTRO_CONFIG_KEYS = new Set([ ]); export const AstroConfigSchema = z.object({ - adapter: z.object({ name: z.string(), hooks: z.object({}).passthrough().default({}) }).optional(), root: z .string() .optional() @@ -127,6 +127,19 @@ export const AstroConfigSchema = z.object({ .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 @@ -153,14 +166,6 @@ export const AstroConfigSchema = z.object({ .optional() .default({}) ), - 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) - ), style: z .object({ postcss: z @@ -227,7 +232,8 @@ export const AstroConfigSchema = z.object({ export async function validateConfig( userConfig: any, root: string, - cmd: string + cmd: string, + logging: LogOptions ): Promise { const fileProtocolRoot = pathToFileURL(root + path.sep); // Manual deprecation checks @@ -388,6 +394,7 @@ interface LoadConfigOptions { flags?: Flags; cmd: string; validate?: boolean; + logging: LogOptions; } /** @@ -459,7 +466,13 @@ export async function openConfig(configOptions: LoadConfigOptions): Promise { const mergedConfig = mergeCLIFlags(userConfig, flags, cmd); - const validatedConfig = await validateConfig(mergedConfig, root, cmd); + const validatedConfig = await validateConfig(mergedConfig, root, cmd, logging); return validatedConfig; } diff --git a/packages/astro/src/core/endpoint/dev/index.ts b/packages/astro/src/core/endpoint/dev/index.ts index 1e7abe8df722..342c9a4f400d 100644 --- a/packages/astro/src/core/endpoint/dev/index.ts +++ b/packages/astro/src/core/endpoint/dev/index.ts @@ -1,13 +1,12 @@ import type { EndpointHandler } from '../../../@types/astro'; import type { SSROptions } from '../../render/dev'; import { preload } from '../../render/dev/index.js'; -import { isBuildingToSSR } from '../../util.js'; import { call as callEndpoint } from '../index.js'; export async function call(ssrOpts: SSROptions) { const [, mod] = await preload(ssrOpts); return await callEndpoint(mod as unknown as EndpointHandler, { ...ssrOpts, - ssr: isBuildingToSSR(ssrOpts.astroConfig), + ssr: ssrOpts.astroConfig.output === 'server', }); } diff --git a/packages/astro/src/core/logger/node.ts b/packages/astro/src/core/logger/node.ts index b655b2631925..23bb2e8bc0d7 100644 --- a/packages/astro/src/core/logger/node.ts +++ b/packages/astro/src/core/logger/node.ts @@ -125,7 +125,6 @@ export const logger = { }; export function enableVerboseLogging() { - //debugPackage.enable('*,-babel'); debug('cli', '--verbose flag enabled! Enabling: DEBUG="*,-babel"'); debug( 'cli', diff --git a/packages/astro/src/core/render/dev/index.ts b/packages/astro/src/core/render/dev/index.ts index df0d131778a6..a6643fc359cb 100644 --- a/packages/astro/src/core/render/dev/index.ts +++ b/packages/astro/src/core/render/dev/index.ts @@ -11,7 +11,7 @@ import type { } from '../../../@types/astro'; import { prependForwardSlash } from '../../../core/path.js'; import { LogOptions } from '../../logger/core.js'; -import { isBuildingToSSR, isPage } from '../../util.js'; +import { isPage } from '../../util.js'; import { render as coreRender } from '../core.js'; import { RouteCache } from '../route-cache.js'; import { collectMdMetadata } from '../util.js'; @@ -194,7 +194,7 @@ export async function render( route, routeCache, site: astroConfig.site ? new URL(astroConfig.base, astroConfig.site).toString() : undefined, - ssr: isBuildingToSSR(astroConfig), + ssr: astroConfig.output === 'server', streaming: true, }); diff --git a/packages/astro/src/core/render/result.ts b/packages/astro/src/core/render/result.ts index 6e5da1d699ce..e6aab55b366e 100644 --- a/packages/astro/src/core/render/result.ts +++ b/packages/astro/src/core/render/result.ts @@ -116,6 +116,7 @@ export function createResult(args: CreateResultArgs): SSRResult { const headers = new Headers(); if (args.streaming) { headers.set('Transfer-Encoding', 'chunked'); + headers.set('Content-Type', 'text/html'); } else { headers.set('Content-Type', 'text/html'); } diff --git a/packages/astro/src/core/request.ts b/packages/astro/src/core/request.ts index 59d8acae92fb..24356983f768 100644 --- a/packages/astro/src/core/request.ts +++ b/packages/astro/src/core/request.ts @@ -56,7 +56,7 @@ export function createRequest({ warn( logging, 'ssg', - `Headers are not exposed in static-site generation (SSG) mode. To enable reading headers you need to set an SSR adapter in your config.` + `Headers are not exposed in static (SSG) output mode. To enable headers: set \`output: "server"\` in your config file.` ); return _headers; }, diff --git a/packages/astro/src/core/util.ts b/packages/astro/src/core/util.ts index 76c2c310a884..4ba0a7286649 100644 --- a/packages/astro/src/core/util.ts +++ b/packages/astro/src/core/util.ts @@ -182,15 +182,8 @@ export function isPage(file: URL, config: AstroConfig): boolean { return endsWithPageExt(file, config); } -export function isBuildingToSSR(config: AstroConfig): boolean { - const adapter = config._ctx.adapter; - if (!adapter) return false; - - if (typeof adapter.serverEntrypoint === 'string') { - return true; - } else { - return false; - } +export function isModeServerWithNoAdapter(config: AstroConfig): boolean { + return config.output === 'server' && !config._ctx.adapter; } export function emoji(char: string, fallback: string) { diff --git a/packages/astro/src/events/session.ts b/packages/astro/src/events/session.ts index 3fc3be723822..f2c26d980750 100644 --- a/packages/astro/src/events/session.ts +++ b/packages/astro/src/events/session.ts @@ -1,6 +1,4 @@ -import { createRequire } from 'node:module'; import type { AstroUserConfig } from '../@types/astro'; -const require = createRequire(import.meta.url); const EVENT_SESSION = 'ASTRO_CLI_SESSION_STARTED'; diff --git a/packages/astro/src/integrations/index.ts b/packages/astro/src/integrations/index.ts index d13d05d52045..a005b62b6beb 100644 --- a/packages/astro/src/integrations/index.ts +++ b/packages/astro/src/integrations/index.ts @@ -7,12 +7,10 @@ import { HookParameters, RouteData, } from '../@types/astro.js'; -import ssgAdapter from '../adapter-ssg/index.js'; import type { SerializedSSRManifest } from '../core/app/types'; import type { PageBuildData } from '../core/build/types'; import { mergeConfig } from '../core/config.js'; import type { ViteConfigWithSSR } from '../core/create-vite.js'; -import { isBuildingToSSR } from '../core/util.js'; export async function runHookConfigSetup({ config: _config, @@ -21,8 +19,9 @@ export async function runHookConfigSetup({ config: AstroConfig; command: 'dev' | 'build'; }): Promise { + // An adapter is an integration, so if one is provided push it. if (_config.adapter) { - _config.integrations.push(_config.adapter); + _config.integrations.push(_config.adapter); } let updatedConfig: AstroConfig = { ..._config }; @@ -80,7 +79,7 @@ export async function runHookConfigDone({ config }: { config: AstroConfig }) { setAdapter(adapter) { if (config._ctx.adapter && config._ctx.adapter.name !== adapter.name) { throw new Error( - `Adapter already set to ${config._ctx.adapter.name}. You can only have one adapter.` + `Integration "${integration.name}" conflicts with "${config._ctx.adapter.name}". You can only configure one deployment integration.` ); } config._ctx.adapter = adapter; @@ -88,22 +87,9 @@ export async function runHookConfigDone({ config }: { config: AstroConfig }) { }); } } - // Call the default adapter - if (!config._ctx.adapter) { - const integration = ssgAdapter(); - config.integrations.push(integration); - if (integration?.hooks?.['astro:config:done']) { - await integration.hooks['astro:config:done']({ - config, - setAdapter(adapter) { - config._ctx.adapter = adapter; - }, - }); - } - } } -export async function runHookServerSetup({ +export async function runHookServerSetup({ config, server, }: { @@ -203,7 +189,7 @@ export async function runHookBuildDone({ pages: string[]; routes: RouteData[]; }) { - const dir = isBuildingToSSR(config) ? buildConfig.client : config.outDir; + const dir = config.output === 'server' ? buildConfig.client : config.outDir; for (const integration of config.integrations) { if (integration?.hooks?.['astro:build:done']) { diff --git a/packages/astro/src/vite-plugin-astro-server/index.ts b/packages/astro/src/vite-plugin-astro-server/index.ts index 86012991ef09..3449b7d3a801 100644 --- a/packages/astro/src/vite-plugin-astro-server/index.ts +++ b/packages/astro/src/vite-plugin-astro-server/index.ts @@ -20,7 +20,7 @@ import { preload, ssr } from '../core/render/dev/index.js'; import { RouteCache } from '../core/render/route-cache.js'; import { createRequest } from '../core/request.js'; import { createRouteManifest, matchRoute } from '../core/routing/index.js'; -import { createSafeError, isBuildingToSSR, resolvePages } from '../core/util.js'; +import { createSafeError, resolvePages } from '../core/util.js'; import notFoundTemplate, { subpathNotUsedTemplate } from '../template/4xx.js'; interface AstroPluginOptions { @@ -205,7 +205,7 @@ async function handleRequest( ) { const reqStart = performance.now(); const origin = `${viteServer.config.server.https ? 'https' : 'http'}://${req.headers.host}`; - const buildingToSSR = isBuildingToSSR(config); + const buildingToSSR = config.output === 'server'; // Ignore `.html` extensions and `index.html` in request URLS to ensure that // routing behavior matches production builds. This supports both file and directory // build formats, and is necessary based on how the manifest tracks build targets. @@ -276,7 +276,7 @@ async function handleRequest( routeCache, pathname: pathname, logging, - ssr: isBuildingToSSR(config), + ssr: config.output === 'server', }); if (paramsAndPropsRes === GetParamsAndPropsError.NoMatchingStaticPath) { warn( diff --git a/packages/astro/test/client-address.test.js b/packages/astro/test/client-address.test.js index c356c16ebd6f..a3c65835d21d 100644 --- a/packages/astro/test/client-address.test.js +++ b/packages/astro/test/client-address.test.js @@ -12,9 +12,7 @@ describe('Astro.clientAddress', () => { before(async () => { fixture = await loadFixture({ root: './fixtures/client-address/', - experimental: { - ssr: true, - }, + output: 'server', adapter: testAdapter(), }); }); @@ -67,9 +65,7 @@ describe('Astro.clientAddress', () => { before(async () => { fixture = await loadFixture({ root: './fixtures/client-address/', - experimental: { - ssr: true, - }, + output: 'server', adapter: testAdapter({ provideAddress: false }), }); await fixture.build(); @@ -90,6 +86,7 @@ describe('Astro.clientAddress', () => { before(async () => { fixture = await loadFixture({ root: './fixtures/client-address/', + output: 'static', }); }); diff --git a/packages/astro/test/config-mode.test.js b/packages/astro/test/config-mode.test.js new file mode 100644 index 000000000000..5c623fdce7c7 --- /dev/null +++ b/packages/astro/test/config-mode.test.js @@ -0,0 +1,101 @@ +import { expect } from 'chai'; +import { load as cheerioLoad } from 'cheerio'; +import { loadFixture } from './test-utils.js'; +import testAdapter from './test-adapter.js'; + +describe('AstroConfig - config.mode', () => { + describe(`output: 'server'`, () => { + describe('deploy config provided', () => { + /** @type {import('./test-utils').Fixture} */ + let fixture; + + before(async () => { + fixture = await loadFixture({ + // This is just a random fixture to test, doesn't matter. + root: './fixtures/astro-basic/', + adapter: testAdapter(), + output: 'server', + }); + await fixture.build(); + }); + + it('Builds an SSR-able app', async () => { + const app = await fixture.loadTestAdapterApp(); + const request = new Request('http://example.com/'); + const response = await app.render(request); + expect(response.status).to.equal(200); + expect(response.headers.get('content-type')).to.equal('text/html'); + const html = await response.text(); + expect(html.length).to.be.greaterThan(0); + }); + }); + + describe('deploy config omitted', () => { + /** @type {import('./test-utils').Fixture} */ + let fixture; + + before(async () => { + fixture = await loadFixture({ + // This is just a random fixture to test, doesn't matter. + root: './fixtures/astro-basic/', + output: 'server' + }); + + }); + + it('Throws during the build', async () => { + let built = false; + try { + await fixture.build(); + built = true; + } catch(err) { + expect(err).to.be.an.instanceOf(Error); + expect(err.message).to.match(/without an adapter/); + } + + expect(built).to.equal(false, 'Should not have built'); + }); + }); + }); + + describe(`output: 'static'`, () => { + describe('Deploy config omitted', () => { + /** @type {import('./test-utils').Fixture} */ + let fixture; + + before(async () => { + fixture = await loadFixture({ + // This is just a random fixture to test, doesn't matter. + root: './fixtures/astro-basic/', + output: 'static' + }); + await fixture.build(); + }); + + it('Builds to static HTML', async () => { + let html; + try { + html = await fixture.readFile('/index.html'); + } catch(err) { + expect(false).to.equal(true, 'Couldnt find the file, which mean it did not build.'); + } + expect(html.length).to.be.greaterThan(0); + }); + }); + + describe.skip('deploy config provided - TODO, we need adapters to support static mode first', () => { + /** @type {import('./test-utils').Fixture} */ + let fixture; + + before(async () => { + fixture = await loadFixture({ + // This is just a random fixture to test, doesn't matter. + root: './fixtures/astro-basic/', + adapter: testAdapter(), + output: 'server' + }); + await fixture.build(); + }); + }); + }); +}); diff --git a/packages/astro/test/fixtures/static-build-ssr/astro.config.mjs b/packages/astro/test/fixtures/static-build-ssr/astro.config.mjs index cf1980887653..3d24e915eb37 100644 --- a/packages/astro/test/fixtures/static-build-ssr/astro.config.mjs +++ b/packages/astro/test/fixtures/static-build-ssr/astro.config.mjs @@ -3,4 +3,5 @@ import nodejs from '@astrojs/node'; export default defineConfig({ adapter: nodejs(), + output: 'server', }); diff --git a/packages/astro/test/ssr-404-500-pages.test.js b/packages/astro/test/ssr-404-500-pages.test.js index 37ff3d3a5ecd..4f5f1f8cf9de 100644 --- a/packages/astro/test/ssr-404-500-pages.test.js +++ b/packages/astro/test/ssr-404-500-pages.test.js @@ -10,9 +10,7 @@ describe('404 and 500 pages', () => { before(async () => { fixture = await loadFixture({ root: './fixtures/ssr-api-route-custom-404/', - experimental: { - ssr: true, - }, + output: 'server', adapter: testAdapter(), }); await fixture.build({}); diff --git a/packages/astro/test/ssr-adapter-build-config.test.js b/packages/astro/test/ssr-adapter-build-config.test.js index 1f76d7e6a275..ab0ccf3af42e 100644 --- a/packages/astro/test/ssr-adapter-build-config.test.js +++ b/packages/astro/test/ssr-adapter-build-config.test.js @@ -11,9 +11,7 @@ describe('Integration buildConfig hook', () => { let _config; fixture = await loadFixture({ root: './fixtures/ssr-request/', - experimental: { - ssr: true, - }, + output: 'server', adapter: { name: 'my-ssr-adapter', hooks: { diff --git a/packages/astro/test/ssr-api-route.test.js b/packages/astro/test/ssr-api-route.test.js index ec6e65641434..8b007391dfa6 100644 --- a/packages/astro/test/ssr-api-route.test.js +++ b/packages/astro/test/ssr-api-route.test.js @@ -9,9 +9,7 @@ describe('API routes in SSR', () => { before(async () => { fixture = await loadFixture({ root: './fixtures/ssr-api-route/', - experimental: { - ssr: true, - }, + output: 'server', adapter: testAdapter(), }); await fixture.build(); diff --git a/packages/astro/test/ssr-assets.test.js b/packages/astro/test/ssr-assets.test.js index ca62fbd88b77..e02045bc04db 100644 --- a/packages/astro/test/ssr-assets.test.js +++ b/packages/astro/test/ssr-assets.test.js @@ -9,9 +9,7 @@ describe('SSR Assets', () => { before(async () => { fixture = await loadFixture({ root: './fixtures/ssr-assets/', - experimental: { - ssr: true, - }, + output: 'server', adapter: testAdapter(), }); await fixture.build(); diff --git a/packages/astro/test/ssr-dynamic.test.js b/packages/astro/test/ssr-dynamic.test.js index f2795794f5c1..a75ace7e3c2a 100644 --- a/packages/astro/test/ssr-dynamic.test.js +++ b/packages/astro/test/ssr-dynamic.test.js @@ -10,9 +10,7 @@ describe('Dynamic pages in SSR', () => { before(async () => { fixture = await loadFixture({ root: './fixtures/ssr-dynamic/', - experimental: { - ssr: true, - }, + output: 'server', adapter: testAdapter(), }); await fixture.build(); diff --git a/packages/astro/test/ssr-env.test.js b/packages/astro/test/ssr-env.test.js index da823fa201c0..fb369482dff1 100644 --- a/packages/astro/test/ssr-env.test.js +++ b/packages/astro/test/ssr-env.test.js @@ -10,9 +10,7 @@ describe('SSR Environment Variables', () => { before(async () => { fixture = await loadFixture({ root: './fixtures/ssr-env/', - experimental: { - ssr: true, - }, + output: 'server', adapter: testAdapter(), }); await fixture.build(); diff --git a/packages/astro/test/ssr-hoisted-script.test.js b/packages/astro/test/ssr-hoisted-script.test.js index 134744ae9f16..7d31875ffdf5 100644 --- a/packages/astro/test/ssr-hoisted-script.test.js +++ b/packages/astro/test/ssr-hoisted-script.test.js @@ -10,9 +10,7 @@ describe('Hoisted scripts in SSR', () => { before(async () => { fixture = await loadFixture({ root: './fixtures/ssr-hoisted-script/', - experimental: { - ssr: true, - }, + output: 'server', adapter: testAdapter(), }); await fixture.build(); diff --git a/packages/astro/test/ssr-large-array.test.js b/packages/astro/test/ssr-large-array.test.js index 64a82222b32b..0145a2fe4591 100644 --- a/packages/astro/test/ssr-large-array.test.js +++ b/packages/astro/test/ssr-large-array.test.js @@ -10,9 +10,7 @@ describe('SSR with Large Array and client rendering', () => { before(async () => { fixture = await loadFixture({ root: './fixtures/large-array/', - experimental: { - ssr: true, - }, + output: 'server', adapter: testAdapter(), }); await fixture.build(); diff --git a/packages/astro/test/ssr-lit.test.js b/packages/astro/test/ssr-lit.test.js index 46dccd680bd6..53555b9838ac 100644 --- a/packages/astro/test/ssr-lit.test.js +++ b/packages/astro/test/ssr-lit.test.js @@ -10,9 +10,7 @@ describe('Lit integration in SSR', () => { before(async () => { fixture = await loadFixture({ root: './fixtures/lit-element/', - experimental: { - ssr: true, - }, + output: 'server', adapter: testAdapter(), }); await fixture.build(); diff --git a/packages/astro/test/ssr-markdown.test.js b/packages/astro/test/ssr-markdown.test.js index 40c12449e872..4f9ae9f940d6 100644 --- a/packages/astro/test/ssr-markdown.test.js +++ b/packages/astro/test/ssr-markdown.test.js @@ -10,9 +10,7 @@ describe('Markdown pages in SSR', () => { before(async () => { fixture = await loadFixture({ root: './fixtures/ssr-markdown/', - experimental: { - ssr: true, - }, + output: 'server', adapter: testAdapter(), }); await fixture.build(); diff --git a/packages/astro/test/ssr-partytown.test.js b/packages/astro/test/ssr-partytown.test.js index 5ea2bcde59e9..4978ead32347 100644 --- a/packages/astro/test/ssr-partytown.test.js +++ b/packages/astro/test/ssr-partytown.test.js @@ -11,9 +11,7 @@ describe('Using the Partytown integration in SSR', () => { fixture = await loadFixture({ root: './fixtures/ssr-partytown/', adapter: testAdapter(), - experimental: { - ssr: true, - }, + output: 'server' }); await fixture.build(); }); diff --git a/packages/astro/test/ssr-redirect.test.js b/packages/astro/test/ssr-redirect.test.js index 29fe701f7a00..dd23e26d382f 100644 --- a/packages/astro/test/ssr-redirect.test.js +++ b/packages/astro/test/ssr-redirect.test.js @@ -9,9 +9,7 @@ describe('Astro.redirect', () => { before(async () => { fixture = await loadFixture({ root: './fixtures/ssr-redirect/', - experimental: { - ssr: true, - }, + output: 'server', adapter: testAdapter(), }); await fixture.build(); diff --git a/packages/astro/test/ssr-request.test.js b/packages/astro/test/ssr-request.test.js index a9c1dd6b6159..b8e0300416f6 100644 --- a/packages/astro/test/ssr-request.test.js +++ b/packages/astro/test/ssr-request.test.js @@ -11,9 +11,7 @@ describe('Using Astro.request in SSR', () => { fixture = await loadFixture({ root: './fixtures/ssr-request/', adapter: testAdapter(), - experimental: { - ssr: true, - }, + output: 'server' }); await fixture.build(); }); diff --git a/packages/astro/test/ssr-response.test.js b/packages/astro/test/ssr-response.test.js index ffabcbba4a34..2e3277e89b62 100644 --- a/packages/astro/test/ssr-response.test.js +++ b/packages/astro/test/ssr-response.test.js @@ -11,9 +11,7 @@ describe('Using Astro.response in SSR', () => { fixture = await loadFixture({ root: './fixtures/ssr-response/', adapter: testAdapter(), - experimental: { - ssr: true, - }, + output: 'server' }); await fixture.build(); }); diff --git a/packages/astro/test/ssr-scripts.test.js b/packages/astro/test/ssr-scripts.test.js index f44099e41c27..241ec894bce5 100644 --- a/packages/astro/test/ssr-scripts.test.js +++ b/packages/astro/test/ssr-scripts.test.js @@ -9,9 +9,7 @@ describe('SSR Hydrated component scripts', () => { before(async () => { fixture = await loadFixture({ root: './fixtures/ssr-scripts/', - experimental: { - ssr: true, - }, + output: 'server', adapter: testAdapter(), }); await fixture.build(); diff --git a/packages/astro/test/static-build.test.js b/packages/astro/test/static-build.test.js index ce470b2b6ccc..a8c67eff3c9d 100644 --- a/packages/astro/test/static-build.test.js +++ b/packages/astro/test/static-build.test.js @@ -176,7 +176,7 @@ describe('Static build', () => { for (const log of logs) { if ( log.type === 'ssg' && - /[hH]eaders are not exposed in static-site generation/.test(log.args[0]) + /[hH]eaders are not exposed in static \(SSG\) output mode/.test(log.args[0]) ) { found = true; } diff --git a/packages/astro/test/streaming.test.js b/packages/astro/test/streaming.test.js index fc7afba05be7..2ca8ee549c3c 100644 --- a/packages/astro/test/streaming.test.js +++ b/packages/astro/test/streaming.test.js @@ -13,9 +13,7 @@ describe('Streaming', () => { fixture = await loadFixture({ root: './fixtures/streaming/', adapter: testAdapter(), - experimental: { - ssr: true, - }, + output: 'server' }); }); @@ -82,9 +80,7 @@ describe('Streaming disabled', () => { fixture = await loadFixture({ root: './fixtures/streaming/', adapter: testAdapter(), - experimental: { - ssr: true, - }, + output: 'server', server: { streaming: false, }, diff --git a/packages/astro/test/test-utils.js b/packages/astro/test/test-utils.js index ab43a94cc8ce..ded7e7745051 100644 --- a/packages/astro/test/test-utils.js +++ b/packages/astro/test/test-utils.js @@ -72,8 +72,14 @@ export async function loadFixture(inlineConfig) { } } + /** @type {import('../src/core/logger/core').LogOptions} */ + const logging = { + dest: nodeLogDestination, + level: 'error', + }; + // Load the config. - let config = await loadConfig({ cwd: fileURLToPath(cwd) }); + let config = await loadConfig({ cwd: fileURLToPath(cwd), logging }); config = merge(config, { ...inlineConfig, root: cwd }); // HACK: the inline config doesn't run through config validation where these normalizations usually occur @@ -89,12 +95,6 @@ export async function loadFixture(inlineConfig) { config._ctx.renderers.unshift(jsxRenderer); } - /** @type {import('../src/core/logger/core').LogOptions} */ - const logging = { - dest: nodeLogDestination, - level: 'error', - }; - /** @type {import('@astrojs/telemetry').AstroTelemetry} */ const telemetry = { record() { diff --git a/packages/integrations/cloudflare/README.md b/packages/integrations/cloudflare/README.md index 3417149b84c0..d2c886df6820 100644 --- a/packages/integrations/cloudflare/README.md +++ b/packages/integrations/cloudflare/README.md @@ -9,6 +9,7 @@ import { defineConfig } from 'astro/config'; import cloudflare from '@astrojs/cloudflare'; export default defineConfig({ + output: 'server', adapter: cloudflare() }); ``` diff --git a/packages/integrations/cloudflare/src/index.ts b/packages/integrations/cloudflare/src/index.ts index dc65f23ce5b0..7070af17fb95 100644 --- a/packages/integrations/cloudflare/src/index.ts +++ b/packages/integrations/cloudflare/src/index.ts @@ -21,6 +21,11 @@ export default function createIntegration(): AstroIntegration { 'astro:config:done': ({ setAdapter, config }) => { setAdapter(getAdapter()); _config = config; + + if(config.output === 'static') { + console.warn(`[@astrojs/cloudflare] \`output: "server"\` is required to use this adapter.`); + console.warn(`[@astrojs/cloudflare] Otherwise, this adapter is not required to deploy a static site to Cloudflare.`); + } }, 'astro:build:start': ({ buildConfig }) => { _buildConfig = buildConfig; diff --git a/packages/integrations/deno/README.md b/packages/integrations/deno/README.md index bc8dfe8f6b0d..35fb27a622b9 100644 --- a/packages/integrations/deno/README.md +++ b/packages/integrations/deno/README.md @@ -37,6 +37,7 @@ import deno from '@astrojs/deno'; export default defineConfig({ // ... + output: 'server', adapter: deno() }); ``` @@ -69,6 +70,7 @@ import { defineConfig } from 'astro/config'; import deno from '@astrojs/deno'; export default defineConfig({ + output: 'server', adapter: deno({ //options go here }) @@ -85,6 +87,7 @@ export default defineConfig({ import deno from '@astrojs/deno'; export default defineConfig({ + output: 'server', adapter: deno({ start: false }) @@ -115,6 +118,7 @@ export default defineConfig({ import deno from '@astrojs/deno'; export default defineConfig({ + output: 'server', adapter: deno({ port: 8081, hostname: 'myhost' diff --git a/packages/integrations/deno/src/index.ts b/packages/integrations/deno/src/index.ts index 9a6df4f769de..73ccf01e0714 100644 --- a/packages/integrations/deno/src/index.ts +++ b/packages/integrations/deno/src/index.ts @@ -29,8 +29,13 @@ export default function createIntegration(args?: Options): AstroIntegration { return { name: '@astrojs/deno', hooks: { - 'astro:config:done': ({ setAdapter }) => { + 'astro:config:done': ({ setAdapter, config }) => { setAdapter(getAdapter(args)); + + if(config.output === 'static') { + console.warn(`[@astrojs/deno] \`output: "server"\` is required to use this adapter.`); + console.warn(`[@astrojs/deno] Otherwise, this adapter is not required to deploy a static site to Deno.`); + } }, 'astro:build:start': ({ buildConfig }) => { _buildConfig = buildConfig; diff --git a/packages/integrations/deno/test/fixtures/basics/astro.config.mjs b/packages/integrations/deno/test/fixtures/basics/astro.config.mjs index af5f1aa6b2ca..d01a93632d5c 100644 --- a/packages/integrations/deno/test/fixtures/basics/astro.config.mjs +++ b/packages/integrations/deno/test/fixtures/basics/astro.config.mjs @@ -5,7 +5,5 @@ import react from '@astrojs/react'; export default defineConfig({ adapter: deno(), integrations: [react()], - experimental: { - ssr: true - } + output: 'server', }) diff --git a/packages/integrations/deno/test/fixtures/dynimport/astro.config.mjs b/packages/integrations/deno/test/fixtures/dynimport/astro.config.mjs index e56fe2e985fd..d670faac6720 100644 --- a/packages/integrations/deno/test/fixtures/dynimport/astro.config.mjs +++ b/packages/integrations/deno/test/fixtures/dynimport/astro.config.mjs @@ -3,7 +3,5 @@ import deno from '@astrojs/deno'; export default defineConfig({ adapter: deno(), - experimental: { - ssr: true - } + output: 'server', }) diff --git a/packages/integrations/image/src/integration.ts b/packages/integrations/image/src/integration.ts index afbeb00a9651..725276d03cc7 100644 --- a/packages/integrations/image/src/integration.ts +++ b/packages/integrations/image/src/integration.ts @@ -16,7 +16,7 @@ export default function integration(options: IntegrationOptions = {}): AstroInte const staticImages = new Map>(); let _config: AstroConfig; - let mode: 'ssr' | 'ssg'; + let output: 'server' | 'static'; function getViteConfiguration() { return { @@ -37,11 +37,11 @@ export default function integration(options: IntegrationOptions = {}): AstroInte _config = config; // Always treat `astro dev` as SSR mode, even without an adapter - mode = command === 'dev' || config.adapter ? 'ssr' : 'ssg'; + output = command === 'dev' ? 'server' : config.output; updateConfig({ vite: getViteConfiguration() }); - if (mode === 'ssr') { + if (output === 'server') { injectRoute({ pattern: ROUTE_PATTERN, entryPoint: @@ -67,7 +67,7 @@ export default function integration(options: IntegrationOptions = {}): AstroInte // Helpers for building static images should only be available for SSG globalThis.astroImage = - mode === 'ssg' + output === 'static' ? { addStaticImage, filenameFormat, @@ -75,7 +75,7 @@ export default function integration(options: IntegrationOptions = {}): AstroInte : {}; }, 'astro:build:done': async ({ dir }) => { - if (mode === 'ssr') { + if (output === 'server') { // for SSR builds, copy all image files from src to dist // to make sure they are available for use in production await ssrBuild({ srcDir: _config.srcDir, outDir: dir }); diff --git a/packages/integrations/image/test/image-ssr.test.js b/packages/integrations/image/test/image-ssr.test.js index 0804f806859a..37274c929aa6 100644 --- a/packages/integrations/image/test/image-ssr.test.js +++ b/packages/integrations/image/test/image-ssr.test.js @@ -19,9 +19,7 @@ describe('SSR images - build', function () { fixture = await loadFixture({ root: './fixtures/basic-image/', adapter: testAdapter({ streaming: false }), - experimental: { - ssr: true, - }, + output: 'server', }); await fixture.build(); }); @@ -139,9 +137,7 @@ describe('SSR images - dev', function () { fixture = await loadFixture({ root: './fixtures/basic-image/', adapter: testAdapter(), - experimental: { - ssr: true, - }, + output: 'server', }); devServer = await fixture.startDevServer(); diff --git a/packages/integrations/image/test/picture-ssr.test.js b/packages/integrations/image/test/picture-ssr.test.js index 080cdbd2b4c4..ebfbdf7497ab 100644 --- a/packages/integrations/image/test/picture-ssr.test.js +++ b/packages/integrations/image/test/picture-ssr.test.js @@ -19,9 +19,7 @@ describe('SSR pictures - build', function () { fixture = await loadFixture({ root: './fixtures/basic-picture/', adapter: testAdapter(), - experimental: { - ssr: true, - }, + output: 'server', }); await fixture.build(); }); @@ -187,9 +185,7 @@ describe('SSR images - dev', function () { fixture = await loadFixture({ root: './fixtures/basic-picture/', adapter: testAdapter(), - experimental: { - ssr: true, - }, + output: 'server', }); devServer = await fixture.startDevServer(); diff --git a/packages/integrations/netlify/README.md b/packages/integrations/netlify/README.md index 5f6b61fa217d..0dc6f3db88aa 100644 --- a/packages/integrations/netlify/README.md +++ b/packages/integrations/netlify/README.md @@ -37,6 +37,7 @@ import { defineConfig } from 'astro/config'; import netlify from '@astrojs/netlify/functions'; export default defineConfig({ + output: 'server', adapter: netlify(), }); ``` @@ -51,6 +52,7 @@ import { defineConfig } from 'astro/config'; + import netlify from '@astrojs/netlify/edge-functions'; export default defineConfig({ + output: 'server', adapter: netlify(), }); ``` @@ -83,6 +85,7 @@ import { defineConfig } from 'astro/config'; import netlify from '@astrojs/netlify/functions'; export default defineConfig({ + output: 'server', adapter: netlify({ dist: new URL('./dist/', import.meta.url) }) diff --git a/packages/integrations/netlify/src/integration-edge-functions.ts b/packages/integrations/netlify/src/integration-edge-functions.ts index b3f27997d5aa..0556317d7ada 100644 --- a/packages/integrations/netlify/src/integration-edge-functions.ts +++ b/packages/integrations/netlify/src/integration-edge-functions.ts @@ -135,6 +135,11 @@ export function netlifyEdgeFunctions({ dist }: NetlifyEdgeFunctionsOptions = {}) 'astro:config:done': ({ config, setAdapter }) => { setAdapter(getAdapter()); _config = config; + + if(config.output === 'static') { + console.warn(`[@astrojs/netlify] \`output: "server"\` is required to use this adapter.`); + console.warn(`[@astrojs/netlify] Otherwise, this adapter is not required to deploy a static site to Netlify.`); + } }, 'astro:build:start': async ({ buildConfig }) => { _buildConfig = buildConfig; diff --git a/packages/integrations/netlify/src/integration-functions.ts b/packages/integrations/netlify/src/integration-functions.ts index e9b4aae33b38..d0b327f87eb3 100644 --- a/packages/integrations/netlify/src/integration-functions.ts +++ b/packages/integrations/netlify/src/integration-functions.ts @@ -35,6 +35,11 @@ function netlifyFunctions({ 'astro:config:done': ({ config, setAdapter }) => { setAdapter(getAdapter({ binaryMediaTypes })); _config = config; + + if(config.output === 'static') { + console.warn(`[@astrojs/netlify] \`output: "server"\` is required to use this adapter.`); + console.warn(`[@astrojs/netlify] Otherwise, this adapter is not required to deploy a static site to Netlify.`); + } }, 'astro:build:start': async ({ buildConfig }) => { entryFile = buildConfig.serverEntry.replace(/\.m?js/, ''); diff --git a/packages/integrations/netlify/test/edge-functions/fixtures/dynimport/astro.config.mjs b/packages/integrations/netlify/test/edge-functions/fixtures/dynimport/astro.config.mjs index c55135e43b7a..cd758352b5f7 100644 --- a/packages/integrations/netlify/test/edge-functions/fixtures/dynimport/astro.config.mjs +++ b/packages/integrations/netlify/test/edge-functions/fixtures/dynimport/astro.config.mjs @@ -5,7 +5,5 @@ export default defineConfig({ adapter: netlifyEdgeFunctions({ dist: new URL('./dist/', import.meta.url), }), - experimental: { - ssr: true - } + output: 'server', }) diff --git a/packages/integrations/netlify/test/edge-functions/fixtures/edge-basic/astro.config.mjs b/packages/integrations/netlify/test/edge-functions/fixtures/edge-basic/astro.config.mjs index d7c89926484f..a08e8e89d382 100644 --- a/packages/integrations/netlify/test/edge-functions/fixtures/edge-basic/astro.config.mjs +++ b/packages/integrations/netlify/test/edge-functions/fixtures/edge-basic/astro.config.mjs @@ -7,7 +7,5 @@ export default defineConfig({ dist: new URL('./dist/', import.meta.url), }), integrations: [react()], - experimental: { - ssr: true - } + output: 'server', }) diff --git a/packages/integrations/netlify/test/edge-functions/fixtures/root-dynamic/astro.config.mjs b/packages/integrations/netlify/test/edge-functions/fixtures/root-dynamic/astro.config.mjs index c55135e43b7a..cd758352b5f7 100644 --- a/packages/integrations/netlify/test/edge-functions/fixtures/root-dynamic/astro.config.mjs +++ b/packages/integrations/netlify/test/edge-functions/fixtures/root-dynamic/astro.config.mjs @@ -5,7 +5,5 @@ export default defineConfig({ adapter: netlifyEdgeFunctions({ dist: new URL('./dist/', import.meta.url), }), - experimental: { - ssr: true - } + output: 'server', }) diff --git a/packages/integrations/netlify/test/functions/base64-response.test.js b/packages/integrations/netlify/test/functions/base64-response.test.js index 064cff1542bd..6e59bd192210 100644 --- a/packages/integrations/netlify/test/functions/base64-response.test.js +++ b/packages/integrations/netlify/test/functions/base64-response.test.js @@ -9,9 +9,7 @@ describe('Base64 Responses', () => { before(async () => { fixture = await loadFixture({ root: new URL('./fixtures/base64-response/', import.meta.url).toString(), - experimental: { - ssr: true, - }, + output: 'server', adapter: netlifyAdapter({ dist: new URL('./fixtures/base64-response/dist/', import.meta.url), binaryMediaTypes: ['font/otf'], diff --git a/packages/integrations/netlify/test/functions/cookies.test.js b/packages/integrations/netlify/test/functions/cookies.test.js index e9121b429377..bc1771512c58 100644 --- a/packages/integrations/netlify/test/functions/cookies.test.js +++ b/packages/integrations/netlify/test/functions/cookies.test.js @@ -11,9 +11,7 @@ describe('Cookies', () => { before(async () => { fixture = await loadFixture({ root: new URL('./fixtures/cookies/', import.meta.url).toString(), - experimental: { - ssr: true, - }, + output: 'server', adapter: netlifyAdapter({ dist: new URL('./fixtures/cookies/dist/', import.meta.url), }), diff --git a/packages/integrations/netlify/test/functions/dynamic-route.test.js b/packages/integrations/netlify/test/functions/dynamic-route.test.js index 8c8977f7efb2..0cfb5359b45a 100644 --- a/packages/integrations/netlify/test/functions/dynamic-route.test.js +++ b/packages/integrations/netlify/test/functions/dynamic-route.test.js @@ -9,9 +9,7 @@ describe('Dynamic pages', () => { before(async () => { fixture = await loadFixture({ root: new URL('./fixtures/dynamic-route/', import.meta.url).toString(), - experimental: { - ssr: true, - }, + output: 'server', adapter: netlifyAdapter({ dist: new URL('./fixtures/dynamic-route/dist/', import.meta.url), }), diff --git a/packages/integrations/node/README.md b/packages/integrations/node/README.md index 3772a23d8d08..2743525771b6 100644 --- a/packages/integrations/node/README.md +++ b/packages/integrations/node/README.md @@ -37,6 +37,7 @@ import node from '@astrojs/node'; export default defineConfig({ // ... + output: 'server', adapter: node() }) ``` diff --git a/packages/integrations/node/src/index.ts b/packages/integrations/node/src/index.ts index b90cd9d2e990..8ff6fc423787 100644 --- a/packages/integrations/node/src/index.ts +++ b/packages/integrations/node/src/index.ts @@ -12,8 +12,12 @@ export default function createIntegration(): AstroIntegration { return { name: '@astrojs/node', hooks: { - 'astro:config:done': ({ setAdapter }) => { + 'astro:config:done': ({ setAdapter, config }) => { setAdapter(getAdapter()); + + if(config.output === 'static') { + console.warn(`[@astrojs/Node] \`output: "server"\` is required to use this adapter.`); + } }, }, }; diff --git a/packages/integrations/node/test/api-route.test.js b/packages/integrations/node/test/api-route.test.js index a28b88e7f363..034b53c07f53 100644 --- a/packages/integrations/node/test/api-route.test.js +++ b/packages/integrations/node/test/api-route.test.js @@ -9,9 +9,7 @@ describe('API routes', () => { before(async () => { fixture = await loadFixture({ root: './fixtures/api-route/', - experimental: { - ssr: true, - }, + output: 'server', adapter: nodejs(), }); await fixture.build(); diff --git a/packages/integrations/sitemap/src/index.ts b/packages/integrations/sitemap/src/index.ts index a1c9650f2bd2..bab3d5af6fb3 100644 --- a/packages/integrations/sitemap/src/index.ts +++ b/packages/integrations/sitemap/src/index.ts @@ -98,9 +98,9 @@ const createPlugin = (options?: SitemapOptions): AstroIntegration => { if (pageUrls.length === 0) { // offer suggestion for SSR users - if (typeof config.adapter !== 'undefined') { + if (config.output !== 'static') { logger.warn( - `No pages found! We can only detect sitemap routes for "static" projects. Since you are using an SSR adapter, we recommend manually listing your sitemap routes using the "customPages" integration option.\n\nExample: \`sitemap({ customPages: ['https://example.com/route'] })\`` + `No pages found! We can only detect sitemap routes for "static" builds. Since you are using an SSR adapter, we recommend manually listing your sitemap routes using the "customPages" integration option.\n\nExample: \`sitemap({ customPages: ['https://example.com/route'] })\`` ); } else { logger.warn(`No pages found!\n\`${OUTFILE}\` not created.`); diff --git a/packages/integrations/vercel/README.md b/packages/integrations/vercel/README.md index aaab5eb40520..f7726805b2c6 100644 --- a/packages/integrations/vercel/README.md +++ b/packages/integrations/vercel/README.md @@ -25,7 +25,7 @@ First, install the `@astrojs/vercel` package using your package manager. If you' npm install @astrojs/vercel ``` -Then, install this adapter in your `astro.config.*` file using the `adapter` property (note the import from `@astrojs/vercel/serverless` - see [targets](#targets)). +Then, install this adapter in your `astro.config.*` file using the `deploy` property (note the import from `@astrojs/vercel/serverless` - see [targets](#targets)). __`astro.config.mjs`__ @@ -34,6 +34,7 @@ import { defineConfig } from 'astro/config'; import vercel from '@astrojs/vercel/serverless'; export default defineConfig({ + output: 'server', adapter: vercel() }); ``` diff --git a/packages/integrations/vercel/src/static/adapter.ts b/packages/integrations/vercel/src/static/adapter.ts index db6690f8cedf..abc9483f5f6b 100644 --- a/packages/integrations/vercel/src/static/adapter.ts +++ b/packages/integrations/vercel/src/static/adapter.ts @@ -23,9 +23,7 @@ export default function vercelStatic(): AstroIntegration { setAdapter(getAdapter()); _config = config; }, - 'astro:build:start': async ({ buildConfig }) => { - buildConfig.staticMode = true; - + 'astro:build:start': async () => { // Ensure to have `.vercel/output` empty. // This is because, when building to static, outDir = .vercel/output/static/, // so .vercel/output itself won't get cleaned. diff --git a/packages/webapi/mod.d.ts b/packages/webapi/mod.d.ts index 7150edbe716e..3750dcb3a4f7 100644 --- a/packages/webapi/mod.d.ts +++ b/packages/webapi/mod.d.ts @@ -1,5 +1,5 @@ export { pathToPosix } from './lib/utils'; -export { AbortController, AbortSignal, alert, atob, Blob, btoa, ByteLengthQueuingStrategy, cancelAnimationFrame, cancelIdleCallback, CanvasRenderingContext2D, CharacterData, clearTimeout, Comment, CountQueuingStrategy, CSSStyleSheet, CustomElementRegistry, CustomEvent, Document, DocumentFragment, DOMException, Element, Event, EventTarget, fetch, File, FormData, Headers, HTMLBodyElement, HTMLCanvasElement, HTMLDivElement, HTMLDocument, HTMLElement, HTMLHeadElement, HTMLHtmlElement, HTMLImageElement, HTMLSpanElement, HTMLStyleElement, HTMLTemplateElement, HTMLUnknownElement, Image, ImageData, IntersectionObserver, MediaQueryList, MutationObserver, Node, NodeFilter, NodeIterator, OffscreenCanvas, ReadableByteStreamController, ReadableStream, ReadableStreamBYOBReader, ReadableStreamBYOBRequest, ReadableStreamDefaultController, ReadableStreamDefaultReader, Request, requestAnimationFrame, requestIdleCallback, ResizeObserver, Response, setTimeout, ShadowRoot, structuredClone, StyleSheet, Text, TransformStream, TreeWalker, URLPattern, Window, WritableStream, WritableStreamDefaultController, WritableStreamDefaultWriter } from './mod.js'; +export { AbortController, AbortSignal, alert, atob, Blob, btoa, ByteLengthQueuingStrategy, cancelAnimationFrame, cancelIdleCallback, CanvasRenderingContext2D, CharacterData, clearTimeout, Comment, CountQueuingStrategy, CSSStyleSheet, CustomElementRegistry, CustomEvent, Document, DocumentFragment, DOMException, Element, Event, EventTarget, fetch, File, FormData, Headers, HTMLBodyElement, HTMLCanvasElement, HTMLDivElement, HTMLDocument, HTMLElement, HTMLHeadElement, HTMLHtmlElement, HTMLImageElement, HTMLSpanElement, HTMLStyleElement, HTMLTemplateElement, HTMLUnknownElement, Image, ImageData, IntersectionObserver, MediaQueryList, MutationObserver, Node, NodeFilter, NodeIterator, OffscreenCanvas, ReadableByteStreamController, ReadableStream, ReadableStreamBYOBReader, ReadableStreamBYOBRequest, ReadableStreamDefaultController, ReadableStreamDefaultReader, Request, requestAnimationFrame, requestIdleCallback, ResizeObserver, Response, setTimeout, ShadowRoot, structuredClone, StyleSheet, Text, TransformStream, TreeWalker, URLPattern, Window, WritableStream, WritableStreamDefaultController, WritableStreamDefaultWriter, } from './mod.js'; export declare const polyfill: { (target: any, options?: PolyfillOptions): any; internals(target: any, name: string): any;