aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Erika <3019731+Princesseuh@users.noreply.github.com> 2024-09-07 01:07:09 +0200
committerGravatar GitHub <noreply@github.com> 2024-09-07 01:07:09 +0200
commit50ca656dbad6dbb3955b83d9f4dcf4795e98823a (patch)
tree928eb296b12e5f62025afd922f99a2aec0b1a1b2
parent5b4e3abbb152146b71c1af05d33c96211000b2a6 (diff)
downloadastro-50ca656dbad6dbb3955b83d9f4dcf4795e98823a.tar.gz
astro-50ca656dbad6dbb3955b83d9f4dcf4795e98823a.tar.zst
astro-50ca656dbad6dbb3955b83d9f4dcf4795e98823a.zip
Merge output: hybrid and output: static (#11824)
* feat: merge hybrid and static * fix: linting * fix: get a bunch of tests passing * fix: make forceServerOutput optional * fix: more tests passing * fix: http2 test * fix: CCC * fix: get unit tests passing * fix: lint * fix: vercel * fix: build * fix: build * fix: db tests * fix: get all normal tests passing * fix: e2e tests * refactor: cleanup code * fix: more tests * fix: windows * fix: apply feedback * perf: do in parallel * fix: tests * fix: tests, for real * fix: make server islands tests server-rendered * fix: apply feedback * nit: remove unnecessary file * fix: test remove test that abuse prerender logic * fix: ensure image endpoint is there on dev reload
-rw-r--r--.vscode/settings.json6
-rw-r--r--packages/astro/e2e/custom-client-directives.test.js8
-rw-r--r--packages/astro/e2e/fixtures/actions-blog/astro.config.mjs2
-rw-r--r--packages/astro/e2e/fixtures/actions-react-19/astro.config.mjs2
-rw-r--r--packages/astro/e2e/fixtures/server-islands/astro.config.mjs2
-rw-r--r--packages/astro/e2e/fixtures/server-islands/src/pages/index.astro2
-rw-r--r--packages/astro/e2e/fixtures/view-transitions/astro.config.mjs2
-rw-r--r--packages/astro/package.json3
-rw-r--r--packages/astro/src/actions/integration.ts4
-rw-r--r--packages/astro/src/assets/build/generate.ts8
-rw-r--r--packages/astro/src/assets/endpoint/config.ts48
-rw-r--r--packages/astro/src/assets/vite-plugin-assets.ts5
-rw-r--r--packages/astro/src/config/index.ts4
-rw-r--r--packages/astro/src/content/vite-plugin-content-imports.ts5
-rw-r--r--packages/astro/src/content/vite-plugin-content-virtual-mod.ts3
-rw-r--r--packages/astro/src/core/build/common.ts15
-rw-r--r--packages/astro/src/core/build/generate.ts10
-rw-r--r--packages/astro/src/core/build/index.ts18
-rw-r--r--packages/astro/src/core/build/page-data.ts2
-rw-r--r--packages/astro/src/core/build/pipeline.ts9
-rw-r--r--packages/astro/src/core/build/plugins/plugin-content.ts4
-rw-r--r--packages/astro/src/core/build/plugins/plugin-manifest.ts2
-rw-r--r--packages/astro/src/core/build/plugins/plugin-pages.ts2
-rw-r--r--packages/astro/src/core/build/plugins/plugin-prerender.ts2
-rw-r--r--packages/astro/src/core/build/plugins/plugin-ssr.ts4
-rw-r--r--packages/astro/src/core/build/static-build.ts35
-rw-r--r--packages/astro/src/core/config/schema.ts2
-rw-r--r--packages/astro/src/core/config/settings.ts1
-rw-r--r--packages/astro/src/core/create-vite.ts14
-rw-r--r--packages/astro/src/core/dev/container.ts25
-rw-r--r--packages/astro/src/core/errors/errors-data.ts6
-rw-r--r--packages/astro/src/core/middleware/vite-plugin.ts2
-rw-r--r--packages/astro/src/core/preview/index.ts9
-rw-r--r--packages/astro/src/core/render-context.ts7
-rw-r--r--packages/astro/src/core/request.ts7
-rw-r--r--packages/astro/src/core/routing/dev-default.ts14
-rw-r--r--packages/astro/src/core/routing/manifest/create.ts69
-rw-r--r--packages/astro/src/core/sync/index.ts14
-rw-r--r--packages/astro/src/core/util.ts14
-rw-r--r--packages/astro/src/integrations/features-validation.ts17
-rw-r--r--packages/astro/src/integrations/hooks.ts33
-rw-r--r--packages/astro/src/prerender/utils.ts11
-rw-r--r--packages/astro/src/runtime/server/endpoint.ts6
-rw-r--r--packages/astro/src/types/astro.ts5
-rw-r--r--packages/astro/src/types/public/config.ts15
-rw-r--r--packages/astro/src/types/public/integrations.ts5
-rw-r--r--packages/astro/src/vite-plugin-astro-server/pipeline.ts6
-rw-r--r--packages/astro/src/vite-plugin-astro-server/plugin.ts30
-rw-r--r--packages/astro/src/vite-plugin-astro-server/route.ts2
-rw-r--r--packages/astro/src/vite-plugin-env/index.ts29
-rw-r--r--packages/astro/src/vite-plugin-scanner/index.ts55
-rw-r--r--packages/astro/src/vite-plugin-scanner/scan.ts92
-rw-r--r--packages/astro/test/astro-global.test.js5
-rw-r--r--packages/astro/test/build-assets.test.js8
-rw-r--r--packages/astro/test/fixtures/astro-dev-http2/astro.config.ts2
-rw-r--r--packages/astro/test/fixtures/astro-global/src/pages/about.astro4
-rw-r--r--packages/astro/test/fixtures/server-islands/hybrid/astro.config.mjs3
-rw-r--r--packages/astro/test/fixtures/streaming/src/pages/index.astro2
-rw-r--r--packages/astro/test/fixtures/streaming/src/pages/multiple-errors.astro2
-rw-r--r--packages/astro/test/fixtures/streaming/src/pages/slot.astro2
-rw-r--r--packages/astro/test/i18n-routing.test.js2
-rw-r--r--packages/astro/test/ssr-prerender-get-static-paths.test.js4
-rw-r--r--packages/astro/test/test-adapter.js3
-rw-r--r--packages/astro/test/underscore-in-folder-name.test.js2
-rw-r--r--packages/astro/test/units/integrations/api.test.js38
-rw-r--r--packages/astro/test/units/routing/manifest.test.js39
-rw-r--r--packages/astro/test/units/routing/route-matching.test.js4
-rw-r--r--packages/astro/test/units/routing/route-sanitization.test.js2
-rw-r--r--packages/astro/test/units/vite-plugin-astro-server/request.test.js2
-rw-r--r--packages/astro/test/units/vite-plugin-scanner/scan.test.js137
-rw-r--r--packages/db/src/core/integration/index.ts15
-rw-r--r--packages/db/test/local-prod.test.js2
-rw-r--r--packages/integrations/web-vitals/src/index.ts8
-rw-r--r--packages/integrations/web-vitals/test/fixtures/basics/astro.config.mjs2
-rw-r--r--pnpm-lock.yaml2
75 files changed, 437 insertions, 550 deletions
diff --git a/.vscode/settings.json b/.vscode/settings.json
index b3e10e7bc..97aeabec0 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -15,7 +15,7 @@
"editor.defaultFormatter": "biomejs.biome"
},
"editor.codeActionsOnSave": {
- "quickFix.biome": true,
- "source.fixAll.biome": true
- }
+ "quickFix.biome": "explicit",
+ "source.fixAll.biome": "explicit"
+ }
}
diff --git a/packages/astro/e2e/custom-client-directives.test.js b/packages/astro/e2e/custom-client-directives.test.js
index 8f90916f2..e0787109e 100644
--- a/packages/astro/e2e/custom-client-directives.test.js
+++ b/packages/astro/e2e/custom-client-directives.test.js
@@ -40,7 +40,13 @@ test.describe('Custom Client Directives - build server', () => {
test.beforeAll(async ({ astro }) => {
await astro.build({
- adapter: testAdapter(),
+ adapter: testAdapter({
+ extendAdapter: {
+ adapterFeatures: {
+ forceServerOutput: false,
+ },
+ },
+ }),
});
previewServer = await astro.preview();
});
diff --git a/packages/astro/e2e/fixtures/actions-blog/astro.config.mjs b/packages/astro/e2e/fixtures/actions-blog/astro.config.mjs
index 8f9f0e353..35f7481f2 100644
--- a/packages/astro/e2e/fixtures/actions-blog/astro.config.mjs
+++ b/packages/astro/e2e/fixtures/actions-blog/astro.config.mjs
@@ -7,7 +7,7 @@ import node from '@astrojs/node';
export default defineConfig({
site: 'https://example.com',
integrations: [db(), react()],
- output: 'hybrid',
+ output: 'static',
adapter: node({
mode: 'standalone',
}),
diff --git a/packages/astro/e2e/fixtures/actions-react-19/astro.config.mjs b/packages/astro/e2e/fixtures/actions-react-19/astro.config.mjs
index 8f9f0e353..35f7481f2 100644
--- a/packages/astro/e2e/fixtures/actions-react-19/astro.config.mjs
+++ b/packages/astro/e2e/fixtures/actions-react-19/astro.config.mjs
@@ -7,7 +7,7 @@ import node from '@astrojs/node';
export default defineConfig({
site: 'https://example.com',
integrations: [db(), react()],
- output: 'hybrid',
+ output: 'static',
adapter: node({
mode: 'standalone',
}),
diff --git a/packages/astro/e2e/fixtures/server-islands/astro.config.mjs b/packages/astro/e2e/fixtures/server-islands/astro.config.mjs
index 2175a1bf8..6b3c2a146 100644
--- a/packages/astro/e2e/fixtures/server-islands/astro.config.mjs
+++ b/packages/astro/e2e/fixtures/server-islands/astro.config.mjs
@@ -6,7 +6,7 @@ import nodejs from '@astrojs/node';
// https://astro.build/config
export default defineConfig({
base: '/base',
- output: 'hybrid',
+ output: 'static',
adapter: nodejs({ mode: 'standalone' }),
integrations: [react(), mdx()],
trailingSlash: process.env.TRAILING_SLASH ?? 'always',
diff --git a/packages/astro/e2e/fixtures/server-islands/src/pages/index.astro b/packages/astro/e2e/fixtures/server-islands/src/pages/index.astro
index 70a4fcabc..3caf452bc 100644
--- a/packages/astro/e2e/fixtures/server-islands/src/pages/index.astro
+++ b/packages/astro/e2e/fixtures/server-islands/src/pages/index.astro
@@ -5,6 +5,8 @@ import HTMLError from '../components/HTMLError.astro';
import { generateLongText } from '../lorem';
const content = generateLongText(5);
+
+export const prerender = false;
---
<html>
diff --git a/packages/astro/e2e/fixtures/view-transitions/astro.config.mjs b/packages/astro/e2e/fixtures/view-transitions/astro.config.mjs
index b7cfd434a..4fdc70ff8 100644
--- a/packages/astro/e2e/fixtures/view-transitions/astro.config.mjs
+++ b/packages/astro/e2e/fixtures/view-transitions/astro.config.mjs
@@ -6,7 +6,7 @@ import { defineConfig } from 'astro/config';
// https://astro.build/config
export default defineConfig({
- output: 'hybrid',
+ output: 'static',
adapter: nodejs({ mode: 'standalone' }),
integrations: [react(),vue(),svelte()],
redirects: {
diff --git a/packages/astro/package.json b/packages/astro/package.json
index 5ffba7fc6..2b425894e 100644
--- a/packages/astro/package.json
+++ b/packages/astro/package.json
@@ -118,7 +118,8 @@
"test:e2e:chrome": "playwright test",
"test:e2e:firefox": "playwright test --config playwright.firefox.config.js",
"test:types": "tsc --project tsconfig.tests.json",
- "test:node": "astro-scripts test \"test/**/*.test.js\""
+ "test:node": "astro-scripts test \"test/**/*.test.js\"",
+ "test:units": "astro-scripts test \"test/**/units/**/*.test.js\""
},
"dependencies": {
"@astrojs/compiler": "^2.10.3",
diff --git a/packages/astro/src/actions/integration.ts b/packages/astro/src/actions/integration.ts
index 4c2cc6e04..830420836 100644
--- a/packages/astro/src/actions/integration.ts
+++ b/packages/astro/src/actions/integration.ts
@@ -1,6 +1,6 @@
import { ActionsWithoutServerOutputError } from '../core/errors/errors-data.js';
import { AstroError } from '../core/errors/errors.js';
-import { isServerLikeOutput, viteID } from '../core/util.js';
+import { viteID } from '../core/util.js';
import type { AstroSettings } from '../types/astro.js';
import type { AstroIntegration } from '../types/public/integrations.js';
import { ACTIONS_TYPES_FILE, VIRTUAL_MODULE_ID } from './consts.js';
@@ -30,7 +30,7 @@ export default function astroIntegrationActionsRouteHandler({
});
},
'astro:config:done': async (params) => {
- if (!isServerLikeOutput(params.config)) {
+ if (params.buildOutput === 'static') {
const error = new AstroError(ActionsWithoutServerOutputError);
error.stack = undefined;
throw error;
diff --git a/packages/astro/src/assets/build/generate.ts b/packages/astro/src/assets/build/generate.ts
index 1c4866592..c38ebb75a 100644
--- a/packages/astro/src/assets/build/generate.ts
+++ b/packages/astro/src/assets/build/generate.ts
@@ -9,7 +9,6 @@ import { AstroError } from '../../core/errors/errors.js';
import { AstroErrorData } from '../../core/errors/index.js';
import type { Logger } from '../../core/logger/core.js';
import { isRemotePath, removeLeadingForwardSlash } from '../../core/path.js';
-import { isServerLikeOutput } from '../../core/util.js';
import type { MapValue } from '../../type-utils.js';
import type { AstroConfig } from '../../types/public/config.js';
import { getConfiguredImageService } from '../internal.js';
@@ -50,7 +49,7 @@ export async function prepareAssetsGenerationEnv(
pipeline: BuildPipeline,
totalCount: number,
): Promise<AssetEnv> {
- const { config, logger } = pipeline;
+ const { config, logger, settings } = pipeline;
let useCache = true;
const assetsCacheDir = new URL('assets/', config.cacheDir);
const count = { total: totalCount, current: 1 };
@@ -66,8 +65,9 @@ export async function prepareAssetsGenerationEnv(
useCache = false;
}
+ const isServerOutput = settings.buildOutput === 'server';
let serverRoot: URL, clientRoot: URL;
- if (isServerLikeOutput(config)) {
+ if (isServerOutput) {
serverRoot = config.build.server;
clientRoot = config.build.client;
} else {
@@ -77,7 +77,7 @@ export async function prepareAssetsGenerationEnv(
return {
logger,
- isSSR: isServerLikeOutput(config),
+ isSSR: isServerOutput,
count,
useCache,
assetsCacheDir,
diff --git a/packages/astro/src/assets/endpoint/config.ts b/packages/astro/src/assets/endpoint/config.ts
index ff9dcc79a..382645d6e 100644
--- a/packages/astro/src/assets/endpoint/config.ts
+++ b/packages/astro/src/assets/endpoint/config.ts
@@ -1,15 +1,47 @@
-import type { AstroSettings } from '../../types/astro.js';
+import { resolveInjectedRoute } from '../../core/routing/manifest/create.js';
+import type { AstroSettings, ManifestData } from '../../types/astro.js';
+import type { RouteData } from '../../types/public/internal.js';
-export function injectImageEndpoint(settings: AstroSettings, mode: 'dev' | 'build') {
+export function injectImageEndpoint(
+ settings: AstroSettings,
+ manifest: ManifestData,
+ mode: 'dev' | 'build',
+ cwd?: string,
+) {
+ manifest.routes.push(getImageEndpointData(settings, mode, cwd));
+}
+
+export function ensureImageEndpointRoute(
+ settings: AstroSettings,
+ manifest: ManifestData,
+ mode: 'dev' | 'build',
+ cwd?: string,
+) {
+ if (!manifest.routes.some((route) => route.route === '/_image')) {
+ manifest.routes.push(getImageEndpointData(settings, mode, cwd));
+ }
+}
+
+function getImageEndpointData(
+ settings: AstroSettings,
+ mode: 'dev' | 'build',
+ cwd?: string,
+): RouteData {
const endpointEntrypoint =
settings.config.image.endpoint ??
(mode === 'dev' ? 'astro/assets/endpoint/node' : 'astro/assets/endpoint/generic');
- settings.injectedRoutes.push({
- pattern: '/_image',
- entrypoint: endpointEntrypoint,
+ return {
+ type: 'endpoint',
+ isIndex: false,
+ route: '/_image',
+ pattern: /^\/_image$/,
+ segments: [[{ content: '_image', dynamic: false, spread: false }]],
+ params: [],
+ component: resolveInjectedRoute(endpointEntrypoint, settings.config.root, cwd).component,
+ generate: () => '',
+ pathname: '/_image',
prerender: false,
- });
-
- return settings;
+ fallbackRoutes: [],
+ };
}
diff --git a/packages/astro/src/assets/vite-plugin-assets.ts b/packages/astro/src/assets/vite-plugin-assets.ts
index f7c541eb6..5c40d98b4 100644
--- a/packages/astro/src/assets/vite-plugin-assets.ts
+++ b/packages/astro/src/assets/vite-plugin-assets.ts
@@ -10,7 +10,6 @@ import {
removeBase,
removeQueryString,
} from '../core/path.js';
-import { isServerLikeOutput } from '../core/util.js';
import type { AstroPluginOptions, AstroSettings } from '../types/astro.js';
import { VALID_INPUT_FORMATS, VIRTUAL_MODULE_ID, VIRTUAL_SERVICE_ID } from './consts.js';
import type { ImageTransform } from './types.js';
@@ -131,7 +130,7 @@ export default function assets({
// so that it's tree-shaken away for all platforms that don't need it.
export const outDir = /* #__PURE__ */ new URL(${JSON.stringify(
new URL(
- isServerLikeOutput(settings.config)
+ settings.buildOutput === 'server'
? settings.config.build.client
: settings.config.outDir,
),
@@ -222,7 +221,7 @@ export default function assets({
if (options?.ssr) {
return `export default ${getProxyCode(
imageMetadata,
- isServerLikeOutput(settings.config),
+ settings.buildOutput === 'server',
)}`;
} else {
globalThis.astroAsset.referencedImages.add(imageMetadata.fsPath);
diff --git a/packages/astro/src/config/index.ts b/packages/astro/src/config/index.ts
index b31105ec7..82f5d77c8 100644
--- a/packages/astro/src/config/index.ts
+++ b/packages/astro/src/config/index.ts
@@ -1,5 +1,6 @@
import type { UserConfig as ViteUserConfig } from 'vite';
import { Logger } from '../core/logger/core.js';
+import { createRouteManifest } from '../core/routing/index.js';
import type { AstroInlineConfig, AstroUserConfig } from '../types/public/config.js';
export function defineConfig(config: AstroUserConfig) {
@@ -40,6 +41,7 @@ export function getViteConfig(
const { astroConfig: config } = await resolveConfig(inlineAstroConfig, cmd);
let settings = await createSettings(config, userViteConfig.root);
settings = await runHookConfigSetup({ settings, command: cmd, logger });
+ const manifest = await createRouteManifest({ settings }, logger);
const viteConfig = await createVite(
{
mode,
@@ -48,7 +50,7 @@ export function getViteConfig(
astroContentListenPlugin({ settings, logger, fs }),
],
},
- { settings, logger, mode, sync: false },
+ { settings, logger, mode, sync: false, manifest },
);
await runHookConfigDone({ settings, logger });
return mergeConfig(viteConfig, userViteConfig);
diff --git a/packages/astro/src/content/vite-plugin-content-imports.ts b/packages/astro/src/content/vite-plugin-content-imports.ts
index 62f129052..4950d2d9b 100644
--- a/packages/astro/src/content/vite-plugin-content-imports.ts
+++ b/packages/astro/src/content/vite-plugin-content-imports.ts
@@ -8,7 +8,6 @@ import { getProxyCode } from '../assets/utils/proxy.js';
import { AstroError } from '../core/errors/errors.js';
import { AstroErrorData } from '../core/errors/index.js';
import type { Logger } from '../core/logger/core.js';
-import { isServerLikeOutput } from '../core/util.js';
import type { AstroSettings } from '../types/astro.js';
import type { AstroConfig } from '../types/public/config.js';
import type {
@@ -115,7 +114,7 @@ export function astroContentImportPlugin({
const code = `
export const id = ${JSON.stringify(id)};
export const collection = ${JSON.stringify(collection)};
-export const data = ${stringifyEntryData(data, isServerLikeOutput(settings.config))};
+export const data = ${stringifyEntryData(data, settings.buildOutput === 'server')};
export const _internal = {
type: 'data',
filePath: ${JSON.stringify(_internal.filePath)},
@@ -140,7 +139,7 @@ export const _internal = {
export const collection = ${JSON.stringify(collection)};
export const slug = ${JSON.stringify(slug)};
export const body = ${JSON.stringify(body)};
- export const data = ${stringifyEntryData(data, isServerLikeOutput(settings.config))};
+ export const data = ${stringifyEntryData(data, settings.buildOutput === 'server')};
export const _internal = {
type: 'content',
filePath: ${JSON.stringify(_internal.filePath)},
diff --git a/packages/astro/src/content/vite-plugin-content-virtual-mod.ts b/packages/astro/src/content/vite-plugin-content-virtual-mod.ts
index 5174a3f91..ca09d1c1e 100644
--- a/packages/astro/src/content/vite-plugin-content-virtual-mod.ts
+++ b/packages/astro/src/content/vite-plugin-content-virtual-mod.ts
@@ -8,7 +8,6 @@ import type { Plugin } from 'vite';
import { encodeName } from '../core/build/util.js';
import { AstroError, AstroErrorData } from '../core/errors/index.js';
import { appendForwardSlash, removeFileExtension } from '../core/path.js';
-import { isServerLikeOutput } from '../core/util.js';
import { rootRelativePath } from '../core/viteUtils.js';
import type { AstroSettings } from '../types/astro.js';
import type { AstroPluginMetadata } from '../vite-plugin-astro/index.js';
@@ -53,7 +52,7 @@ export function astroContentVirtualModPlugin({
fs,
}: AstroContentVirtualModPluginParams): Plugin {
let IS_DEV = false;
- const IS_SERVER = isServerLikeOutput(settings.config);
+ const IS_SERVER = settings.buildOutput === 'server';
let dataStoreFile: URL;
return {
name: 'astro-content-virtual-mod-plugin',
diff --git a/packages/astro/src/core/build/common.ts b/packages/astro/src/core/build/common.ts
index f9ed45836..4ee826f8b 100644
--- a/packages/astro/src/core/build/common.ts
+++ b/packages/astro/src/core/build/common.ts
@@ -1,26 +1,27 @@
import npath from 'node:path';
import { fileURLToPath, pathToFileURL } from 'node:url';
import { appendForwardSlash } from '../../core/path.js';
+import type { AstroSettings } from '../../types/astro.js';
import type { AstroConfig } from '../../types/public/config.js';
import type { RouteData } from '../../types/public/internal.js';
const STATUS_CODE_PAGES = new Set(['/404', '/500']);
const FALLBACK_OUT_DIR_NAME = './.astro/';
-function getOutRoot(astroConfig: AstroConfig): URL {
- if (astroConfig.output === 'static') {
- return new URL('./', astroConfig.outDir);
+function getOutRoot(astroSettings: AstroSettings): URL {
+ if (astroSettings.buildOutput === 'static') {
+ return new URL('./', astroSettings.config.outDir);
} else {
- return new URL('./', astroConfig.build.client);
+ return new URL('./', astroSettings.config.build.client);
}
}
export function getOutFolder(
- astroConfig: AstroConfig,
+ astroSettings: AstroSettings,
pathname: string,
routeData: RouteData,
): URL {
- const outRoot = getOutRoot(astroConfig);
+ const outRoot = getOutRoot(astroSettings);
const routeType = routeData.type;
// This is the root folder to write to.
@@ -30,7 +31,7 @@ export function getOutFolder(
case 'fallback':
case 'page':
case 'redirect':
- switch (astroConfig.build.format) {
+ switch (astroSettings.config.build.format) {
case 'directory': {
if (STATUS_CODE_PAGES.has(pathname)) {
return new URL('.' + appendForwardSlash(npath.dirname(pathname)), outRoot);
diff --git a/packages/astro/src/core/build/generate.ts b/packages/astro/src/core/build/generate.ts
index 0a33d554c..fc96184c0 100644
--- a/packages/astro/src/core/build/generate.ts
+++ b/packages/astro/src/core/build/generate.ts
@@ -35,7 +35,7 @@ import { callGetStaticPaths } from '../render/route-cache.js';
import { createRequest } from '../request.js';
import { matchRoute } from '../routing/match.js';
import { stringifyParams } from '../routing/params.js';
-import { getOutputFilename, isServerLikeOutput } from '../util.js';
+import { getOutputFilename } from '../util.js';
import { getOutFile, getOutFolder } from './common.js';
import { cssOrder, mergeInlineCss } from './internal.js';
import { BuildPipeline } from './pipeline.js';
@@ -49,12 +49,12 @@ import { getTimeStat, shouldAppendForwardSlash } from './util.js';
export async function generatePages(options: StaticBuildOptions, internals: BuildInternals) {
const generatePagesTimer = performance.now();
- const ssr = isServerLikeOutput(options.settings.config);
+ const ssr = options.settings.buildOutput === 'server';
let manifest: SSRManifest;
if (ssr) {
manifest = await BuildPipeline.retrieveManifest(options, internals);
} else {
- const baseDirectory = getOutputDirectory(options.settings.config);
+ const baseDirectory = getOutputDirectory(options.settings);
const renderersEntryUrl = new URL('renderers.mjs', baseDirectory);
const renderers = await import(renderersEntryUrl.toString());
let middleware: MiddlewareHandler = (_, next) => next();
@@ -138,7 +138,7 @@ export async function generatePages(options: StaticBuildOptions, internals: Buil
delete globalThis?.astroAsset?.addStaticImage;
}
- await runHookBuildGenerated({ config, logger });
+ await runHookBuildGenerated({ settings: options.settings, logger });
}
const THRESHOLD_SLOW_RENDER_TIME_MS = 500;
@@ -466,7 +466,7 @@ async function generatePath(
body = Buffer.from(await response.arrayBuffer());
}
- const outFolder = getOutFolder(config, pathname, route);
+ const outFolder = getOutFolder(pipeline.settings, pathname, route);
const outFile = getOutFile(config, outFolder, pathname, route);
route.distURL = outFile;
diff --git a/packages/astro/src/core/build/index.ts b/packages/astro/src/core/build/index.ts
index 10e6b2dbc..2cc37f5fd 100644
--- a/packages/astro/src/core/build/index.ts
+++ b/packages/astro/src/core/build/index.ts
@@ -19,13 +19,14 @@ import { createNodeLogger } from '../config/logging.js';
import { createSettings } from '../config/settings.js';
import { createVite } from '../create-vite.js';
import { createKey } from '../encryption.js';
+import { AstroError, AstroErrorData } from '../errors/index.js';
import type { Logger } from '../logger/core.js';
import { levels, timerMessage } from '../logger/core.js';
import { apply as applyPolyfill } from '../polyfill.js';
import { createRouteManifest } from '../routing/index.js';
import { getServerIslandRouteData } from '../server-islands/endpoint.js';
import { clearContentLayerCache } from '../sync/index.js';
-import { ensureProcessNodeEnv, isServerLikeOutput } from '../util.js';
+import { ensureProcessNodeEnv } from '../util.js';
import { collectPagesData } from './page-data.js';
import { staticBuild, viteBuild } from './static-build.js';
import type { StaticBuildOptions } from './types.js';
@@ -118,11 +119,16 @@ class AstroBuilder {
logger: logger,
});
- if (isServerLikeOutput(this.settings.config)) {
- this.settings = injectImageEndpoint(this.settings, 'build');
+ this.manifest = await createRouteManifest({ settings: this.settings }, this.logger);
+
+ if (this.settings.buildOutput === 'server') {
+ injectImageEndpoint(this.settings, this.manifest, 'build');
}
- this.manifest = createRouteManifest({ settings: this.settings }, this.logger);
+ // If we're building for the server, we need to ensure that an adapter is installed.
+ if (!this.settings.config.adapter && this.settings.buildOutput === 'server') {
+ throw new AstroError(AstroErrorData.NoAdapterInstalled);
+ }
const viteConfig = await createVite(
{
@@ -138,6 +144,7 @@ class AstroBuilder {
mode: 'build',
command: 'build',
sync: false,
+ manifest: this.manifest,
},
);
await runHookConfigDone({ settings: this.settings, logger: logger });
@@ -147,6 +154,7 @@ class AstroBuilder {
settings: this.settings,
logger,
fs,
+ manifest: this.manifest,
});
return { viteConfig };
@@ -212,7 +220,7 @@ class AstroBuilder {
// You're done! Time to clean up.
await runHookBuildDone({
- config: this.settings.config,
+ settings: this.settings,
pages: pageNames,
routes: Object.values(allPages)
.flat()
diff --git a/packages/astro/src/core/build/page-data.ts b/packages/astro/src/core/build/page-data.ts
index fa1ff2a9e..c36fe0d57 100644
--- a/packages/astro/src/core/build/page-data.ts
+++ b/packages/astro/src/core/build/page-data.ts
@@ -41,7 +41,7 @@ export function collectPagesData(opts: CollectPagesDataOptions): CollectPagesDat
styles: [],
};
- if (settings.config.output === 'static') {
+ if (settings.buildOutput === 'static') {
const html = `${route.pathname}`.replace(/\/?$/, '/index.html');
debug(
'build',
diff --git a/packages/astro/src/core/build/pipeline.ts b/packages/astro/src/core/build/pipeline.ts
index 55cc0d456..f6aa47e11 100644
--- a/packages/astro/src/core/build/pipeline.ts
+++ b/packages/astro/src/core/build/pipeline.ts
@@ -16,7 +16,6 @@ import { Pipeline } from '../render/index.js';
import { createAssetLink, createStylesheetElementSet } from '../render/ssr-element.js';
import { createDefaultRoutes } from '../routing/default.js';
import { findRouteToRewrite } from '../routing/rewrite.js';
-import { isServerLikeOutput } from '../util.js';
import { getOutDirWithinCwd } from './common.js';
import { type BuildInternals, cssOrder, getPageData, mergeInlineCss } from './internal.js';
import { ASTRO_PAGE_MODULE_ID, ASTRO_PAGE_RESOLVED_MODULE_ID } from './plugins/plugin-pages.js';
@@ -39,8 +38,7 @@ export class BuildPipeline extends Pipeline {
#routesByFilePath: WeakMap<RouteData, string> = new WeakMap<RouteData, string>();
get outFolder() {
- const ssr = isServerLikeOutput(this.settings.config);
- return ssr
+ return this.settings.buildOutput === 'server'
? this.settings.config.build.server
: getOutDirWithinCwd(this.settings.config.outDir);
}
@@ -74,7 +72,7 @@ export class BuildPipeline extends Pipeline {
return assetLink;
}
- const serverLike = isServerLikeOutput(config);
+ const serverLike = settings.buildOutput === 'server';
// We can skip streaming in SSG for performance as writing as strings are faster
const streaming = serverLike;
super(
@@ -113,8 +111,7 @@ export class BuildPipeline extends Pipeline {
staticBuildOptions: StaticBuildOptions,
internals: BuildInternals,
): Promise<SSRManifest> {
- const config = staticBuildOptions.settings.config;
- const baseDirectory = getOutputDirectory(config);
+ const baseDirectory = getOutputDirectory(staticBuildOptions.settings);
const manifestEntryUrl = new URL(
`${internals.manifestFileName}?time=${Date.now()}`,
baseDirectory,
diff --git a/packages/astro/src/core/build/plugins/plugin-content.ts b/packages/astro/src/core/build/plugins/plugin-content.ts
index 3482657fb..260489fd1 100644
--- a/packages/astro/src/core/build/plugins/plugin-content.ts
+++ b/packages/astro/src/core/build/plugins/plugin-content.ts
@@ -496,7 +496,7 @@ export function pluginContent(
targets: ['server'],
hooks: {
async 'build:before'() {
- if (!isContentCollectionsCacheEnabled(opts.settings.config)) {
+ if (!isContentCollectionsCacheEnabled(opts.settings)) {
return { vitePlugin: undefined };
}
const lookupMap = await generateLookupMap({ settings: opts.settings, fs: fsMod });
@@ -506,7 +506,7 @@ export function pluginContent(
},
async 'build:post'() {
- if (!isContentCollectionsCacheEnabled(opts.settings.config)) {
+ if (!isContentCollectionsCacheEnabled(opts.settings)) {
return;
}
// Cache build output of chunks and assets
diff --git a/packages/astro/src/core/build/plugins/plugin-manifest.ts b/packages/astro/src/core/build/plugins/plugin-manifest.ts
index 6259d1526..79bd3b44d 100644
--- a/packages/astro/src/core/build/plugins/plugin-manifest.ts
+++ b/packages/astro/src/core/build/plugins/plugin-manifest.ts
@@ -176,7 +176,7 @@ function buildManifest(
if (!route.prerender) continue;
if (!route.pathname) continue;
- const outFolder = getOutFolder(opts.settings.config, route.pathname, route);
+ const outFolder = getOutFolder(opts.settings, route.pathname, route);
const outFile = getOutFile(opts.settings.config, outFolder, route.pathname, route);
const file = outFile.toString().replace(opts.settings.config.build.client.toString(), '');
routes.push({
diff --git a/packages/astro/src/core/build/plugins/plugin-pages.ts b/packages/astro/src/core/build/plugins/plugin-pages.ts
index 6996e3342..75cbde220 100644
--- a/packages/astro/src/core/build/plugins/plugin-pages.ts
+++ b/packages/astro/src/core/build/plugins/plugin-pages.ts
@@ -14,7 +14,7 @@ function vitePluginPages(opts: StaticBuildOptions, internals: BuildInternals): V
return {
name: '@astro/plugin-build-pages',
options(options) {
- if (opts.settings.config.output === 'static') {
+ if (opts.settings.buildOutput === 'static') {
const inputs = new Set<string>();
for (const pageData of Object.values(opts.allPages)) {
diff --git a/packages/astro/src/core/build/plugins/plugin-prerender.ts b/packages/astro/src/core/build/plugins/plugin-prerender.ts
index c7552db9b..f915c9270 100644
--- a/packages/astro/src/core/build/plugins/plugin-prerender.ts
+++ b/packages/astro/src/core/build/plugins/plugin-prerender.ts
@@ -90,7 +90,7 @@ export function pluginPrerender(
internals: BuildInternals,
): AstroBuildPlugin {
// Static output can skip prerender completely because we're already rendering all pages
- if (opts.settings.config.output === 'static') {
+ if (opts.settings.buildOutput === 'static') {
return { targets: ['server'] };
}
diff --git a/packages/astro/src/core/build/plugins/plugin-ssr.ts b/packages/astro/src/core/build/plugins/plugin-ssr.ts
index 992dc6561..3ddd1d127 100644
--- a/packages/astro/src/core/build/plugins/plugin-ssr.ts
+++ b/packages/astro/src/core/build/plugins/plugin-ssr.ts
@@ -3,7 +3,6 @@ import type { AstroSettings } from '../../../types/astro.js';
import type { AstroAdapter } from '../../../types/public/integrations.js';
import { routeIsRedirect } from '../../redirects/index.js';
import { VIRTUAL_ISLAND_MAP_ID } from '../../server-islands/vite-plugin-server-islands.js';
-import { isServerLikeOutput } from '../../util.js';
import { addRollupInput } from '../add-rollup-input.js';
import type { BuildInternals } from '../internal.js';
import type { AstroBuildPlugin } from '../plugin.js';
@@ -131,11 +130,12 @@ export function pluginSSR(
options: StaticBuildOptions,
internals: BuildInternals,
): AstroBuildPlugin {
- const ssr = isServerLikeOutput(options.settings.config);
+ const ssr = options.settings.buildOutput === 'server';
return {
targets: ['server'],
hooks: {
'build:before': () => {
+ // We check before this point if there's an adapter, so we can safely assume it exists here.
const adapter = options.settings.adapter!;
const ssrPlugin = ssr && vitePluginSSR(internals, adapter, options);
const vitePlugin = [vitePluginAdapter(adapter)];
diff --git a/packages/astro/src/core/build/static-build.ts b/packages/astro/src/core/build/static-build.ts
index 725f919ba..3fffe9626 100644
--- a/packages/astro/src/core/build/static-build.ts
+++ b/packages/astro/src/core/build/static-build.ts
@@ -18,12 +18,10 @@ import {
} from '../../core/build/internal.js';
import { emptyDir, removeEmptyDirs } from '../../core/fs/index.js';
import { appendForwardSlash, prependForwardSlash, removeFileExtension } from '../../core/path.js';
-import { isModeServerWithNoAdapter, isServerLikeOutput } from '../../core/util.js';
import { runHookBuildSetup } from '../../integrations/hooks.js';
import { getOutputDirectory } from '../../prerender/utils.js';
import type { RouteData } from '../../types/public/internal.js';
import { PAGE_SCRIPT_ID } from '../../vite-plugin-scripts/index.js';
-import { AstroError, AstroErrorData } from '../errors/index.js';
import type { Logger } from '../logger/core.js';
import { routeIsRedirect } from '../redirects/index.js';
import { getOutDirWithinCwd } from './common.js';
@@ -43,10 +41,6 @@ import { encodeName, getTimeStat, viteBuildReturnToRollupOutputs } from './util.
export async function viteBuild(opts: StaticBuildOptions) {
const { allPages, settings, logger } = opts;
- // Make sure we have an adapter before building
- if (isModeServerWithNoAdapter(opts.settings)) {
- throw new AstroError(AstroErrorData.NoAdapterInstalled);
- }
settings.timer.start('SSR build');
@@ -144,15 +138,15 @@ export async function staticBuild(
contentFileNames?: string[],
) {
const { settings } = opts;
- switch (true) {
- case settings.config.output === 'static': {
+ switch (settings.buildOutput) {
+ case 'static': {
settings.timer.start('Static generate');
await generatePages(opts, internals);
await cleanServerOutput(opts, ssrOutputChunkNames, contentFileNames, internals);
settings.timer.end('Static generate');
return;
}
- case isServerLikeOutput(settings.config): {
+ case 'server': {
settings.timer.start('Server generate');
await generatePages(opts, internals);
await cleanStaticOutput(opts, internals);
@@ -161,7 +155,7 @@ export async function staticBuild(
settings.timer.end('Server generate');
return;
}
- default:
+ default: // `settings.buildOutput` will always be one of the above, but TS doesn't know that
return;
}
}
@@ -175,8 +169,8 @@ async function ssrBuild(
) {
const buildID = Date.now().toString();
const { allPages, settings, viteConfig } = opts;
- const ssr = isServerLikeOutput(settings.config);
- const out = getOutputDirectory(settings.config);
+ const ssr = settings.buildOutput === 'server';
+ const out = getOutputDirectory(settings);
const routes = Object.values(allPages).flatMap((pageData) => pageData.route);
const isContentCache = !ssr && settings.config.experimental.contentCollectionCache;
const { lastVitePlugins, vitePlugins } = await container.runBeforeHook('server', input);
@@ -306,7 +300,7 @@ async function clientBuild(
container: AstroBuildPluginContainer,
) {
const { settings, viteConfig } = opts;
- const ssr = isServerLikeOutput(settings.config);
+ const ssr = settings.buildOutput === 'server';
const out = ssr ? settings.config.build.client : getOutDirWithinCwd(settings.config.outDir);
// Nothing to do if there is no client-side JS.
@@ -370,11 +364,12 @@ async function runPostBuildHooks(
const config = container.options.settings.config;
const build = container.options.settings.config.build;
for (const [fileName, mutation] of mutations) {
- const root = isServerLikeOutput(config)
- ? mutation.targets.includes('server')
- ? build.server
- : build.client
- : getOutDirWithinCwd(config.outDir);
+ const root =
+ container.options.settings.buildOutput === 'server'
+ ? mutation.targets.includes('server')
+ ? build.server
+ : build.client
+ : getOutDirWithinCwd(config.outDir);
const fullPath = path.join(fileURLToPath(root), fileName);
const fileURL = pathToFileURL(fullPath);
await fs.promises.mkdir(new URL('./', fileURL), { recursive: true });
@@ -386,7 +381,7 @@ async function runPostBuildHooks(
* Remove chunks that are used for prerendering only
*/
async function cleanStaticOutput(opts: StaticBuildOptions, internals: BuildInternals) {
- const ssr = isServerLikeOutput(opts.settings.config);
+ const ssr = opts.settings.buildOutput === 'server';
const out = ssr
? opts.settings.config.build.server
: getOutDirWithinCwd(opts.settings.config.outDir);
@@ -478,7 +473,7 @@ export async function copyFiles(fromFolder: URL, toFolder: URL, includeDotfiles
async function ssrMoveAssets(opts: StaticBuildOptions) {
opts.logger.info('build', 'Rearranging server assets...');
const serverRoot =
- opts.settings.config.output === 'static'
+ opts.settings.buildOutput === 'static'
? opts.settings.config.build.client
: opts.settings.config.build.server;
const clientRoot = opts.settings.config.build.client;
diff --git a/packages/astro/src/core/config/schema.ts b/packages/astro/src/core/config/schema.ts
index 4c079eae9..f2a397030 100644
--- a/packages/astro/src/core/config/schema.ts
+++ b/packages/astro/src/core/config/schema.ts
@@ -130,7 +130,7 @@ export const AstroConfigSchema = z.object({
.optional()
.default(ASTRO_CONFIG_DEFAULTS.trailingSlash),
output: z
- .union([z.literal('static'), z.literal('server'), z.literal('hybrid')])
+ .union([z.literal('static'), z.literal('server')])
.optional()
.default('static'),
scopedStyleStrategy: z
diff --git a/packages/astro/src/core/config/settings.ts b/packages/astro/src/core/config/settings.ts
index 902ff7d03..e333d6b06 100644
--- a/packages/astro/src/core/config/settings.ts
+++ b/packages/astro/src/core/config/settings.ts
@@ -111,6 +111,7 @@ export function createBaseSettings(config: AstroConfig): AstroSettings {
dotAstroDir,
latestAstroVersion: undefined, // Will be set later if applicable when the dev server starts
injectedTypes: [],
+ buildOutput: undefined,
};
}
diff --git a/packages/astro/src/core/create-vite.ts b/packages/astro/src/core/create-vite.ts
index a63d90ff9..e8d9edfe3 100644
--- a/packages/astro/src/core/create-vite.ts
+++ b/packages/astro/src/core/create-vite.ts
@@ -17,7 +17,7 @@ import astroInternationalization from '../i18n/vite-plugin-i18n.js';
import astroPrefetch from '../prefetch/vite-plugin-prefetch.js';
import astroDevToolbar from '../toolbar/vite-plugin-dev-toolbar.js';
import astroTransitions from '../transitions/vite-plugin-transitions.js';
-import type { AstroSettings } from '../types/astro.js';
+import type { AstroSettings, ManifestData } from '../types/astro.js';
import astroPostprocessVitePlugin from '../vite-plugin-astro-postprocess/index.js';
import { vitePluginAstroServer } from '../vite-plugin-astro-server/index.js';
import astroVitePlugin from '../vite-plugin-astro/index.js';
@@ -33,6 +33,7 @@ import astroScannerPlugin from '../vite-plugin-scanner/index.js';
import astroScriptsPlugin from '../vite-plugin-scripts/index.js';
import astroScriptsPageSSRPlugin from '../vite-plugin-scripts/page-ssr.js';
import { vitePluginSSRManifest } from '../vite-plugin-ssr-manifest/index.js';
+import type { SSRManifest } from './app/types.js';
import type { Logger } from './logger/core.js';
import { createViteLogger } from './logger/vite.js';
import { vitePluginMiddleware } from './middleware/vite-plugin.js';
@@ -48,6 +49,8 @@ interface CreateViteOptions {
command?: 'dev' | 'build';
fs?: typeof nodeFs;
sync: boolean;
+ manifest: ManifestData;
+ ssrManifest?: SSRManifest;
}
const ALWAYS_NOEXTERNAL = [
@@ -75,7 +78,7 @@ const ONLY_DEV_EXTERNAL = [
/** Return a base vite config as a common starting point for all Vite commands. */
export async function createVite(
commandConfig: vite.InlineConfig,
- { settings, logger, mode, command, fs = nodeFs, sync }: CreateViteOptions,
+ { settings, logger, mode, command, fs = nodeFs, sync, manifest, ssrManifest }: CreateViteOptions,
): Promise<vite.InlineConfig> {
const astroPkgsConfig = await crawlFrameworkPkgs({
root: fileURLToPath(settings.config.root),
@@ -131,8 +134,9 @@ export async function createVite(
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' && vitePluginAstroServer({ settings, logger, fs }),
- envVitePlugin({ settings, logger }),
+ mode !== 'build' &&
+ vitePluginAstroServer({ settings, logger, fs, manifest, ssrManifest: ssrManifest! }), // ssrManifest is only required in dev mode, where it gets created before a Vite instance is created, and get passed to this function
+ envVitePlugin({ settings }),
astroEnv({ settings, mode, sync }),
markdownVitePlugin({ settings, logger }),
htmlVitePlugin(),
@@ -140,7 +144,7 @@ export async function createVite(
astroIntegrationsContainerPlugin({ settings, logger }),
astroScriptsPageSSRPlugin({ settings }),
astroHeadPlugin(),
- astroScannerPlugin({ settings, logger }),
+ astroScannerPlugin({ settings, logger, manifest }),
astroContentVirtualModPlugin({ fs, settings }),
astroContentImportPlugin({ fs, settings, logger }),
astroContentAssetPropagationPlugin({ mode, settings }),
diff --git a/packages/astro/src/core/dev/container.ts b/packages/astro/src/core/dev/container.ts
index f89e33197..1fea20620 100644
--- a/packages/astro/src/core/dev/container.ts
+++ b/packages/astro/src/core/dev/container.ts
@@ -4,7 +4,6 @@ import type { AstroSettings } from '../../types/astro.js';
import nodeFs from 'node:fs';
import * as vite from 'vite';
-import { injectImageEndpoint } from '../../assets/endpoint/config.js';
import {
runHookConfigDone,
runHookConfigSetup,
@@ -12,9 +11,12 @@ import {
runHookServerStart,
} from '../../integrations/hooks.js';
import type { AstroInlineConfig } from '../../types/public/config.js';
+import { createDevelopmentManifest } from '../../vite-plugin-astro-server/plugin.js';
import { createVite } from '../create-vite.js';
import type { Logger } from '../logger/core.js';
import { apply as applyPolyfill } from '../polyfill.js';
+import { injectDefaultDevRoutes } from '../routing/dev-default.js';
+import { createRouteManifest } from '../routing/index.js';
import { syncInternal } from '../sync/index.js';
export interface Container {
@@ -52,8 +54,6 @@ export async function createContainer({
isRestart,
});
- settings = injectImageEndpoint(settings, 'dev');
-
const {
base,
server: { host, headers, open: serverOpen },
@@ -81,6 +81,12 @@ export async function createContainer({
.map((r) => r.clientEntrypoint)
.filter(Boolean) as string[];
+ // Create the route manifest already outside of Vite so that `runHookConfigDone` can use it to inform integrations of the build output
+ let manifest = await createRouteManifest({ settings, fsMod: fs }, logger);
+ const devSSRManifest = createDevelopmentManifest(settings);
+
+ manifest = injectDefaultDevRoutes(settings, devSSRManifest, manifest);
+
const viteConfig = await createVite(
{
mode: 'development',
@@ -89,8 +95,18 @@ export async function createContainer({
include: rendererClientEntries,
},
},
- { settings, logger, mode: 'dev', command: 'dev', fs, sync: false },
+ {
+ settings,
+ logger,
+ mode: 'dev',
+ command: 'dev',
+ fs,
+ sync: false,
+ manifest,
+ ssrManifest: devSSRManifest,
+ },
);
+
await runHookConfigDone({ settings, logger });
await syncInternal({
settings,
@@ -99,6 +115,7 @@ export async function createContainer({
content: true,
},
force: inlineConfig?.force,
+ manifest,
});
const viteServer = await vite.createServer(viteConfig);
diff --git a/packages/astro/src/core/errors/errors-data.ts b/packages/astro/src/core/errors/errors-data.ts
index 72d5739f5..8e189bfe4 100644
--- a/packages/astro/src/core/errors/errors-data.ts
+++ b/packages/astro/src/core/errors/errors-data.ts
@@ -74,6 +74,7 @@ export const PrerenderClientAddressNotAvailable = {
export const StaticClientAddressNotAvailable = {
name: 'StaticClientAddressNotAvailable',
title: '`Astro.clientAddress` is not available in static mode.',
+ // TODO: Update this for the new static mode? I'm not sure this error can even still happen.
message:
"`Astro.clientAddress` is only available when using `output: 'server'` or `output: 'hybrid'`. Update your Astro config if you need SSR features.",
hint: 'See https://docs.astro.build/en/guides/server-side-rendering/ for more information on how to enable SSR.',
@@ -367,8 +368,7 @@ export const GetStaticPathsRequired = {
'`getStaticPaths()` function is required for dynamic routes. Make sure that you `export` a `getStaticPaths` function from your dynamic route.',
hint: `See https://docs.astro.build/en/guides/routing/#dynamic-routes for more information on dynamic routes.
-Alternatively, set \`output: "server"\` or \`output: "hybrid"\` in your Astro config file to switch to a non-static server build. This error can also occur if using \`export const prerender = true;\`.
-See https://docs.astro.build/en/guides/server-side-rendering/ for more information on non-static rendering.`,
+ If you meant for this route to be server-rendered, set \`export const prerender = false;\` in the page.`,
} satisfies ErrorData;
/**
* @docs
@@ -393,7 +393,7 @@ export const ReservedSlotName = {
export const NoAdapterInstalled = {
name: 'NoAdapterInstalled',
title: 'Cannot use Server-side Rendering without an adapter.',
- message: `Cannot use \`output: 'server'\` or \`output: 'hybrid'\` without an adapter. Please install and configure the appropriate server adapter for your final deployment.`,
+ message: `Cannot use server-rendered pages without an adapter. Please install and configure the appropriate server adapter for your final deployment.`,
hint: 'See https://docs.astro.build/en/guides/server-side-rendering/ for more information.',
} satisfies ErrorData;
/**
diff --git a/packages/astro/src/core/middleware/vite-plugin.ts b/packages/astro/src/core/middleware/vite-plugin.ts
index 8a76bed92..f80af2467 100644
--- a/packages/astro/src/core/middleware/vite-plugin.ts
+++ b/packages/astro/src/core/middleware/vite-plugin.ts
@@ -126,7 +126,7 @@ export function vitePluginMiddlewareBuild(
writeBundle(_, bundle) {
for (const [chunkName, chunk] of Object.entries(bundle)) {
if (chunk.type !== 'asset' && chunk.fileName === 'middleware.mjs') {
- const outputDirectory = getOutputDirectory(opts.settings.config);
+ const outputDirectory = getOutputDirectory(opts.settings);
internals.middlewareEntryPoint = new URL(chunkName, outputDirectory);
}
}
diff --git a/packages/astro/src/core/preview/index.ts b/packages/astro/src/core/preview/index.ts
index a8a533fe8..88338c606 100644
--- a/packages/astro/src/core/preview/index.ts
+++ b/packages/astro/src/core/preview/index.ts
@@ -11,6 +11,7 @@ import { resolveConfig } from '../config/config.js';
import { createNodeLogger } from '../config/logging.js';
import { createSettings } from '../config/settings.js';
import { apply as applyPolyfills } from '../polyfill.js';
+import { createRouteManifest } from '../routing/index.js';
import { ensureProcessNodeEnv } from '../util.js';
import createStaticPreviewServer from './static-preview-server.js';
import { getResolvedHostForHttpServer } from './util.js';
@@ -35,9 +36,13 @@ export default async function preview(inlineConfig: AstroInlineConfig): Promise<
command: 'preview',
logger: logger,
});
+
+ // Create a route manifest so we can know if the build output is a static site or not
+ await createRouteManifest({ settings: settings, cwd: inlineConfig.root }, logger);
+
await runHookConfigDone({ settings: settings, logger: logger });
- if (settings.config.output === 'static') {
+ if (settings.buildOutput === 'static') {
if (!fs.existsSync(settings.config.outDir)) {
const outDirPath = fileURLToPath(settings.config.outDir);
throw new Error(
@@ -47,9 +52,11 @@ export default async function preview(inlineConfig: AstroInlineConfig): Promise<
const server = await createStaticPreviewServer(settings, logger);
return server;
}
+
if (!settings.adapter) {
throw new Error(`[preview] No adapter found.`);
}
+
if (!settings.adapter.previewEntrypoint) {
throw new Error(
`[preview] The ${settings.adapter.name} adapter does not support the preview command.`,
diff --git a/packages/astro/src/core/render-context.ts b/packages/astro/src/core/render-context.ts
index df86598b5..1e3533943 100644
--- a/packages/astro/src/core/render-context.ts
+++ b/packages/astro/src/core/render-context.ts
@@ -148,7 +148,12 @@ export class RenderContext {
switch (this.routeData.type) {
case 'endpoint': {
- response = await renderEndpoint(componentInstance as any, ctx, serverLike, logger);
+ response = await renderEndpoint(
+ componentInstance as any,
+ ctx,
+ this.routeData.prerender,
+ logger,
+ );
break;
}
case 'redirect':
diff --git a/packages/astro/src/core/request.ts b/packages/astro/src/core/request.ts
index 93b552901..9b2dc07ca 100644
--- a/packages/astro/src/core/request.ts
+++ b/packages/astro/src/core/request.ts
@@ -1,6 +1,5 @@
import type { IncomingHttpHeaders } from 'node:http';
import type { Logger } from './logger/core.js';
-import { appendForwardSlash, prependForwardSlash } from './path.js';
type HeaderType = Headers | Record<string, any> | IncomingHttpHeaders;
type RequestBody = ArrayBuffer | Blob | ReadableStream | URLSearchParams | FormData;
@@ -35,7 +34,6 @@ const clientLocalsSymbol = Symbol.for('astro.locals');
* This is used by the static build to create fake requests for prerendering, and by the dev server to convert node requests into the standard request object.
*/
export function createRequest({
- base,
url,
headers,
clientAddress,
@@ -60,10 +58,7 @@ export function createRequest({
if (typeof url === 'string') url = new URL(url);
- const imageEndpoint = prependForwardSlash(appendForwardSlash(base)) + '_image';
-
- // HACK! astro:assets uses query params for the injected route in `dev`
- if (staticLike && url.pathname !== imageEndpoint) {
+ if (staticLike) {
url.search = '';
}
diff --git a/packages/astro/src/core/routing/dev-default.ts b/packages/astro/src/core/routing/dev-default.ts
new file mode 100644
index 000000000..ac2ea1d3b
--- /dev/null
+++ b/packages/astro/src/core/routing/dev-default.ts
@@ -0,0 +1,14 @@
+import { ensureImageEndpointRoute } from '../../assets/endpoint/config.js';
+import type { AstroSettings, ManifestData } from '../../types/astro.js';
+import type { SSRManifest } from '../app/types.js';
+import { injectDefaultRoutes } from './default.js';
+
+export function injectDefaultDevRoutes(
+ settings: AstroSettings,
+ ssrManifest: SSRManifest,
+ routeManifest: ManifestData,
+) {
+ ensureImageEndpointRoute(settings, routeManifest, 'dev');
+ injectDefaultRoutes(ssrManifest, routeManifest);
+ return routeManifest;
+}
diff --git a/packages/astro/src/core/routing/manifest/create.ts b/packages/astro/src/core/routing/manifest/create.ts
index 858c2b9f2..e22eb8de1 100644
--- a/packages/astro/src/core/routing/manifest/create.ts
+++ b/packages/astro/src/core/routing/manifest/create.ts
@@ -6,7 +6,9 @@ import { createRequire } from 'node:module';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import { bold } from 'kleur/colors';
+import pLimit from 'p-limit';
import { toRoutingStrategy } from '../../../i18n/utils.js';
+import { runHookRouteSetup } from '../../../integrations/hooks.js';
import { getPrerenderDefault } from '../../../prerender/utils.js';
import type { AstroConfig } from '../../../types/public/config.js';
import type { RouteData, RoutePart } from '../../../types/public/internal.js';
@@ -278,13 +280,7 @@ function createInjectedRoutes({ settings, cwd }: CreateRouteManifestParams): Rou
for (const injectedRoute of settings.injectedRoutes) {
const { pattern: name, entrypoint, prerender: prerenderInjected } = injectedRoute;
- let resolved: string;
- try {
- resolved = require.resolve(entrypoint, { paths: [cwd || fileURLToPath(config.root)] });
- } catch {
- resolved = fileURLToPath(new URL(entrypoint, config.root));
- }
- const component = slash(path.relative(cwd || fileURLToPath(config.root), resolved));
+ const { resolved, component } = resolveInjectedRoute(entrypoint, config.root, cwd);
const segments = removeLeadingForwardSlash(name)
.split(path.posix.sep)
@@ -478,10 +474,10 @@ function detectRouteCollision(a: RouteData, b: RouteData, _config: AstroConfig,
}
/** Create manifest of all static routes */
-export function createRouteManifest(
+export async function createRouteManifest(
params: CreateRouteManifestParams,
logger: Logger,
-): ManifestData {
+): Promise<ManifestData> {
const { settings } = params;
const { config } = settings;
// Create a map of all routes so redirects can refer to any route
@@ -503,6 +499,18 @@ export function createRouteManifest(
...[...fileBasedRoutes, ...injectedRoutes, ...redirectRoutes].sort(routeComparator),
];
+ settings.buildOutput = getPrerenderDefault(config) ? 'static' : 'server';
+
+ // Check the prerender option for each route
+ const limit = pLimit(10);
+ let promises = [];
+ for (const route of routes) {
+ promises.push(
+ limit(async () => await getRoutePrerenderOption(route, settings, params.fsMod, logger)),
+ );
+ }
+ await Promise.all(promises);
+
// Report route collisions
for (const [index, higherRoute] of routes.entries()) {
for (const lowerRoute of routes.slice(index + 1)) {
@@ -706,6 +714,49 @@ export function createRouteManifest(
};
}
+async function getRoutePrerenderOption(
+ route: RouteData,
+ settings: AstroSettings,
+ fsMod: typeof nodeFs | undefined,
+ logger: Logger,
+) {
+ if (route.type !== 'page' && route.type !== 'endpoint') return;
+ const localFs = fsMod ?? nodeFs;
+ const content = await localFs.promises.readFile(
+ fileURLToPath(new URL(route.component, settings.config.root)),
+ 'utf-8',
+ );
+
+ // Check if the route is pre-rendered or not
+ const match = /^\s*export\s+const\s+prerender\s*=\s*(true|false);?/m.exec(content);
+ if (match) {
+ route.prerender = match[1] === 'true';
+ }
+
+ await runHookRouteSetup({ route, settings, logger });
+
+ // If not explicitly set, default to the global setting
+ if (typeof route.prerender === undefined) {
+ route.prerender = getPrerenderDefault(settings.config);
+ }
+
+ if (!route.prerender) settings.buildOutput = 'server';
+}
+
+export function resolveInjectedRoute(entrypoint: string, root: URL, cwd?: string) {
+ let resolved;
+ try {
+ resolved = require.resolve(entrypoint, { paths: [cwd || fileURLToPath(root)] });
+ } catch {
+ resolved = fileURLToPath(new URL(entrypoint, root));
+ }
+
+ return {
+ resolved: resolved,
+ component: slash(path.relative(cwd || fileURLToPath(root), resolved)),
+ };
+}
+
function joinSegments(segments: RoutePart[][]): string {
const arr = segments.map((segment) => {
return segment.map((rp) => (rp.dynamic ? `[${rp.content}]` : rp.content)).join('');
diff --git a/packages/astro/src/core/sync/index.ts b/packages/astro/src/core/sync/index.ts
index 08701b67e..436598dce 100644
--- a/packages/astro/src/core/sync/index.ts
+++ b/packages/astro/src/core/sync/index.ts
@@ -14,7 +14,7 @@ import { syncAstroEnv } from '../../env/sync.js';
import { telemetry } from '../../events/index.js';
import { eventCliSession } from '../../events/session.js';
import { runHookConfigDone, runHookConfigSetup } from '../../integrations/hooks.js';
-import type { AstroSettings } from '../../types/astro.js';
+import type { AstroSettings, ManifestData } from '../../types/astro.js';
import type { AstroInlineConfig } from '../../types/public/config.js';
import { getTimeStat } from '../build/util.js';
import { resolveConfig } from '../config/config.js';
@@ -31,6 +31,7 @@ import {
} from '../errors/index.js';
import type { Logger } from '../logger/core.js';
import { formatErrorMessage } from '../messages.js';
+import { createRouteManifest } from '../routing/index.js';
import { ensureProcessNodeEnv } from '../util.js';
export type SyncOptions = {
@@ -45,6 +46,7 @@ export type SyncOptions = {
// Must be skipped in dev
content?: boolean;
};
+ manifest: ManifestData;
};
export default async function sync(
@@ -63,8 +65,9 @@ export default async function sync(
settings,
logger,
});
+ const manifest = await createRouteManifest({ settings, fsMod: fs }, logger);
await runHookConfigDone({ settings, logger });
- return await syncInternal({ settings, logger, fs, force: inlineConfig.force });
+ return await syncInternal({ settings, logger, fs, force: inlineConfig.force, manifest });
}
/**
@@ -95,6 +98,7 @@ export async function syncInternal({
settings,
skip,
force,
+ manifest,
}: SyncOptions): Promise<void> {
if (force) {
await clearContentLayerCache({ settings, logger, fs });
@@ -104,7 +108,7 @@ export async function syncInternal({
try {
if (!skip?.content) {
- await syncContentCollections(settings, { fs, logger });
+ await syncContentCollections(settings, { fs, logger, manifest });
settings.timer.start('Sync content layer');
let store: MutableDataStore | undefined;
try {
@@ -192,7 +196,7 @@ function writeInjectedTypes(settings: AstroSettings, fs: typeof fsMod) {
*/
async function syncContentCollections(
settings: AstroSettings,
- { logger, fs }: Required<Pick<SyncOptions, 'logger' | 'fs'>>,
+ { logger, fs, manifest }: Required<Pick<SyncOptions, 'logger' | 'fs' | 'manifest'>>,
): Promise<void> {
// Needed to load content config
const tempViteServer = await createServer(
@@ -203,7 +207,7 @@ async function syncContentCollections(
ssr: { external: [] },
logLevel: 'silent',
},
- { settings, logger, mode: 'build', command: 'build', fs, sync: true },
+ { settings, logger, mode: 'build', command: 'build', fs, sync: true, manifest },
),
);
diff --git a/packages/astro/src/core/util.ts b/packages/astro/src/core/util.ts
index 497d54610..3375deaef 100644
--- a/packages/astro/src/core/util.ts
+++ b/packages/astro/src/core/util.ts
@@ -158,19 +158,11 @@ export function isEndpoint(file: URL, settings: AstroSettings): boolean {
return !endsWithPageExt(file, settings) && !file.toString().includes('?astro');
}
-export function isServerLikeOutput(config: AstroConfig) {
- return config.output === 'server' || config.output === 'hybrid';
-}
-
-export function isModeServerWithNoAdapter(settings: AstroSettings): boolean {
- return isServerLikeOutput(settings.config) && !settings.adapter;
-}
-
-export function isContentCollectionsCacheEnabled(config: AstroConfig): boolean {
+export function isContentCollectionsCacheEnabled(settings: AstroSettings): boolean {
return (
- config.experimental.contentCollectionCache &&
+ settings.config.experimental.contentCollectionCache &&
// contentCollectionsCache is an SSG only feature
- !isServerLikeOutput(config)
+ settings.buildOutput !== 'server'
);
}
diff --git a/packages/astro/src/integrations/features-validation.ts b/packages/astro/src/integrations/features-validation.ts
index a9386090d..0d4d081c4 100644
--- a/packages/astro/src/integrations/features-validation.ts
+++ b/packages/astro/src/integrations/features-validation.ts
@@ -1,4 +1,5 @@
import type { Logger } from '../core/logger/core.js';
+import type { AstroSettings } from '../types/astro.js';
import type { AstroConfig } from '../types/public/config.js';
import type {
AdapterSupportsKind,
@@ -31,7 +32,7 @@ type ValidationResult = {
export function validateSupportedFeatures(
adapterName: string,
featureMap: AstroAdapterFeatureMap,
- config: AstroConfig,
+ settings: AstroSettings,
adapterFeatures: AstroAdapterFeatures | undefined,
logger: Logger,
): ValidationResult {
@@ -50,7 +51,7 @@ export function validateSupportedFeatures(
adapterName,
logger,
'staticOutput',
- () => config?.output === 'static',
+ () => settings.buildOutput === 'static',
);
validationResult.hybridOutput = validateSupportKind(
@@ -58,7 +59,7 @@ export function validateSupportedFeatures(
adapterName,
logger,
'hybridOutput',
- () => config?.output === 'hybrid',
+ () => settings.config.output == 'static' && settings.buildOutput === 'server',
);
validationResult.serverOutput = validateSupportKind(
@@ -66,18 +67,18 @@ export function validateSupportedFeatures(
adapterName,
logger,
'serverOutput',
- () => config?.output === 'server',
+ () => settings.config?.output === 'server',
);
- validationResult.assets = validateAssetsFeature(assets, adapterName, config, logger);
+ validationResult.assets = validateAssetsFeature(assets, adapterName, settings.config, logger);
- if (config.i18n?.domains) {
+ if (settings.config.i18n?.domains) {
validationResult.i18nDomains = validateSupportKind(
i18nDomains,
adapterName,
logger,
'i18nDomains',
() => {
- return config?.output === 'server' && !config?.site;
+ return settings.config?.output === 'server' && !settings.config?.site;
},
);
}
@@ -87,7 +88,7 @@ export function validateSupportedFeatures(
adapterName,
logger,
'astro:env getSecret',
- () => Object.keys(config?.env?.schema ?? {}).length !== 0,
+ () => Object.keys(settings.config?.env?.schema ?? {}).length !== 0,
);
return validationResult;
diff --git a/packages/astro/src/integrations/hooks.ts b/packages/astro/src/integrations/hooks.ts
index 7ac4a2323..e8828fac9 100644
--- a/packages/astro/src/integrations/hooks.ts
+++ b/packages/astro/src/integrations/hooks.ts
@@ -13,7 +13,6 @@ import type { PageBuildData } from '../core/build/types.js';
import { buildClientDirectiveEntrypoint } from '../core/client-directive/index.js';
import { mergeConfig } from '../core/config/index.js';
import type { AstroIntegrationLogger, Logger } from '../core/logger/core.js';
-import { isServerLikeOutput } from '../core/util.js';
import type { AstroSettings } from '../types/astro.js';
import type { AstroConfig } from '../types/public/config.js';
import type {
@@ -133,9 +132,9 @@ export async function runHookConfigSetup({
isRestart?: boolean;
fs?: typeof fsMod;
}): Promise<AstroSettings> {
- // An adapter is an integration, so if one is provided push it.
+ // An adapter is an integration, so if one is provided add it to the list of integrations.
if (settings.config.adapter) {
- settings.config.integrations.push(settings.config.adapter);
+ settings.config.integrations.unshift(settings.config.adapter);
}
if (await isActionsFilePresent(fs, settings.config.srcDir)) {
settings.config.integrations.push(astroIntegrationActionsRouteHandler({ settings }));
@@ -314,6 +313,11 @@ export async function runHookConfigDone({
`Integration "${integration.name}" conflicts with "${settings.adapter.name}". You can only configure one deployment integration.`,
);
}
+
+ if (adapter.adapterFeatures?.forceServerOutput) {
+ settings.buildOutput = 'server';
+ }
+
if (!adapter.supportedAstroFeatures) {
throw new Error(
`The adapter ${adapter.name} doesn't provide a feature map. It is required in Astro 4.0.`,
@@ -322,7 +326,7 @@ export async function runHookConfigDone({
const validationResult = validateSupportedFeatures(
adapter.name,
adapter.supportedAstroFeatures,
- settings.config,
+ settings,
// SAFETY: we checked before if it's not present, and we throw an error
adapter.adapterFeatures,
logger,
@@ -358,6 +362,9 @@ export async function runHookConfigDone({
return new URL(normalizedFilename, settings.dotAstroDir);
},
logger: getLogger(integration, logger),
+ get buildOutput() {
+ return settings.buildOutput!; // settings.buildOutput is always set at this point
+ },
}),
logger,
});
@@ -544,15 +551,16 @@ export async function runHookBuildSsr({
}
export async function runHookBuildGenerated({
- config,
+ settings,
logger,
}: {
- config: AstroConfig;
+ settings: AstroSettings;
logger: Logger;
}) {
- const dir = isServerLikeOutput(config) ? config.build.client : config.outDir;
+ const dir =
+ settings.buildOutput === 'server' ? settings.config.build.client : settings.config.outDir;
- for (const integration of config.integrations) {
+ for (const integration of settings.config.integrations) {
if (integration?.hooks?.['astro:build:generated']) {
await withTakingALongTimeMsg({
name: integration.name,
@@ -568,7 +576,7 @@ export async function runHookBuildGenerated({
}
type RunHookBuildDone = {
- config: AstroConfig;
+ settings: AstroSettings;
pages: string[];
routes: RouteData[];
logging: Logger;
@@ -576,16 +584,17 @@ type RunHookBuildDone = {
};
export async function runHookBuildDone({
- config,
+ settings,
pages,
routes,
logging,
cacheManifest,
}: RunHookBuildDone) {
- const dir = isServerLikeOutput(config) ? config.build.client : config.outDir;
+ const dir =
+ settings.buildOutput === 'server' ? settings.config.build.client : settings.config.outDir;
await fsMod.promises.mkdir(dir, { recursive: true });
- for (const integration of config.integrations) {
+ for (const integration of settings.config.integrations) {
if (integration?.hooks?.['astro:build:done']) {
const logger = getLogger(integration, logging);
diff --git a/packages/astro/src/prerender/utils.ts b/packages/astro/src/prerender/utils.ts
index e34e0d5fd..06ddc09ef 100644
--- a/packages/astro/src/prerender/utils.ts
+++ b/packages/astro/src/prerender/utils.ts
@@ -1,5 +1,5 @@
import { getOutDirWithinCwd } from '../core/build/common.js';
-import { isServerLikeOutput } from '../core/util.js';
+import type { AstroSettings } from '../types/astro.js';
import type { AstroConfig } from '../types/public/config.js';
export function getPrerenderDefault(config: AstroConfig) {
@@ -9,11 +9,10 @@ export function getPrerenderDefault(config: AstroConfig) {
/**
* Returns the correct output directory of the SSR build based on the configuration
*/
-export function getOutputDirectory(config: AstroConfig): URL {
- const ssr = isServerLikeOutput(config);
- if (ssr) {
- return config.build.server;
+export function getOutputDirectory(settings: AstroSettings): URL {
+ if (settings.buildOutput === 'server') {
+ return settings.config.build.server;
} else {
- return getOutDirWithinCwd(config.outDir);
+ return getOutDirWithinCwd(settings.config.outDir);
}
}
diff --git a/packages/astro/src/runtime/server/endpoint.ts b/packages/astro/src/runtime/server/endpoint.ts
index 674d62093..7adc89de0 100644
--- a/packages/astro/src/runtime/server/endpoint.ts
+++ b/packages/astro/src/runtime/server/endpoint.ts
@@ -12,7 +12,7 @@ export async function renderEndpoint(
[method: string]: APIRoute;
},
context: APIContext,
- ssr: boolean,
+ isPrerendered: boolean,
logger: Logger,
) {
const { request, url } = context;
@@ -20,12 +20,12 @@ export async function renderEndpoint(
const method = request.method.toUpperCase();
// use the exact match on `method`, fallback to ALL
const handler = mod[method] ?? mod['ALL'];
- if (!ssr && ssr === false && method !== 'GET') {
+ if (isPrerendered && method !== 'GET') {
logger.warn(
'router',
`${url.pathname} ${bold(
method,
- )} requests are not available for a static site. Update your config to \`output: 'server'\` or \`output: 'hybrid'\` to enable.`,
+ )} requests are not available in static endpoints. Mark this page as server-rendered (\`export const prerender = false;\`) or update your config to \`output: 'server'\` to make all your pages server-rendered by default.`,
);
}
if (handler === undefined) {
diff --git a/packages/astro/src/types/astro.ts b/packages/astro/src/types/astro.ts
index 1d48536d0..7e2f2af7e 100644
--- a/packages/astro/src/types/astro.ts
+++ b/packages/astro/src/types/astro.ts
@@ -67,6 +67,11 @@ export interface AstroSettings {
serverIslandMap: NonNullable<SSRManifest['serverIslandMap']>;
serverIslandNameMap: NonNullable<SSRManifest['serverIslandNameMap']>;
injectedTypes: Array<InjectedType>;
+ /**
+ * Determine if the build output should be a static, dist folder or a adapter-based server output
+ * undefined when unknown
+ */
+ buildOutput: undefined | 'static' | 'server';
}
/** Generic interface for a component (Astro, Svelte, React, etc.) */
diff --git a/packages/astro/src/types/public/config.ts b/packages/astro/src/types/public/config.ts
index 76b224824..b75cf027b 100644
--- a/packages/astro/src/types/public/config.ts
+++ b/packages/astro/src/types/public/config.ts
@@ -240,16 +240,15 @@ export interface AstroUserConfig {
/**
* @docs
* @name output
- * @type {('static' | 'server' | 'hybrid')}
+ * @type {('static' | 'server')}
* @default `'static'`
* @see adapter
* @description
*
* Specifies the output target for builds.
*
- * - `'static'` - Building a static site to be deployed to any static host.
- * - `'server'` - Building an app to be deployed to a host supporting SSR (server-side rendering).
- * - `'hybrid'` - Building a static site with a few server-side rendered pages.
+ * - `'static'` - Prerender all your pages by default, outputting a completely static site if none of your pages opt out of prerendering.
+ * - `'server'` - Use server-side rendering (SSR) for all pages by default, always outputting a server-rendered site.
*
* ```js
* import { defineConfig } from 'astro/config';
@@ -259,7 +258,7 @@ export interface AstroUserConfig {
* })
* ```
*/
- output?: 'static' | 'server' | 'hybrid';
+ output?: 'static' | 'server';
/**
* @docs
@@ -450,7 +449,7 @@ export interface AstroUserConfig {
*
* Enables security measures for an Astro website.
*
- * These features only exist for pages rendered on demand (SSR) using `server` mode or pages that opt out of prerendering in `hybrid` mode.
+ * These features only exist for pages rendered on demand (SSR) using `server` mode or pages that opt out of prerendering in `static` mode.
*
* ```js
* // astro.config.mjs
@@ -563,14 +562,14 @@ export interface AstroUserConfig {
* @type {string}
* @default `'./dist/client'`
* @description
- * Controls the output directory of your client-side CSS and JavaScript when `output: 'server'` or `output: 'hybrid'` only.
+ * Controls the output directory of your client-side CSS and JavaScript when building a website with server-rendered pages.
* `outDir` controls where the code is built to.
*
* This value is relative to the `outDir`.
*
* ```js
* {
- * output: 'server', // or 'hybrid'
+ * output: 'server',
* build: {
* client: './client'
* }
diff --git a/packages/astro/src/types/public/integrations.ts b/packages/astro/src/types/public/integrations.ts
index 19cc1e298..eaa84f279 100644
--- a/packages/astro/src/types/public/integrations.ts
+++ b/packages/astro/src/types/public/integrations.ts
@@ -67,6 +67,10 @@ export interface AstroAdapterFeatures {
* Creates an edge function that will communiate with the Astro middleware
*/
edgeMiddleware: boolean;
+ /**
+ * Force Astro to output a server output, even if all the pages are prerendered
+ */
+ forceServerOutput?: boolean;
}
export interface AstroAdapter {
@@ -183,6 +187,7 @@ export interface BaseIntegrationHooks {
setAdapter: (adapter: AstroAdapter) => void;
injectTypes: (injectedType: InjectedType) => URL;
logger: AstroIntegrationLogger;
+ buildOutput: 'static' | 'server';
}) => void | Promise<void>;
'astro:server:setup': (options: {
server: ViteDevServer;
diff --git a/packages/astro/src/vite-plugin-astro-server/pipeline.ts b/packages/astro/src/vite-plugin-astro-server/pipeline.ts
index b22a3653d..bbeba1eaa 100644
--- a/packages/astro/src/vite-plugin-astro-server/pipeline.ts
+++ b/packages/astro/src/vite-plugin-astro-server/pipeline.ts
@@ -9,7 +9,7 @@ import type { ModuleLoader } from '../core/module-loader/index.js';
import { Pipeline, loadRenderer } from '../core/render/index.js';
import { createDefaultRoutes } from '../core/routing/default.js';
import { findRouteToRewrite } from '../core/routing/rewrite.js';
-import { isPage, isServerLikeOutput, viteID } from '../core/util.js';
+import { isPage, viteID } from '../core/util.js';
import { resolveIdToUrl } from '../core/viteUtils.js';
import type { AstroSettings, ComponentInstance, ManifestData } from '../types/astro.js';
import type { RewritePayload } from '../types/public/common.js';
@@ -47,7 +47,7 @@ export class DevPipeline extends Pipeline {
) {
const mode = 'development';
const resolve = createResolve(loader, config.root);
- const serverLike = isServerLikeOutput(config);
+ const serverLike = settings.buildOutput === 'server';
const streaming = true;
super(logger, manifest, mode, [], resolve, serverLike, streaming);
manifest.serverIslandMap = settings.serverIslandMap;
@@ -219,7 +219,7 @@ export class DevPipeline extends Pipeline {
}
rewriteKnownRoute(route: string, sourceRoute: RouteData): ComponentInstance {
- if (isServerLikeOutput(this.config) && sourceRoute.prerender) {
+ if (this.serverLike && sourceRoute.prerender) {
for (let def of this.defaultRoutes) {
if (route === def.route) {
return def.instance;
diff --git a/packages/astro/src/vite-plugin-astro-server/plugin.ts b/packages/astro/src/vite-plugin-astro-server/plugin.ts
index f1cfa16ba..20020559e 100644
--- a/packages/astro/src/vite-plugin-astro-server/plugin.ts
+++ b/packages/astro/src/vite-plugin-astro-server/plugin.ts
@@ -9,7 +9,7 @@ import { AstroError, AstroErrorData } from '../core/errors/index.js';
import { patchOverlay } from '../core/errors/overlay.js';
import type { Logger } from '../core/logger/core.js';
import { createViteLoader } from '../core/module-loader/index.js';
-import { injectDefaultRoutes } from '../core/routing/default.js';
+import { injectDefaultDevRoutes } from '../core/routing/dev-default.js';
import { createRouteManifest } from '../core/routing/index.js';
import { toFallbackType, toRoutingStrategy } from '../i18n/utils.js';
import type { AstroSettings, ManifestData } from '../types/astro.js';
@@ -24,32 +24,40 @@ export interface AstroPluginOptions {
settings: AstroSettings;
logger: Logger;
fs: typeof fs;
+ manifest: ManifestData;
+ ssrManifest: SSRManifest;
}
export default function createVitePluginAstroServer({
settings,
logger,
fs: fsMod,
+ manifest: routeManifest,
+ ssrManifest: devSSRManifest,
}: AstroPluginOptions): vite.Plugin {
return {
name: 'astro:server',
configureServer(viteServer) {
const loader = createViteLoader(viteServer);
- const manifest = createDevelopmentManifest(settings);
- let manifestData: ManifestData = injectDefaultRoutes(
- manifest,
- createRouteManifest({ settings, fsMod }, logger),
- );
- const pipeline = DevPipeline.create(manifestData, { loader, logger, manifest, settings });
+ const pipeline = DevPipeline.create(routeManifest, {
+ loader,
+ logger,
+ manifest: devSSRManifest,
+ settings,
+ });
const controller = createController({ loader });
const localStorage = new AsyncLocalStorage();
/** rebuild the route cache + manifest, as needed. */
- function rebuildManifest(needsManifestRebuild: boolean) {
+ async function rebuildManifest(needsManifestRebuild: boolean) {
pipeline.clearRouteCache();
if (needsManifestRebuild) {
- manifestData = injectDefaultRoutes(manifest, createRouteManifest({ settings }, logger));
- pipeline.setManifestData(manifestData);
+ routeManifest = injectDefaultDevRoutes(
+ settings,
+ devSSRManifest,
+ await createRouteManifest({ settings, fsMod }, logger), // TODO: Handle partial updates to the manifest
+ );
+ pipeline.setManifestData(routeManifest);
}
}
@@ -94,7 +102,7 @@ export default function createVitePluginAstroServer({
localStorage.run(request, () => {
handleRequest({
pipeline,
- manifestData,
+ manifestData: routeManifest,
controller,
incomingRequest: request,
incomingResponse: response,
diff --git a/packages/astro/src/vite-plugin-astro-server/route.ts b/packages/astro/src/vite-plugin-astro-server/route.ts
index e60024e4b..5fb3c3265 100644
--- a/packages/astro/src/vite-plugin-astro-server/route.ts
+++ b/packages/astro/src/vite-plugin-astro-server/route.ts
@@ -175,7 +175,7 @@ export async function handleRoute({
body,
logger,
clientAddress: incomingRequest.socket.remoteAddress,
- staticLike: config.output === 'static' || route.prerender,
+ staticLike: route.prerender,
});
// Set user specified headers to response object.
diff --git a/packages/astro/src/vite-plugin-env/index.ts b/packages/astro/src/vite-plugin-env/index.ts
index 0408e645d..f68012927 100644
--- a/packages/astro/src/vite-plugin-env/index.ts
+++ b/packages/astro/src/vite-plugin-env/index.ts
@@ -1,16 +1,13 @@
import { fileURLToPath } from 'node:url';
import { transform } from 'esbuild';
-import { bold } from 'kleur/colors';
import MagicString from 'magic-string';
import type * as vite from 'vite';
import { loadEnv } from 'vite';
-import type { Logger } from '../core/logger/core.js';
import type { AstroSettings } from '../types/astro.js';
import type { AstroConfig } from '../types/public/config.js';
interface EnvPluginOptions {
settings: AstroSettings;
- logger: Logger;
}
// Match `import.meta.env` directly without trailing property access
@@ -18,9 +15,6 @@ const importMetaEnvOnlyRe = /\bimport\.meta\.env\b(?!\.)/;
// Match valid JS variable names (identifiers), which accepts most alphanumeric characters,
// except that the first character cannot be a number.
const isValidIdentifierRe = /^[_$a-zA-Z][\w$]*$/;
-// Match `export const prerender = import.meta.env.*` since `vite=plugin-scanner` requires
-// the `import.meta.env.*` to always be replaced.
-const exportConstPrerenderRe = /\bexport\s+const\s+prerender\s*=\s*import\.meta\.env\.(.+?)\b/;
function getPrivateEnv(
viteConfig: vite.ResolvedConfig,
@@ -120,7 +114,7 @@ async function replaceDefine(
};
}
-export default function envVitePlugin({ settings, logger }: EnvPluginOptions): vite.Plugin {
+export default function envVitePlugin({ settings }: EnvPluginOptions): vite.Plugin {
let privateEnv: Record<string, string>;
let defaultDefines: Record<string, string>;
let isDev: boolean;
@@ -173,27 +167,6 @@ export default function envVitePlugin({ settings, logger }: EnvPluginOptions): v
}
s.prepend(devImportMetaEnvPrepend);
- // EDGE CASE: We need to do a static replacement for `export const prerender` for `vite-plugin-scanner`
- // TODO: Remove in Astro 5
- let exportConstPrerenderStr: string | undefined;
- s.replace(exportConstPrerenderRe, (m, key) => {
- if (privateEnv[key] != null) {
- exportConstPrerenderStr = m;
- return `export const prerender = ${privateEnv[key]}`;
- } else {
- return m;
- }
- });
- if (exportConstPrerenderStr) {
- logger.warn(
- 'router',
- `Exporting dynamic values from prerender is deprecated. Please use an integration with the "astro:route:setup" hook ` +
- `to update the route's \`prerender\` option instead. This allows for better treeshaking and bundling configuration ` +
- `in the future. See https://docs.astro.build/en/reference/integrations-reference/#astroroutesetup for a migration example.` +
- `\nFound \`${bold(exportConstPrerenderStr)}\` in ${bold(id)}.`,
- );
- }
-
return {
code: s.toString(),
map: s.generateMap({ hires: 'boundary' }),
diff --git a/packages/astro/src/vite-plugin-scanner/index.ts b/packages/astro/src/vite-plugin-scanner/index.ts
index 5923377a4..b53decd2d 100644
--- a/packages/astro/src/vite-plugin-scanner/index.ts
+++ b/packages/astro/src/vite-plugin-scanner/index.ts
@@ -1,20 +1,17 @@
import { extname } from 'node:path';
+import { fileURLToPath } from 'node:url';
import { bold } from 'kleur/colors';
import type { Plugin as VitePlugin } from 'vite';
import { normalizePath } from 'vite';
import type { Logger } from '../core/logger/core.js';
-import { isEndpoint, isPage, isServerLikeOutput } from '../core/util.js';
+import { isEndpoint, isPage } from '../core/util.js';
import { rootRelativePath } from '../core/viteUtils.js';
-import { runHookRouteSetup } from '../integrations/hooks.js';
-import { getPrerenderDefault } from '../prerender/utils.js';
-import type { AstroSettings } from '../types/astro.js';
-import type { RouteOptions } from '../types/public/integrations.js';
-import type { PageOptions } from '../vite-plugin-astro/types.js';
-import { scan } from './scan.js';
+import type { AstroSettings, ManifestData } from '../types/astro.js';
export interface AstroPluginScannerOptions {
settings: AstroSettings;
logger: Logger;
+ manifest: ManifestData;
}
const KNOWN_FILE_EXTENSIONS = ['.astro', '.js', '.ts'];
@@ -22,6 +19,7 @@ const KNOWN_FILE_EXTENSIONS = ['.astro', '.js', '.ts'];
export default function astroScannerPlugin({
settings,
logger,
+ manifest,
}: AstroPluginScannerOptions): VitePlugin {
return {
name: 'astro:scanner',
@@ -42,12 +40,19 @@ export default function astroScannerPlugin({
const fileIsPage = isPage(fileURL, settings);
const fileIsEndpoint = isEndpoint(fileURL, settings);
if (!(fileIsPage || fileIsEndpoint)) return;
- const pageOptions = await getPageOptions(code, id, fileURL, settings, logger);
+
+ const route = manifest.routes.find((r) => {
+ const filePath = new URL(`./${r.component}`, settings.config.root);
+ return normalizePath(fileURLToPath(filePath)) === filename;
+ });
+
+ if (!route) {
+ return;
+ }
// `getStaticPaths` warning is just a string check, should be good enough for most cases
if (
- !pageOptions.prerender &&
- isServerLikeOutput(settings.config) &&
+ !route.prerender &&
code.includes('getStaticPaths') &&
// this should only be valid for `.astro`, `.js` and `.ts` files
KNOWN_FILE_EXTENSIONS.includes(extname(filename))
@@ -68,36 +73,12 @@ export default function astroScannerPlugin({
...meta,
astro: {
...(meta.astro ?? { hydratedComponents: [], clientOnlyComponents: [], scripts: [] }),
- pageOptions,
+ pageOptions: {
+ prerender: route.prerender,
+ },
},
},
};
},
};
}
-
-async function getPageOptions(
- code: string,
- id: string,
- fileURL: URL,
- settings: AstroSettings,
- logger: Logger,
-): Promise<PageOptions> {
- // Run initial scan
- const pageOptions = await scan(code, id, settings);
-
- // Run integration hooks to alter page options
- const route: RouteOptions = {
- component: rootRelativePath(settings.config.root, fileURL, false),
- prerender: pageOptions.prerender,
- };
- await runHookRouteSetup({ route, settings, logger });
- pageOptions.prerender = route.prerender;
-
- // Fallback if unset
- if (typeof pageOptions.prerender === 'undefined') {
- pageOptions.prerender = getPrerenderDefault(settings.config);
- }
-
- return pageOptions;
-}
diff --git a/packages/astro/src/vite-plugin-scanner/scan.ts b/packages/astro/src/vite-plugin-scanner/scan.ts
deleted file mode 100644
index 35e3de20a..000000000
--- a/packages/astro/src/vite-plugin-scanner/scan.ts
+++ /dev/null
@@ -1,92 +0,0 @@
-import type { AstroSettings } from '../types/astro.js';
-import type { PageOptions } from '../vite-plugin-astro/types.js';
-
-import * as eslexer from 'es-module-lexer';
-import { AstroError, AstroErrorData } from '../core/errors/index.js';
-
-const BOOLEAN_EXPORTS = new Set(['prerender']);
-
-// Quick scan to determine if code includes recognized export
-// False positives are not a problem, so be forgiving!
-function includesExport(code: string) {
- for (const name of BOOLEAN_EXPORTS) {
- if (code.includes(name)) return true;
- }
- return false;
-}
-
-// Support quoted values to allow statically known `import.meta.env` variables to be used
-function isQuoted(value: string) {
- return (value[0] === '"' || value[0] === "'") && value[value.length - 1] === value[0];
-}
-
-function isTruthy(value: string) {
- if (isQuoted(value)) {
- value = value.slice(1, -1);
- }
- return value === 'true' || value === '1';
-}
-
-function isFalsy(value: string) {
- if (isQuoted(value)) {
- value = value.slice(1, -1);
- }
- return value === 'false' || value === '0';
-}
-
-let didInit = false;
-
-export async function scan(
- code: string,
- id: string,
- settings?: AstroSettings,
-): Promise<PageOptions> {
- if (!includesExport(code)) return {};
- if (!didInit) {
- await eslexer.init;
- didInit = true;
- }
-
- const [, exports] = eslexer.parse(code, id);
-
- let pageOptions: PageOptions = {};
- for (const _export of exports) {
- const { n: name, le: endOfLocalName } = _export;
- // mark that a `prerender` export was found
- if (BOOLEAN_EXPORTS.has(name)) {
- // For a given export, check the value of the local declaration
- // Basically extract the `const` from the statement `export const prerender = true`
- const prefix = code
- .slice(0, endOfLocalName)
- .split('export')
- .pop()!
- .trim()
- .replace('prerender', '')
- .trim();
- // For a given export, check the value of the first non-whitespace token.
- // Basically extract the `true` from the statement `export const prerender = true`
- const suffix = code
- .slice(endOfLocalName)
- .trim()
- .replace(/=/, '')
- .trim()
- .split(/[;\n\r]/)[0]
- .trim();
- if (prefix !== 'const' || !(isTruthy(suffix) || isFalsy(suffix))) {
- throw new AstroError({
- ...AstroErrorData.InvalidPrerenderExport,
- message: AstroErrorData.InvalidPrerenderExport.message(
- prefix,
- suffix,
- settings?.config.output === 'hybrid',
- ),
- location: { file: id },
- });
- } else {
- pageOptions[name as keyof PageOptions] = isTruthy(suffix);
- }
- }
- }
-
- return pageOptions;
-}
diff --git a/packages/astro/test/astro-global.test.js b/packages/astro/test/astro-global.test.js
index 2b400e87f..c996de436 100644
--- a/packages/astro/test/astro-global.test.js
+++ b/packages/astro/test/astro-global.test.js
@@ -66,11 +66,6 @@ describe('Astro Global', () => {
let $ = cheerio.load(html);
assert.match($('#prerender').text(), /Astro route prerender: true/);
assert.match($('#prerender-middleware').text(), /Astro route prerender middleware: true/);
-
- html = await fixture.fetch('/blog/about', {}).then((res) => res.text());
- $ = cheerio.load(html);
- assert.match($('#prerender').text(), /Astro route prerender: false/);
- assert.match($('#prerender-middleware').text(), /Astro route prerender middleware: false/);
});
});
diff --git a/packages/astro/test/build-assets.test.js b/packages/astro/test/build-assets.test.js
index b33b0a0d1..4507345da 100644
--- a/packages/astro/test/build-assets.test.js
+++ b/packages/astro/test/build-assets.test.js
@@ -99,7 +99,7 @@ describe('build assets (server)', () => {
fixture = await loadFixture({
root: './fixtures/build-assets/',
integrations: [preact()],
- adapter: testAdapter(),
+ adapter: testAdapter({ extendAdapter: { adapterFeatures: { forceServerOutput: false } } }),
// test suite was authored when inlineStylesheets defaulted to never
build: { inlineStylesheets: 'never' },
});
@@ -148,7 +148,11 @@ describe('build assets (server)', () => {
assets: 'custom-assets',
inlineStylesheets: 'never',
},
- adapter: testAdapter(),
+ adapter: testAdapter({extendAdapter: {
+ adapterFeatures: {
+ forceServerOutput: false,
+ }
+ }}),
});
await fixture.build();
});
diff --git a/packages/astro/test/fixtures/astro-dev-http2/astro.config.ts b/packages/astro/test/fixtures/astro-dev-http2/astro.config.ts
index b02d07ce5..3b6406ec5 100644
--- a/packages/astro/test/fixtures/astro-dev-http2/astro.config.ts
+++ b/packages/astro/test/fixtures/astro-dev-http2/astro.config.ts
@@ -2,7 +2,7 @@ import { defineConfig } from "astro/config";
import { readFileSync } from "fs";
// https://astro.build/config
export default defineConfig({
- output: "hybrid",
+ output: "static",
vite: {
server: {
https: {
diff --git a/packages/astro/test/fixtures/astro-global/src/pages/about.astro b/packages/astro/test/fixtures/astro-global/src/pages/about.astro
index edafce0d2..c54179032 100644
--- a/packages/astro/test/fixtures/astro-global/src/pages/about.astro
+++ b/packages/astro/test/fixtures/astro-global/src/pages/about.astro
@@ -1,7 +1,5 @@
---
-import Route from '../components/Route.astro'
-
-export const prerender = false
+import Route from '../components/Route.astro';
---
<html>
<head>
diff --git a/packages/astro/test/fixtures/server-islands/hybrid/astro.config.mjs b/packages/astro/test/fixtures/server-islands/hybrid/astro.config.mjs
index 70d0e6d6a..b01b674f5 100644
--- a/packages/astro/test/fixtures/server-islands/hybrid/astro.config.mjs
+++ b/packages/astro/test/fixtures/server-islands/hybrid/astro.config.mjs
@@ -2,7 +2,7 @@ import svelte from '@astrojs/svelte';
import { defineConfig } from 'astro/config';
export default defineConfig({
- output: 'hybrid',
+ output: 'static',
integrations: [
svelte()
],
@@ -10,4 +10,3 @@ export default defineConfig({
serverIslands: true,
}
});
-
diff --git a/packages/astro/test/fixtures/streaming/src/pages/index.astro b/packages/astro/test/fixtures/streaming/src/pages/index.astro
index dd680eba7..c40dfe95c 100644
--- a/packages/astro/test/fixtures/streaming/src/pages/index.astro
+++ b/packages/astro/test/fixtures/streaming/src/pages/index.astro
@@ -3,6 +3,8 @@ import AsyncEach from '../components/AsyncEach.astro';
import Header from '../components/Header.astro';
import { wait } from '../wait';
+export const prerender = false;
+
async function * list() {
const nums = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
for(const num of nums) {
diff --git a/packages/astro/test/fixtures/streaming/src/pages/multiple-errors.astro b/packages/astro/test/fixtures/streaming/src/pages/multiple-errors.astro
index 7d922ef0d..0bfefd295 100644
--- a/packages/astro/test/fixtures/streaming/src/pages/multiple-errors.astro
+++ b/packages/astro/test/fixtures/streaming/src/pages/multiple-errors.astro
@@ -1,6 +1,8 @@
---
import ReactComp from '../components/react.tsx';
+export const prerender = false;
+
const foo = { bar: null } as any;
---
<ReactComp foo={foo} />
diff --git a/packages/astro/test/fixtures/streaming/src/pages/slot.astro b/packages/astro/test/fixtures/streaming/src/pages/slot.astro
index 8c41d3a5b..a4f13e4d2 100644
--- a/packages/astro/test/fixtures/streaming/src/pages/slot.astro
+++ b/packages/astro/test/fixtures/streaming/src/pages/slot.astro
@@ -1,6 +1,8 @@
---
import BareComponent from '../components/BareComponent.astro';
import Wait from '../components/Wait.astro';
+
+export const prerender = false;
---
<html>
diff --git a/packages/astro/test/i18n-routing.test.js b/packages/astro/test/i18n-routing.test.js
index ddb31762f..e0413377d 100644
--- a/packages/astro/test/i18n-routing.test.js
+++ b/packages/astro/test/i18n-routing.test.js
@@ -1763,7 +1763,7 @@ describe('[SSR] i18n routing', () => {
before(async () => {
fixture = await loadFixture({
root: './fixtures/i18n-routing-prefix-always/',
- output: 'hybrid',
+ output: 'static',
adapter: testAdapter(),
});
await fixture.build();
diff --git a/packages/astro/test/ssr-prerender-get-static-paths.test.js b/packages/astro/test/ssr-prerender-get-static-paths.test.js
index 50b403891..8ac65899d 100644
--- a/packages/astro/test/ssr-prerender-get-static-paths.test.js
+++ b/packages/astro/test/ssr-prerender-get-static-paths.test.js
@@ -137,7 +137,7 @@ describe('Prerender', () => {
});
});
- describe('output: "hybrid"', () => {
+ describe('output: "static" with server output', () => {
describe('getStaticPaths - build calls', () => {
before(async () => {
fixture = await loadFixture({
@@ -145,7 +145,7 @@ describe('Prerender', () => {
site: 'https://mysite.dev/',
adapter: testAdapter(),
base: '/blog',
- output: 'hybrid',
+ output: 'static',
vite: {
plugins: [vitePluginRemovePrerenderExport()],
},
diff --git a/packages/astro/test/test-adapter.js b/packages/astro/test/test-adapter.js
index 4a2ac65d0..fc3923719 100644
--- a/packages/astro/test/test-adapter.js
+++ b/packages/astro/test/test-adapter.js
@@ -113,6 +113,9 @@ export default function ({
assets: 'stable',
i18nDomains: 'stable',
},
+ adapterFeatures: {
+ forceServerOutput: true,
+ },
...extendAdapter,
});
},
diff --git a/packages/astro/test/underscore-in-folder-name.test.js b/packages/astro/test/underscore-in-folder-name.test.js
index 622ba977d..b804a715c 100644
--- a/packages/astro/test/underscore-in-folder-name.test.js
+++ b/packages/astro/test/underscore-in-folder-name.test.js
@@ -9,7 +9,7 @@ describe('Projects with a underscore in the folder name', () => {
before(async () => {
fixture = await loadFixture({
root: './fixtures/_underscore in folder name/',
- output: 'hybrid',
+ output: 'static',
adapter: testAdapter(),
});
await fixture.build();
diff --git a/packages/astro/test/units/integrations/api.test.js b/packages/astro/test/units/integrations/api.test.js
index f3dc8f159..4aef593be 100644
--- a/packages/astro/test/units/integrations/api.test.js
+++ b/packages/astro/test/units/integrations/api.test.js
@@ -137,7 +137,7 @@ describe('Astro feature map', function () {
hybridOutput: 'stable',
},
{
- output: 'hybrid',
+ config: { output: 'static' },
},
{},
defaultLogger,
@@ -150,7 +150,8 @@ describe('Astro feature map', function () {
'test',
{},
{
- output: 'hybrid',
+ buildOutput: 'server',
+ config: { output: 'static' },
},
{},
defaultLogger,
@@ -163,7 +164,8 @@ describe('Astro feature map', function () {
'test',
{},
{
- output: 'hybrid',
+ buildOutput: 'server',
+ config: { output: 'static' },
},
{},
defaultLogger,
@@ -177,7 +179,7 @@ describe('Astro feature map', function () {
'test',
{ staticOutput: 'stable' },
{
- output: 'static',
+ config: { output: 'static' },
},
{},
defaultLogger,
@@ -190,7 +192,8 @@ describe('Astro feature map', function () {
'test',
{ staticOutput: 'unsupported' },
{
- output: 'static',
+ buildOutput: 'static',
+ config: { output: 'static' },
},
{},
defaultLogger,
@@ -204,7 +207,7 @@ describe('Astro feature map', function () {
'test',
{ hybridOutput: 'stable' },
{
- output: 'hybrid',
+ config: { output: 'static' },
},
{},
defaultLogger,
@@ -219,7 +222,8 @@ describe('Astro feature map', function () {
hybridOutput: 'unsupported',
},
{
- output: 'hybrid',
+ buildOutput: 'server',
+ config: { output: 'static' },
},
{},
defaultLogger,
@@ -233,7 +237,7 @@ describe('Astro feature map', function () {
'test',
{ serverOutput: 'stable' },
{
- output: 'server',
+ config: { output: 'server' },
},
{},
defaultLogger,
@@ -248,7 +252,7 @@ describe('Astro feature map', function () {
serverOutput: 'unsupported',
},
{
- output: 'server',
+ config: { output: 'server' },
},
{},
defaultLogger,
@@ -268,9 +272,11 @@ describe('Astro feature map', function () {
},
},
{
- image: {
- service: {
- entrypoint: 'astro/assets/services/sharp',
+ config: {
+ image: {
+ service: {
+ entrypoint: 'astro/assets/services/sharp',
+ },
},
},
},
@@ -290,9 +296,11 @@ describe('Astro feature map', function () {
},
},
{
- image: {
- service: {
- entrypoint: 'astro/assets/services/sharp',
+ config: {
+ image: {
+ service: {
+ entrypoint: 'astro/assets/services/sharp',
+ },
},
},
},
diff --git a/packages/astro/test/units/routing/manifest.test.js b/packages/astro/test/units/routing/manifest.test.js
index 518a2fc8f..856679a71 100644
--- a/packages/astro/test/units/routing/manifest.test.js
+++ b/packages/astro/test/units/routing/manifest.test.js
@@ -52,7 +52,7 @@ describe('routing - createRouteManifest', () => {
base: '/search',
trailingSlash: 'never',
});
- const manifest = createRouteManifest({
+ const manifest = await createRouteManifest({
cwd: fileURLToPath(root),
settings,
fsMod: fs,
@@ -67,6 +67,8 @@ describe('routing - createRouteManifest', () => {
{
'/src/pages/[contact].astro': `<h1>test</h1>`,
'/src/pages/[contact].ts': `<h1>test</h1>`,
+ '/src/entrypoint.astro': `<h1>test</h1>`,
+ '/src/entrypoint.ts': `<h1>test</h1>`,
},
root,
);
@@ -79,15 +81,15 @@ describe('routing - createRouteManifest', () => {
settings.injectedRoutes = [
{
pattern: '/about',
- entrypoint: '@lib/legacy/static.astro',
+ entrypoint: 'src/entrypoint.astro',
},
{
pattern: '/api',
- entrypoint: '@lib/legacy/static.ts',
+ entrypoint: 'src/entrypoint.ts',
},
];
- const manifest = createRouteManifest({
+ const manifest = await createRouteManifest({
cwd: fileURLToPath(root),
settings,
fsMod: fs,
@@ -130,7 +132,7 @@ describe('routing - createRouteManifest', () => {
trailingSlash: 'never',
});
- const manifest = createRouteManifest({
+ const manifest = await createRouteManifest({
cwd: fileURLToPath(root),
settings,
fsMod: fs,
@@ -167,7 +169,7 @@ describe('routing - createRouteManifest', () => {
trailingSlash: 'never',
});
- const manifest = createRouteManifest({
+ const manifest = await createRouteManifest({
cwd: fileURLToPath(root),
settings,
fsMod: fs,
@@ -213,7 +215,7 @@ describe('routing - createRouteManifest', () => {
trailingSlash: 'never',
});
- const manifest = createRouteManifest({
+ const manifest = await createRouteManifest({
cwd: fileURLToPath(root),
settings,
fsMod: fs,
@@ -255,6 +257,7 @@ describe('routing - createRouteManifest', () => {
{
'/src/pages/index.astro': `<h1>test</h1>`,
'/src/pages/blog/[...slug].astro': `<h1>test</h1>`,
+ '/src/entrypoint.astro': `<h1>test</h1>`,
},
root,
);
@@ -268,16 +271,16 @@ describe('routing - createRouteManifest', () => {
settings.injectedRoutes = [
{
pattern: '/contributing',
- entrypoint: '@lib/legacy/static.astro',
+ entrypoint: 'src/entrypoint.astro',
},
{
pattern: '/[...slug]',
- entrypoint: '@lib/legacy/dynamic.astro',
+ entrypoint: 'src/entrypoint.astro',
priority: 'normal',
},
];
- const manifest = createRouteManifest({
+ const manifest = await createRouteManifest({
cwd: fileURLToPath(root),
settings,
fsMod: fs,
@@ -327,7 +330,7 @@ describe('routing - createRouteManifest', () => {
},
},
});
- const manifest = createRouteManifest({
+ const manifest = await createRouteManifest({
cwd: fileURLToPath(root),
settings,
fsMod: fs,
@@ -357,6 +360,7 @@ describe('routing - createRouteManifest', () => {
const fs = createFs(
{
'/src/pages/contributing.astro': `<h1>test</h1>`,
+ '/src/entrypoint.astro': `<h1>test</h1>`,
},
root,
);
@@ -371,7 +375,7 @@ describe('routing - createRouteManifest', () => {
settings.injectedRoutes = [
{
pattern: '/contributing',
- entrypoint: '@lib/legacy/static.astro',
+ entrypoint: 'src/entrypoint.astro',
},
];
@@ -383,14 +387,14 @@ describe('routing - createRouteManifest', () => {
const { logger, logs } = getLogger();
- createRouteManifest(manifestOptions, logger);
+ await createRouteManifest(manifestOptions, logger);
assert.deepEqual(logs, [
{
label: 'router',
level: 'warn',
message:
- 'The route "/contributing" is defined in both "src/pages/contributing.astro" and "@lib/legacy/static.astro". A static route cannot be defined more than once.',
+ 'The route "/contributing" is defined in both "src/pages/contributing.astro" and "src/entrypoint.astro". A static route cannot be defined more than once.',
newLine: true,
},
{
@@ -426,7 +430,7 @@ describe('routing - createRouteManifest', () => {
const { logger, logs } = getLogger();
- createRouteManifest(manifestOptions, logger);
+ await createRouteManifest(manifestOptions, logger);
assert.deepEqual(logs, [
{
@@ -450,6 +454,7 @@ describe('routing - createRouteManifest', () => {
{
'/src/pages/a-[b].astro': `<h1>test</h1>`,
'/src/pages/blog/a-[b].233.ts': ``,
+ '/src/entrypoint.astro': `<h1>test</h1>`,
},
root,
);
@@ -467,12 +472,12 @@ describe('routing - createRouteManifest', () => {
settings.injectedRoutes = [
{
pattern: '/[c]-d',
- entrypoint: '@lib/legacy/dynamic.astro',
+ entrypoint: 'src/entrypoint.astro',
priority: 'normal',
},
];
- const manifest = createRouteManifest({
+ const manifest = await createRouteManifest({
cwd: fileURLToPath(root),
settings,
fsMod: fs,
diff --git a/packages/astro/test/units/routing/route-matching.test.js b/packages/astro/test/units/routing/route-matching.test.js
index a9a0e48a3..26e540e6e 100644
--- a/packages/astro/test/units/routing/route-matching.test.js
+++ b/packages/astro/test/units/routing/route-matching.test.js
@@ -135,7 +135,7 @@ describe('Route matching', () => {
settings = await createBasicSettings({
root: fileURLToPath(root),
trailingSlash: 'never',
- output: 'hybrid',
+ output: 'static',
adapter: testAdapter(),
});
container = await createContainer({
@@ -147,7 +147,7 @@ describe('Route matching', () => {
const loader = createViteLoader(container.viteServer);
const manifest = createDevelopmentManifest(container.settings);
pipeline = DevPipeline.create(undefined, { loader, logger: defaultLogger, manifest, settings });
- manifestData = createRouteManifest(
+ manifestData = await createRouteManifest(
{
cwd: fileURLToPath(root),
settings,
diff --git a/packages/astro/test/units/routing/route-sanitization.test.js b/packages/astro/test/units/routing/route-sanitization.test.js
index 802418868..45020d141 100644
--- a/packages/astro/test/units/routing/route-sanitization.test.js
+++ b/packages/astro/test/units/routing/route-sanitization.test.js
@@ -38,7 +38,7 @@ describe('Route sanitization', () => {
settings = await createBasicSettings({
root: fileURLToPath(root),
trailingSlash: 'never',
- output: 'hybrid',
+ output: 'static',
adapter: testAdapter(),
});
container = await createContainer({
diff --git a/packages/astro/test/units/vite-plugin-astro-server/request.test.js b/packages/astro/test/units/vite-plugin-astro-server/request.test.js
index 26f446cb1..3ec1e5223 100644
--- a/packages/astro/test/units/vite-plugin-astro-server/request.test.js
+++ b/packages/astro/test/units/vite-plugin-astro-server/request.test.js
@@ -50,7 +50,7 @@ describe('vite-plugin-astro-server', () => {
},
'/',
);
- const manifestData = createRouteManifest(
+ const manifestData = await createRouteManifest(
{
fsMod: fs,
settings: pipeline.settings,
diff --git a/packages/astro/test/units/vite-plugin-scanner/scan.test.js b/packages/astro/test/units/vite-plugin-scanner/scan.test.js
deleted file mode 100644
index 5dab2449d..000000000
--- a/packages/astro/test/units/vite-plugin-scanner/scan.test.js
+++ /dev/null
@@ -1,137 +0,0 @@
-import * as assert from 'node:assert/strict';
-import { describe, it } from 'node:test';
-import { scan } from '../../../dist/vite-plugin-scanner/scan.js';
-
-describe('astro scan', () => {
- it('should return empty object', async () => {
- const result = await scan(`export {}`, '/src/components/index.astro');
- assert.equal(Object.keys(result).length, 0);
- });
-
- it('recognizes constant boolean literal (false)', async () => {
- const result = await scan(`export const prerender = true;`, '/src/components/index.astro');
- assert.equal(result.prerender, true);
- });
-
- it('recognizes constant boolean literal (false)', async () => {
- const result = await scan(`export const prerender = false;`, '/src/components/index.astro');
- assert.equal(result.prerender, false);
- });
-
- it("recognizes single quoted boolean ('true')", async () => {
- const result = await scan(`export const prerender = 'true';`, '/src/components/index.astro');
- assert.equal(result.prerender, true);
- });
-
- it('recognizes double quoted boolean ("true")', async () => {
- const result = await scan(`export const prerender = "true";`, '/src/components/index.astro');
- assert.equal(result.prerender, true);
- });
-
- it('recognizes double quoted boolean ("false")', async () => {
- const result = await scan(`export const prerender = "false";`, '/src/components/index.astro');
- assert.equal(result.prerender, false);
- });
-
- it("recognizes single quoted boolean ('false')", async () => {
- const result = await scan(`export const prerender = 'false';`, '/src/components/index.astro');
- assert.equal(result.prerender, false);
- });
-
- it('recognizes number (1)', async () => {
- const result = await scan(`export const prerender = 1;`, '/src/components/index.astro');
- assert.equal(result.prerender, true);
- });
-
- it('recognizes number (0)', async () => {
- const result = await scan(`export const prerender = 0;`, '/src/components/index.astro');
- assert.equal(result.prerender, false);
- });
-
- it('throws on let boolean literal', async () => {
- try {
- await scan(`export let prerender = true;`, '/src/components/index.astro');
- assert.equal(false).to.be.true;
- } catch (e) {
- assert.equal(
- e.message.includes(
- `A \`prerender\` export has been detected, but its value cannot be statically analyzed.`,
- ),
- true,
- );
- }
- });
-
- it('throws on var boolean literal', async () => {
- try {
- await scan(`export var prerender = true;`, '/src/components/index.astro');
- assert.equal(false).to.be.true;
- } catch (e) {
- assert.equal(
- e.message.includes(
- `A \`prerender\` export has been detected, but its value cannot be statically analyzed.`,
- ),
- true,
- );
- }
- });
-
- it('throws on unknown values I', async () => {
- try {
- await scan(`export const prerender = !!value;`, '/src/components/index.astro');
- assert.equal(false).to.be.true;
- } catch (e) {
- assert.equal(
- e.message.includes(
- `A \`prerender\` export has been detected, but its value cannot be statically analyzed.`,
- ),
- true,
- );
- }
- });
-
- it('throws on unknown values II', async () => {
- try {
- await scan(`export const prerender = value;`, '/src/components/index.astro');
- assert.equal(false).to.be.true;
- } catch (e) {
- assert.equal(
- e.message.includes(
- `A \`prerender\` export has been detected, but its value cannot be statically analyzed.`,
- ),
- true,
- );
- }
- });
-
- it('throws on unknown values III', async () => {
- try {
- await scan(
- `export let prerender = undefined; prerender = true;`,
- '/src/components/index.astro',
- );
- assert.equal(false).to.be.true;
- } catch (e) {
- assert.equal(
- e.message.includes(
- `A \`prerender\` export has been detected, but its value cannot be statically analyzed.`,
- ),
- true,
- );
- }
- });
-
- it('throws on unknown values IV', async () => {
- try {
- await scan(`let prerender = true; export { prerender }`, '/src/components/index.astro');
- assert.equal(false).to.be.true;
- } catch (e) {
- assert.equal(
- e.message.includes(
- `A \`prerender\` export has been detected, but its value cannot be statically analyzed.`,
- ),
- true,
- );
- }
- });
-});
diff --git a/packages/db/src/core/integration/index.ts b/packages/db/src/core/integration/index.ts
index 2a5824c35..b51f98661 100644
--- a/packages/db/src/core/integration/index.ts
+++ b/packages/db/src/core/integration/index.ts
@@ -4,7 +4,7 @@ import { dirname } from 'node:path';
import { fileURLToPath } from 'node:url';
import type { ManagedAppToken } from '@astrojs/studio';
import { LibsqlError } from '@libsql/client';
-import type { AstroConfig, AstroIntegration } from 'astro';
+import type { AstroIntegration } from 'astro';
import { blue, yellow } from 'kleur/colors';
import {
type HMRPayload,
@@ -59,14 +59,13 @@ function astroDBIntegration(): AstroIntegration {
};
let command: 'dev' | 'build' | 'preview' | 'sync';
- let output: AstroConfig['output'] = 'server';
+ let finalBuildOutput: string;
return {
name: 'astro:db',
hooks: {
'astro:config:setup': async ({ updateConfig, config, command: _command, logger }) => {
command = _command;
root = config.root;
- output = config.output;
if (command === 'preview') return;
@@ -105,9 +104,11 @@ function astroDBIntegration(): AstroIntegration {
},
});
},
- 'astro:config:done': async ({ config, injectTypes }) => {
+ 'astro:config:done': async ({ config, injectTypes, buildOutput }) => {
if (command === 'preview') return;
+ finalBuildOutput = buildOutput;
+
// TODO: refine where we load tables
// @matthewp: may want to load tables by path at runtime
const { dbConfig, dependencies, integrationSeedPaths } = await resolveDbConfig(config);
@@ -159,11 +160,7 @@ function astroDBIntegration(): AstroIntegration {
}, 100);
},
'astro:build:start': async ({ logger }) => {
- if (
- !connectToRemote &&
- !databaseFileEnvDefined() &&
- (output === 'server' || output === 'hybrid')
- ) {
+ if (!connectToRemote && !databaseFileEnvDefined() && finalBuildOutput === 'server') {
const message = `Attempting to build without the --remote flag or the ASTRO_DATABASE_FILE environment variable defined. You probably want to pass --remote to astro build.`;
const hint =
'Learn more connecting to Studio: https://docs.astro.build/en/guides/astro-db/#connect-to-astro-studio';
diff --git a/packages/db/test/local-prod.test.js b/packages/db/test/local-prod.test.js
index 6513aeb08..9bd56dad0 100644
--- a/packages/db/test/local-prod.test.js
+++ b/packages/db/test/local-prod.test.js
@@ -71,7 +71,7 @@ describe('astro:db local database', () => {
it('should throw during the build for hybrid output', async () => {
let fixture2 = await loadFixture({
root: new URL('./fixtures/local-prod/', import.meta.url),
- output: 'hybrid',
+ output: 'static',
adapter: testAdapter(),
});
diff --git a/packages/integrations/web-vitals/src/index.ts b/packages/integrations/web-vitals/src/index.ts
index 02293ac6f..c74ab3261 100644
--- a/packages/integrations/web-vitals/src/index.ts
+++ b/packages/integrations/web-vitals/src/index.ts
@@ -19,14 +19,6 @@ export default function webVitals({ deprecated }: { deprecated?: boolean } = {})
);
}
- if (config.output !== 'hybrid' && config.output !== 'server') {
- throw new AstroError(
- 'No SSR adapter found.',
- '`@astrojs/web-vitals` requires your site to be built with `hybrid` or `server` output.\n' +
- 'Please add an SSR adapter: https://docs.astro.build/en/guides/server-side-rendering/',
- );
- }
-
// Middleware that adds a `<meta>` tag to each page.
addMiddleware({ entrypoint: '@astrojs/web-vitals/middleware', order: 'post' });
// Endpoint that collects metrics and inserts them in Astro DB.
diff --git a/packages/integrations/web-vitals/test/fixtures/basics/astro.config.mjs b/packages/integrations/web-vitals/test/fixtures/basics/astro.config.mjs
index 42bfa6f66..4dae98ae0 100644
--- a/packages/integrations/web-vitals/test/fixtures/basics/astro.config.mjs
+++ b/packages/integrations/web-vitals/test/fixtures/basics/astro.config.mjs
@@ -6,7 +6,7 @@ import { defineConfig } from 'astro/config';
// https://astro.build/config
export default defineConfig({
integrations: [db(), webVitals()],
- output: 'hybrid',
+ output: 'static',
adapter: node({ mode: 'standalone' }),
devToolbar: {
enabled: false,
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index ecfeac33a..dc6a79046 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -8752,12 +8752,10 @@ packages:
libsql@0.3.19:
resolution: {integrity: sha512-Aj5cQ5uk/6fHdmeW0TiXK42FqUlwx7ytmMLPSaUQPin5HKKKuUPD62MAbN4OEweGBBI7q1BekoEN4gPUEL6MZA==}
- cpu: [x64, arm64, wasm32]
os: [darwin, linux, win32]
libsql@0.4.1:
resolution: {integrity: sha512-qZlR9Yu1zMBeLChzkE/cKfoKV3Esp9cn9Vx5Zirn4AVhDWPcjYhKwbtJcMuHehgk3mH+fJr9qW+3vesBWbQpBg==}
- cpu: [x64, arm64, wasm32]
os: [darwin, linux, win32]
lilconfig@2.1.0: