Skip to content

Commit 50ca656

Browse files
authored
Merge output: hybrid and output: static (withastro#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
1 parent 5b4e3ab commit 50ca656

File tree

75 files changed

+437
-550
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

75 files changed

+437
-550
lines changed

.vscode/settings.json

+3-3
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
"editor.defaultFormatter": "biomejs.biome"
1616
},
1717
"editor.codeActionsOnSave": {
18-
"quickFix.biome": true,
19-
"source.fixAll.biome": true
20-
}
18+
"quickFix.biome": "explicit",
19+
"source.fixAll.biome": "explicit"
20+
}
2121
}

packages/astro/e2e/custom-client-directives.test.js

+7-1
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,13 @@ test.describe('Custom Client Directives - build server', () => {
4040

4141
test.beforeAll(async ({ astro }) => {
4242
await astro.build({
43-
adapter: testAdapter(),
43+
adapter: testAdapter({
44+
extendAdapter: {
45+
adapterFeatures: {
46+
forceServerOutput: false,
47+
},
48+
},
49+
}),
4450
});
4551
previewServer = await astro.preview();
4652
});

packages/astro/e2e/fixtures/actions-blog/astro.config.mjs

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import node from '@astrojs/node';
77
export default defineConfig({
88
site: 'https://example.com',
99
integrations: [db(), react()],
10-
output: 'hybrid',
10+
output: 'static',
1111
adapter: node({
1212
mode: 'standalone',
1313
}),

packages/astro/e2e/fixtures/actions-react-19/astro.config.mjs

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import node from '@astrojs/node';
77
export default defineConfig({
88
site: 'https://example.com',
99
integrations: [db(), react()],
10-
output: 'hybrid',
10+
output: 'static',
1111
adapter: node({
1212
mode: 'standalone',
1313
}),

packages/astro/e2e/fixtures/server-islands/astro.config.mjs

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import nodejs from '@astrojs/node';
66
// https://astro.build/config
77
export default defineConfig({
88
base: '/base',
9-
output: 'hybrid',
9+
output: 'static',
1010
adapter: nodejs({ mode: 'standalone' }),
1111
integrations: [react(), mdx()],
1212
trailingSlash: process.env.TRAILING_SLASH ?? 'always',

packages/astro/e2e/fixtures/server-islands/src/pages/index.astro

+2
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import HTMLError from '../components/HTMLError.astro';
55
import { generateLongText } from '../lorem';
66
77
const content = generateLongText(5);
8+
9+
export const prerender = false;
810
---
911

1012
<html>

packages/astro/e2e/fixtures/view-transitions/astro.config.mjs

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { defineConfig } from 'astro/config';
66

77
// https://astro.build/config
88
export default defineConfig({
9-
output: 'hybrid',
9+
output: 'static',
1010
adapter: nodejs({ mode: 'standalone' }),
1111
integrations: [react(),vue(),svelte()],
1212
redirects: {

packages/astro/package.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,8 @@
118118
"test:e2e:chrome": "playwright test",
119119
"test:e2e:firefox": "playwright test --config playwright.firefox.config.js",
120120
"test:types": "tsc --project tsconfig.tests.json",
121-
"test:node": "astro-scripts test \"test/**/*.test.js\""
121+
"test:node": "astro-scripts test \"test/**/*.test.js\"",
122+
"test:units": "astro-scripts test \"test/**/units/**/*.test.js\""
122123
},
123124
"dependencies": {
124125
"@astrojs/compiler": "^2.10.3",

packages/astro/src/actions/integration.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { ActionsWithoutServerOutputError } from '../core/errors/errors-data.js';
22
import { AstroError } from '../core/errors/errors.js';
3-
import { isServerLikeOutput, viteID } from '../core/util.js';
3+
import { viteID } from '../core/util.js';
44
import type { AstroSettings } from '../types/astro.js';
55
import type { AstroIntegration } from '../types/public/integrations.js';
66
import { ACTIONS_TYPES_FILE, VIRTUAL_MODULE_ID } from './consts.js';
@@ -30,7 +30,7 @@ export default function astroIntegrationActionsRouteHandler({
3030
});
3131
},
3232
'astro:config:done': async (params) => {
33-
if (!isServerLikeOutput(params.config)) {
33+
if (params.buildOutput === 'static') {
3434
const error = new AstroError(ActionsWithoutServerOutputError);
3535
error.stack = undefined;
3636
throw error;

packages/astro/src/assets/build/generate.ts

+4-4
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ import { AstroError } from '../../core/errors/errors.js';
99
import { AstroErrorData } from '../../core/errors/index.js';
1010
import type { Logger } from '../../core/logger/core.js';
1111
import { isRemotePath, removeLeadingForwardSlash } from '../../core/path.js';
12-
import { isServerLikeOutput } from '../../core/util.js';
1312
import type { MapValue } from '../../type-utils.js';
1413
import type { AstroConfig } from '../../types/public/config.js';
1514
import { getConfiguredImageService } from '../internal.js';
@@ -50,7 +49,7 @@ export async function prepareAssetsGenerationEnv(
5049
pipeline: BuildPipeline,
5150
totalCount: number,
5251
): Promise<AssetEnv> {
53-
const { config, logger } = pipeline;
52+
const { config, logger, settings } = pipeline;
5453
let useCache = true;
5554
const assetsCacheDir = new URL('assets/', config.cacheDir);
5655
const count = { total: totalCount, current: 1 };
@@ -66,8 +65,9 @@ export async function prepareAssetsGenerationEnv(
6665
useCache = false;
6766
}
6867

68+
const isServerOutput = settings.buildOutput === 'server';
6969
let serverRoot: URL, clientRoot: URL;
70-
if (isServerLikeOutput(config)) {
70+
if (isServerOutput) {
7171
serverRoot = config.build.server;
7272
clientRoot = config.build.client;
7373
} else {
@@ -77,7 +77,7 @@ export async function prepareAssetsGenerationEnv(
7777

7878
return {
7979
logger,
80-
isSSR: isServerLikeOutput(config),
80+
isSSR: isServerOutput,
8181
count,
8282
useCache,
8383
assetsCacheDir,
+40-8
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,47 @@
1-
import type { AstroSettings } from '../../types/astro.js';
1+
import { resolveInjectedRoute } from '../../core/routing/manifest/create.js';
2+
import type { AstroSettings, ManifestData } from '../../types/astro.js';
3+
import type { RouteData } from '../../types/public/internal.js';
24

3-
export function injectImageEndpoint(settings: AstroSettings, mode: 'dev' | 'build') {
5+
export function injectImageEndpoint(
6+
settings: AstroSettings,
7+
manifest: ManifestData,
8+
mode: 'dev' | 'build',
9+
cwd?: string,
10+
) {
11+
manifest.routes.push(getImageEndpointData(settings, mode, cwd));
12+
}
13+
14+
export function ensureImageEndpointRoute(
15+
settings: AstroSettings,
16+
manifest: ManifestData,
17+
mode: 'dev' | 'build',
18+
cwd?: string,
19+
) {
20+
if (!manifest.routes.some((route) => route.route === '/_image')) {
21+
manifest.routes.push(getImageEndpointData(settings, mode, cwd));
22+
}
23+
}
24+
25+
function getImageEndpointData(
26+
settings: AstroSettings,
27+
mode: 'dev' | 'build',
28+
cwd?: string,
29+
): RouteData {
430
const endpointEntrypoint =
531
settings.config.image.endpoint ??
632
(mode === 'dev' ? 'astro/assets/endpoint/node' : 'astro/assets/endpoint/generic');
733

8-
settings.injectedRoutes.push({
9-
pattern: '/_image',
10-
entrypoint: endpointEntrypoint,
34+
return {
35+
type: 'endpoint',
36+
isIndex: false,
37+
route: '/_image',
38+
pattern: /^\/_image$/,
39+
segments: [[{ content: '_image', dynamic: false, spread: false }]],
40+
params: [],
41+
component: resolveInjectedRoute(endpointEntrypoint, settings.config.root, cwd).component,
42+
generate: () => '',
43+
pathname: '/_image',
1144
prerender: false,
12-
});
13-
14-
return settings;
45+
fallbackRoutes: [],
46+
};
1547
}

packages/astro/src/assets/vite-plugin-assets.ts

+2-3
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ import {
1010
removeBase,
1111
removeQueryString,
1212
} from '../core/path.js';
13-
import { isServerLikeOutput } from '../core/util.js';
1413
import type { AstroPluginOptions, AstroSettings } from '../types/astro.js';
1514
import { VALID_INPUT_FORMATS, VIRTUAL_MODULE_ID, VIRTUAL_SERVICE_ID } from './consts.js';
1615
import type { ImageTransform } from './types.js';
@@ -131,7 +130,7 @@ export default function assets({
131130
// so that it's tree-shaken away for all platforms that don't need it.
132131
export const outDir = /* #__PURE__ */ new URL(${JSON.stringify(
133132
new URL(
134-
isServerLikeOutput(settings.config)
133+
settings.buildOutput === 'server'
135134
? settings.config.build.client
136135
: settings.config.outDir,
137136
),
@@ -222,7 +221,7 @@ export default function assets({
222221
if (options?.ssr) {
223222
return `export default ${getProxyCode(
224223
imageMetadata,
225-
isServerLikeOutput(settings.config),
224+
settings.buildOutput === 'server',
226225
)}`;
227226
} else {
228227
globalThis.astroAsset.referencedImages.add(imageMetadata.fsPath);

packages/astro/src/config/index.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import type { UserConfig as ViteUserConfig } from 'vite';
22
import { Logger } from '../core/logger/core.js';
3+
import { createRouteManifest } from '../core/routing/index.js';
34
import type { AstroInlineConfig, AstroUserConfig } from '../types/public/config.js';
45

56
export function defineConfig(config: AstroUserConfig) {
@@ -40,6 +41,7 @@ export function getViteConfig(
4041
const { astroConfig: config } = await resolveConfig(inlineAstroConfig, cmd);
4142
let settings = await createSettings(config, userViteConfig.root);
4243
settings = await runHookConfigSetup({ settings, command: cmd, logger });
44+
const manifest = await createRouteManifest({ settings }, logger);
4345
const viteConfig = await createVite(
4446
{
4547
mode,
@@ -48,7 +50,7 @@ export function getViteConfig(
4850
astroContentListenPlugin({ settings, logger, fs }),
4951
],
5052
},
51-
{ settings, logger, mode, sync: false },
53+
{ settings, logger, mode, sync: false, manifest },
5254
);
5355
await runHookConfigDone({ settings, logger });
5456
return mergeConfig(viteConfig, userViteConfig);

packages/astro/src/content/vite-plugin-content-imports.ts

+2-3
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import { getProxyCode } from '../assets/utils/proxy.js';
88
import { AstroError } from '../core/errors/errors.js';
99
import { AstroErrorData } from '../core/errors/index.js';
1010
import type { Logger } from '../core/logger/core.js';
11-
import { isServerLikeOutput } from '../core/util.js';
1211
import type { AstroSettings } from '../types/astro.js';
1312
import type { AstroConfig } from '../types/public/config.js';
1413
import type {
@@ -115,7 +114,7 @@ export function astroContentImportPlugin({
115114
const code = `
116115
export const id = ${JSON.stringify(id)};
117116
export const collection = ${JSON.stringify(collection)};
118-
export const data = ${stringifyEntryData(data, isServerLikeOutput(settings.config))};
117+
export const data = ${stringifyEntryData(data, settings.buildOutput === 'server')};
119118
export const _internal = {
120119
type: 'data',
121120
filePath: ${JSON.stringify(_internal.filePath)},
@@ -140,7 +139,7 @@ export const _internal = {
140139
export const collection = ${JSON.stringify(collection)};
141140
export const slug = ${JSON.stringify(slug)};
142141
export const body = ${JSON.stringify(body)};
143-
export const data = ${stringifyEntryData(data, isServerLikeOutput(settings.config))};
142+
export const data = ${stringifyEntryData(data, settings.buildOutput === 'server')};
144143
export const _internal = {
145144
type: 'content',
146145
filePath: ${JSON.stringify(_internal.filePath)},

packages/astro/src/content/vite-plugin-content-virtual-mod.ts

+1-2
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import type { Plugin } from 'vite';
88
import { encodeName } from '../core/build/util.js';
99
import { AstroError, AstroErrorData } from '../core/errors/index.js';
1010
import { appendForwardSlash, removeFileExtension } from '../core/path.js';
11-
import { isServerLikeOutput } from '../core/util.js';
1211
import { rootRelativePath } from '../core/viteUtils.js';
1312
import type { AstroSettings } from '../types/astro.js';
1413
import type { AstroPluginMetadata } from '../vite-plugin-astro/index.js';
@@ -53,7 +52,7 @@ export function astroContentVirtualModPlugin({
5352
fs,
5453
}: AstroContentVirtualModPluginParams): Plugin {
5554
let IS_DEV = false;
56-
const IS_SERVER = isServerLikeOutput(settings.config);
55+
const IS_SERVER = settings.buildOutput === 'server';
5756
let dataStoreFile: URL;
5857
return {
5958
name: 'astro-content-virtual-mod-plugin',

packages/astro/src/core/build/common.ts

+8-7
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,27 @@
11
import npath from 'node:path';
22
import { fileURLToPath, pathToFileURL } from 'node:url';
33
import { appendForwardSlash } from '../../core/path.js';
4+
import type { AstroSettings } from '../../types/astro.js';
45
import type { AstroConfig } from '../../types/public/config.js';
56
import type { RouteData } from '../../types/public/internal.js';
67

78
const STATUS_CODE_PAGES = new Set(['/404', '/500']);
89
const FALLBACK_OUT_DIR_NAME = './.astro/';
910

10-
function getOutRoot(astroConfig: AstroConfig): URL {
11-
if (astroConfig.output === 'static') {
12-
return new URL('./', astroConfig.outDir);
11+
function getOutRoot(astroSettings: AstroSettings): URL {
12+
if (astroSettings.buildOutput === 'static') {
13+
return new URL('./', astroSettings.config.outDir);
1314
} else {
14-
return new URL('./', astroConfig.build.client);
15+
return new URL('./', astroSettings.config.build.client);
1516
}
1617
}
1718

1819
export function getOutFolder(
19-
astroConfig: AstroConfig,
20+
astroSettings: AstroSettings,
2021
pathname: string,
2122
routeData: RouteData,
2223
): URL {
23-
const outRoot = getOutRoot(astroConfig);
24+
const outRoot = getOutRoot(astroSettings);
2425
const routeType = routeData.type;
2526

2627
// This is the root folder to write to.
@@ -30,7 +31,7 @@ export function getOutFolder(
3031
case 'fallback':
3132
case 'page':
3233
case 'redirect':
33-
switch (astroConfig.build.format) {
34+
switch (astroSettings.config.build.format) {
3435
case 'directory': {
3536
if (STATUS_CODE_PAGES.has(pathname)) {
3637
return new URL('.' + appendForwardSlash(npath.dirname(pathname)), outRoot);

packages/astro/src/core/build/generate.ts

+5-5
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ import { callGetStaticPaths } from '../render/route-cache.js';
3535
import { createRequest } from '../request.js';
3636
import { matchRoute } from '../routing/match.js';
3737
import { stringifyParams } from '../routing/params.js';
38-
import { getOutputFilename, isServerLikeOutput } from '../util.js';
38+
import { getOutputFilename } from '../util.js';
3939
import { getOutFile, getOutFolder } from './common.js';
4040
import { cssOrder, mergeInlineCss } from './internal.js';
4141
import { BuildPipeline } from './pipeline.js';
@@ -49,12 +49,12 @@ import { getTimeStat, shouldAppendForwardSlash } from './util.js';
4949

5050
export async function generatePages(options: StaticBuildOptions, internals: BuildInternals) {
5151
const generatePagesTimer = performance.now();
52-
const ssr = isServerLikeOutput(options.settings.config);
52+
const ssr = options.settings.buildOutput === 'server';
5353
let manifest: SSRManifest;
5454
if (ssr) {
5555
manifest = await BuildPipeline.retrieveManifest(options, internals);
5656
} else {
57-
const baseDirectory = getOutputDirectory(options.settings.config);
57+
const baseDirectory = getOutputDirectory(options.settings);
5858
const renderersEntryUrl = new URL('renderers.mjs', baseDirectory);
5959
const renderers = await import(renderersEntryUrl.toString());
6060
let middleware: MiddlewareHandler = (_, next) => next();
@@ -138,7 +138,7 @@ export async function generatePages(options: StaticBuildOptions, internals: Buil
138138
delete globalThis?.astroAsset?.addStaticImage;
139139
}
140140

141-
await runHookBuildGenerated({ config, logger });
141+
await runHookBuildGenerated({ settings: options.settings, logger });
142142
}
143143

144144
const THRESHOLD_SLOW_RENDER_TIME_MS = 500;
@@ -466,7 +466,7 @@ async function generatePath(
466466
body = Buffer.from(await response.arrayBuffer());
467467
}
468468

469-
const outFolder = getOutFolder(config, pathname, route);
469+
const outFolder = getOutFolder(pipeline.settings, pathname, route);
470470
const outFile = getOutFile(config, outFolder, pathname, route);
471471
route.distURL = outFile;
472472

0 commit comments

Comments
 (0)