Revert "feat(hybrid): unflag hybrid output (#7255)" (#7259)

This reverts commit bc5d6ed39f.
This commit is contained in:
Nate Moore 2023-05-31 12:06:24 -05:00 committed by GitHub
parent 4e653a4d29
commit e0ca0d8c8a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
29 changed files with 139 additions and 65 deletions

View file

@ -1,10 +0,0 @@
---
'astro': minor
'@astrojs/image': minor
'@astrojs/cloudflare': patch
'@astrojs/netlify': patch
'@astrojs/node': patch
'@astrojs/vercel': patch
---
Unflags support for `output: 'hybrid'` mode, which enables pre-rendering by default. The additional `experimental.hybridOutput` flag can be safely removed from your configuration.

View file

@ -1141,6 +1141,44 @@ export interface AstroUserConfig {
* ``` * ```
*/ */
middleware?: boolean; middleware?: boolean;
/**
* @docs
* @name experimental.hybridOutput
* @type {boolean}
* @default `false`
* @version 2.5.0
* @description
* Enable experimental support for hybrid SSR with pre-rendering enabled by default.
*
* To enable this feature, first set `experimental.hybridOutput` to `true` in your Astro config, and set `output` to `hybrid`.
*
* ```js
* {
* output: 'hybrid',
* experimental: {
* hybridOutput: true,
* },
* }
* ```
* Then add `export const prerender = false` to any page or endpoint you want to opt-out of pre-rendering.
* ```astro
* ---
* // pages/contact.astro
* export const prerender = false
*
* if (Astro.request.method === 'POST') {
* // handle form submission
* }
* ---
* <form method="POST">
* <input type="text" name="name" />
* <input type="email" name="email" />
* <button type="submit">Submit</button>
* </form>
* ```
*/
hybridOutput?: boolean;
}; };
// Legacy options to be removed // Legacy options to be removed

View file

@ -3,7 +3,7 @@ import { basename, join } from 'node:path/posix';
import type { StaticBuildOptions } from '../core/build/types.js'; import type { StaticBuildOptions } from '../core/build/types.js';
import { warn } from '../core/logger/core.js'; import { warn } from '../core/logger/core.js';
import { prependForwardSlash } from '../core/path.js'; import { prependForwardSlash } from '../core/path.js';
import { isServerLikeOutput } from '../prerender/utils.js'; import { isHybridOutput } from '../prerender/utils.js';
import { getConfiguredImageService, isESMImportedImage } from './internal.js'; import { getConfiguredImageService, isESMImportedImage } from './internal.js';
import type { LocalImageService } from './services/service.js'; import type { LocalImageService } from './services/service.js';
import type { ImageTransform } from './types.js'; import type { ImageTransform } from './types.js';
@ -47,7 +47,7 @@ export async function generateImage(
} }
let serverRoot: URL, clientRoot: URL; let serverRoot: URL, clientRoot: URL;
if (isServerLikeOutput(buildOpts.settings.config)) { if (buildOpts.settings.config.output === 'server' || isHybridOutput(buildOpts.settings.config)) {
serverRoot = buildOpts.settings.config.build.server; serverRoot = buildOpts.settings.config.build.server;
clientRoot = buildOpts.settings.config.build.client; clientRoot = buildOpts.settings.config.build.client;
} else { } else {

View file

@ -31,7 +31,7 @@ import {
removeTrailingForwardSlash, removeTrailingForwardSlash,
} from '../../core/path.js'; } from '../../core/path.js';
import { runHookBuildGenerated } from '../../integrations/index.js'; import { runHookBuildGenerated } from '../../integrations/index.js';
import { isServerLikeOutput } from '../../prerender/utils.js'; import { isHybridOutput } from '../../prerender/utils.js';
import { BEFORE_HYDRATION_SCRIPT_ID, PAGE_SCRIPT_ID } from '../../vite-plugin-scripts/index.js'; import { BEFORE_HYDRATION_SCRIPT_ID, PAGE_SCRIPT_ID } from '../../vite-plugin-scripts/index.js';
import { callEndpoint, createAPIContext, throwIfRedirectNotAllowed } from '../endpoint/index.js'; import { callEndpoint, createAPIContext, throwIfRedirectNotAllowed } from '../endpoint/index.js';
import { AstroError } from '../errors/index.js'; import { AstroError } from '../errors/index.js';
@ -94,7 +94,7 @@ export function chunkIsPage(
export async function generatePages(opts: StaticBuildOptions, internals: BuildInternals) { export async function generatePages(opts: StaticBuildOptions, internals: BuildInternals) {
const timer = performance.now(); const timer = performance.now();
const ssr = isServerLikeOutput(opts.settings.config); const ssr = opts.settings.config.output === 'server' || isHybridOutput(opts.settings.config); // hybrid mode is essentially SSR with prerender by default
const serverEntry = opts.buildConfig.serverEntry; const serverEntry = opts.buildConfig.serverEntry;
const outFolder = ssr ? opts.buildConfig.server : getOutDirWithinCwd(opts.settings.config.outDir); const outFolder = ssr ? opts.buildConfig.server : getOutDirWithinCwd(opts.settings.config.outDir);
@ -237,7 +237,7 @@ async function getPathsForRoute(
route: pageData.route, route: pageData.route,
isValidate: false, isValidate: false,
logging: opts.logging, logging: opts.logging,
ssr: isServerLikeOutput(opts.settings.config), ssr: opts.settings.config.output === 'server' || isHybridOutput(opts.settings.config),
}) })
.then((_result) => { .then((_result) => {
const label = _result.staticPaths.length === 1 ? 'page' : 'pages'; const label = _result.staticPaths.length === 1 ? 'page' : 'pages';
@ -413,7 +413,7 @@ async function generatePath(
} }
} }
const ssr = isServerLikeOutput(settings.config); const ssr = settings.config.output === 'server' || isHybridOutput(settings.config);
const url = getUrlForPath( const url = getUrlForPath(
pathname, pathname,
opts.settings.config.base, opts.settings.config.base,

View file

@ -3,7 +3,7 @@ import { fileURLToPath } from 'url';
import type { Plugin as VitePlugin } from 'vite'; import type { Plugin as VitePlugin } from 'vite';
import type { AstroAdapter } from '../../../@types/astro'; import type { AstroAdapter } from '../../../@types/astro';
import { runHookBuildSsr } from '../../../integrations/index.js'; import { runHookBuildSsr } from '../../../integrations/index.js';
import { isServerLikeOutput } from '../../../prerender/utils.js'; import { isHybridOutput } from '../../../prerender/utils.js';
import { BEFORE_HYDRATION_SCRIPT_ID, PAGE_SCRIPT_ID } from '../../../vite-plugin-scripts/index.js'; import { BEFORE_HYDRATION_SCRIPT_ID, PAGE_SCRIPT_ID } from '../../../vite-plugin-scripts/index.js';
import type { SerializedRouteInfo, SerializedSSRManifest } from '../../app/types'; import type { SerializedRouteInfo, SerializedSSRManifest } from '../../app/types';
import { joinPaths, prependForwardSlash } from '../../path.js'; import { joinPaths, prependForwardSlash } from '../../path.js';
@ -272,7 +272,8 @@ export function pluginSSR(
options: StaticBuildOptions, options: StaticBuildOptions,
internals: BuildInternals internals: BuildInternals
): AstroBuildPlugin { ): AstroBuildPlugin {
const ssr = isServerLikeOutput(options.settings.config); const ssr =
options.settings.config.output === 'server' || isHybridOutput(options.settings.config);
return { return {
build: 'ssr', build: 'ssr',
hooks: { hooks: {

View file

@ -15,7 +15,7 @@ import { emptyDir, removeEmptyDirs } from '../../core/fs/index.js';
import { appendForwardSlash, prependForwardSlash } from '../../core/path.js'; import { appendForwardSlash, prependForwardSlash } from '../../core/path.js';
import { isModeServerWithNoAdapter } from '../../core/util.js'; import { isModeServerWithNoAdapter } from '../../core/util.js';
import { runHookBuildSetup } from '../../integrations/index.js'; import { runHookBuildSetup } from '../../integrations/index.js';
import { isServerLikeOutput } from '../../prerender/utils.js'; import { isHybridOutput } from '../../prerender/utils.js';
import { PAGE_SCRIPT_ID } from '../../vite-plugin-scripts/index.js'; import { PAGE_SCRIPT_ID } from '../../vite-plugin-scripts/index.js';
import { AstroError, AstroErrorData } from '../errors/index.js'; import { AstroError, AstroErrorData } from '../errors/index.js';
import { info } from '../logger/core.js'; import { info } from '../logger/core.js';
@ -117,6 +117,7 @@ export async function viteBuild(opts: StaticBuildOptions) {
export async function staticBuild(opts: StaticBuildOptions, internals: BuildInternals) { export async function staticBuild(opts: StaticBuildOptions, internals: BuildInternals) {
const { settings } = opts; const { settings } = opts;
const hybridOutput = isHybridOutput(settings.config);
switch (true) { switch (true) {
case settings.config.output === 'static': { case settings.config.output === 'static': {
settings.timer.start('Static generate'); settings.timer.start('Static generate');
@ -125,7 +126,7 @@ export async function staticBuild(opts: StaticBuildOptions, internals: BuildInte
settings.timer.end('Static generate'); settings.timer.end('Static generate');
return; return;
} }
case isServerLikeOutput(settings.config): { case settings.config.output === 'server' || hybridOutput: {
settings.timer.start('Server generate'); settings.timer.start('Server generate');
await generatePages(opts, internals); await generatePages(opts, internals);
await cleanStaticOutput(opts, internals); await cleanStaticOutput(opts, internals);
@ -144,7 +145,7 @@ async function ssrBuild(
container: AstroBuildPluginContainer container: AstroBuildPluginContainer
) { ) {
const { settings, viteConfig } = opts; const { settings, viteConfig } = opts;
const ssr = isServerLikeOutput(settings.config); const ssr = settings.config.output === 'server' || isHybridOutput(settings.config);
const out = ssr ? opts.buildConfig.server : getOutDirWithinCwd(settings.config.outDir); const out = ssr ? opts.buildConfig.server : getOutDirWithinCwd(settings.config.outDir);
const { lastVitePlugins, vitePlugins } = container.runBeforeHook('ssr', input); const { lastVitePlugins, vitePlugins } = container.runBeforeHook('ssr', input);
@ -224,7 +225,7 @@ async function clientBuild(
) { ) {
const { settings, viteConfig } = opts; const { settings, viteConfig } = opts;
const timer = performance.now(); const timer = performance.now();
const ssr = isServerLikeOutput(settings.config); const ssr = settings.config.output === 'server' || isHybridOutput(settings.config);
const out = ssr ? opts.buildConfig.client : getOutDirWithinCwd(settings.config.outDir); const out = ssr ? opts.buildConfig.client : getOutDirWithinCwd(settings.config.outDir);
// Nothing to do if there is no client-side JS. // Nothing to do if there is no client-side JS.
@ -289,11 +290,12 @@ async function runPostBuildHooks(
const config = container.options.settings.config; const config = container.options.settings.config;
const buildConfig = container.options.settings.config.build; const buildConfig = container.options.settings.config.build;
for (const [fileName, mutation] of mutations) { for (const [fileName, mutation] of mutations) {
const root = isServerLikeOutput(config) const root =
? mutation.build === 'server' config.output === 'server' || isHybridOutput(config)
? buildConfig.server ? mutation.build === 'server'
: buildConfig.client ? buildConfig.server
: config.outDir; : buildConfig.client
: config.outDir;
const fileURL = new URL(fileName, root); const fileURL = new URL(fileName, root);
await fs.promises.mkdir(new URL('./', fileURL), { recursive: true }); await fs.promises.mkdir(new URL('./', fileURL), { recursive: true });
await fs.promises.writeFile(fileURL, mutation.code, 'utf-8'); await fs.promises.writeFile(fileURL, mutation.code, 'utf-8');
@ -310,7 +312,7 @@ async function cleanStaticOutput(opts: StaticBuildOptions, internals: BuildInter
if (pageData.route.prerender) if (pageData.route.prerender)
allStaticFiles.add(internals.pageToBundleMap.get(pageData.moduleSpecifier)); allStaticFiles.add(internals.pageToBundleMap.get(pageData.moduleSpecifier));
} }
const ssr = isServerLikeOutput(opts.settings.config); const ssr = opts.settings.config.output === 'server' || isHybridOutput(opts.settings.config);
const out = ssr ? opts.buildConfig.server : getOutDirWithinCwd(opts.settings.config.outDir); const out = ssr ? opts.buildConfig.server : getOutDirWithinCwd(opts.settings.config.outDir);
// The SSR output is all .mjs files, the client output is not. // The SSR output is all .mjs files, the client output is not.
const files = await glob('**/*.mjs', { const files = await glob('**/*.mjs', {

View file

@ -6,6 +6,7 @@ import * as colors from 'kleur/colors';
import path from 'path'; import path from 'path';
import { fileURLToPath, pathToFileURL } from 'url'; import { fileURLToPath, pathToFileURL } from 'url';
import { mergeConfig as mergeViteConfig } from 'vite'; import { mergeConfig as mergeViteConfig } from 'vite';
import { isHybridMalconfigured } from '../../prerender/utils.js';
import { AstroError, AstroErrorData } from '../errors/index.js'; import { AstroError, AstroErrorData } from '../errors/index.js';
import type { LogOptions } from '../logger/core.js'; import type { LogOptions } from '../logger/core.js';
import { arraify, isObject, isURL } from '../util.js'; import { arraify, isObject, isURL } from '../util.js';
@ -223,6 +224,12 @@ export async function openConfig(configOptions: LoadConfigOptions): Promise<Open
} }
const astroConfig = await resolveConfig(userConfig, root, flags, configOptions.cmd); const astroConfig = await resolveConfig(userConfig, root, flags, configOptions.cmd);
if (isHybridMalconfigured(astroConfig)) {
throw new Error(
`The "output" config option must be set to "hybrid" and "experimental.hybridOutput" must be set to true to use the hybrid output mode. Falling back to "static" output mode.`
);
}
return { return {
astroConfig, astroConfig,
userConfig, userConfig,

View file

@ -39,6 +39,7 @@ const ASTRO_CONFIG_DEFAULTS: AstroUserConfig & any = {
legacy: {}, legacy: {},
experimental: { experimental: {
assets: false, assets: false,
hybridOutput: false,
customClientDirectives: false, customClientDirectives: false,
inlineStylesheets: 'never', inlineStylesheets: 'never',
middleware: false, middleware: false,
@ -207,6 +208,7 @@ export const AstroConfigSchema = z.object({
.optional() .optional()
.default(ASTRO_CONFIG_DEFAULTS.experimental.inlineStylesheets), .default(ASTRO_CONFIG_DEFAULTS.experimental.inlineStylesheets),
middleware: z.oboolean().optional().default(ASTRO_CONFIG_DEFAULTS.experimental.middleware), middleware: z.oboolean().optional().default(ASTRO_CONFIG_DEFAULTS.experimental.middleware),
hybridOutput: z.boolean().optional().default(ASTRO_CONFIG_DEFAULTS.experimental.hybridOutput),
}) })
.passthrough() .passthrough()
.refine( .refine(

View file

@ -4,7 +4,7 @@ import { fileURLToPath, pathToFileURL } from 'url';
import type { AstroConfig, AstroSettings, AstroUserConfig } from '../../@types/astro'; import type { AstroConfig, AstroSettings, AstroUserConfig } from '../../@types/astro';
import { getContentPaths } from '../../content/index.js'; import { getContentPaths } from '../../content/index.js';
import jsxRenderer from '../../jsx/renderer.js'; import jsxRenderer from '../../jsx/renderer.js';
import { isServerLikeOutput } from '../../prerender/utils.js'; import { isHybridOutput } from '../../prerender/utils.js';
import { markdownContentEntryType } from '../../vite-plugin-markdown/content-entry-type.js'; import { markdownContentEntryType } from '../../vite-plugin-markdown/content-entry-type.js';
import { getDefaultClientDirectives } from '../client-directive/index.js'; import { getDefaultClientDirectives } from '../client-directive/index.js';
import { AstroError, AstroErrorData } from '../errors/index.js'; import { AstroError, AstroErrorData } from '../errors/index.js';
@ -23,7 +23,7 @@ export function createBaseSettings(config: AstroConfig): AstroSettings {
adapter: undefined, adapter: undefined,
injectedRoutes: injectedRoutes:
config.experimental.assets && isServerLikeOutput(config) config.experimental.assets && (config.output === 'server' || isHybridOutput(config))
? [{ pattern: '/_image', entryPoint: 'astro/assets/image-endpoint' }] ? [{ pattern: '/_image', entryPoint: 'astro/assets/image-endpoint' }]
: [], : [],
pageExtensions: ['.astro', '.html', ...SUPPORTED_MARKDOWN_FILE_EXTENSIONS], pageExtensions: ['.astro', '.html', ...SUPPORTED_MARKDOWN_FILE_EXTENSIONS],

View file

@ -9,7 +9,7 @@ import type {
} from '../../@types/astro'; } from '../../@types/astro';
import type { Environment, RenderContext } from '../render/index'; import type { Environment, RenderContext } from '../render/index';
import { isServerLikeOutput } from '../../prerender/utils.js'; import { isHybridOutput } from '../../prerender/utils.js';
import { renderEndpoint } from '../../runtime/server/index.js'; import { renderEndpoint } from '../../runtime/server/index.js';
import { ASTRO_VERSION } from '../constants.js'; import { ASTRO_VERSION } from '../constants.js';
import { AstroCookies, attachToResponse } from '../cookies/index.js'; import { AstroCookies, attachToResponse } from '../cookies/index.js';
@ -161,7 +161,7 @@ function isRedirect(statusCode: number) {
} }
export function throwIfRedirectNotAllowed(response: Response, config: AstroConfig) { export function throwIfRedirectNotAllowed(response: Response, config: AstroConfig) {
if (!isServerLikeOutput(config) && isRedirect(response.status)) { if (config.output !== 'server' && !isHybridOutput(config) && isRedirect(response.status)) {
throw new AstroError(AstroErrorData.StaticRedirectNotAvailable); throw new AstroError(AstroErrorData.StaticRedirectNotAvailable);
} }
} }

View file

@ -1,5 +1,5 @@
import type { AstroSettings, RuntimeMode } from '../../../@types/astro'; import type { AstroSettings, RuntimeMode } from '../../../@types/astro';
import { isServerLikeOutput } from '../../../prerender/utils.js'; import { isHybridOutput } from '../../../prerender/utils.js';
import type { LogOptions } from '../../logger/core.js'; import type { LogOptions } from '../../logger/core.js';
import type { ModuleLoader } from '../../module-loader/index'; import type { ModuleLoader } from '../../module-loader/index';
import type { Environment } from '../index'; import type { Environment } from '../index';
@ -30,7 +30,7 @@ export function createDevelopmentEnvironment(
resolve: createResolve(loader, settings.config.root), resolve: createResolve(loader, settings.config.root),
routeCache: new RouteCache(logging, mode), routeCache: new RouteCache(logging, mode),
site: settings.config.site, site: settings.config.site,
ssr: isServerLikeOutput(settings.config), ssr: settings.config.output === 'server' || isHybridOutput(settings.config),
streaming: true, streaming: true,
telemetry: Boolean(settings.forceDisableTelemetry), telemetry: Boolean(settings.forceDisableTelemetry),
}); });

View file

@ -13,7 +13,7 @@ import { createRequire } from 'module';
import path from 'path'; import path from 'path';
import slash from 'slash'; import slash from 'slash';
import { fileURLToPath } from 'url'; import { fileURLToPath } from 'url';
import { getPrerenderDefault } from '../../../prerender/utils.js'; import { isHybridOutput } from '../../../prerender/utils.js';
import { SUPPORTED_MARKDOWN_FILE_EXTENSIONS } from '../../constants.js'; import { SUPPORTED_MARKDOWN_FILE_EXTENSIONS } from '../../constants.js';
import { warn } from '../../logger/core.js'; import { warn } from '../../logger/core.js';
import { removeLeadingForwardSlash } from '../../path.js'; import { removeLeadingForwardSlash } from '../../path.js';
@ -228,7 +228,7 @@ export function createRouteManifest(
]); ]);
const validEndpointExtensions: Set<string> = new Set(['.js', '.ts']); const validEndpointExtensions: Set<string> = new Set(['.js', '.ts']);
const localFs = fsMod ?? nodeFs; const localFs = fsMod ?? nodeFs;
const prerender = getPrerenderDefault(settings.config); const isPrerenderDefault = isHybridOutput(settings.config);
const foundInvalidFileExtensions: Set<string> = new Set(); const foundInvalidFileExtensions: Set<string> = new Set();
@ -341,7 +341,7 @@ export function createRouteManifest(
component, component,
generate, generate,
pathname: pathname || undefined, pathname: pathname || undefined,
prerender, prerender: isPrerenderDefault,
}); });
} }
}); });
@ -417,7 +417,7 @@ export function createRouteManifest(
component, component,
generate, generate,
pathname: pathname || void 0, pathname: pathname || void 0,
prerender, prerender: isPrerenderDefault,
}); });
}); });

View file

@ -4,7 +4,7 @@ import slash from 'slash';
import { fileURLToPath } from 'url'; import { fileURLToPath } from 'url';
import { normalizePath } from 'vite'; import { normalizePath } from 'vite';
import type { AstroConfig, AstroSettings, RouteType } from '../@types/astro'; import type { AstroConfig, AstroSettings, RouteType } from '../@types/astro';
import { isServerLikeOutput } from '../prerender/utils.js'; import { isHybridOutput } from '../prerender/utils.js';
import { SUPPORTED_MARKDOWN_FILE_EXTENSIONS } from './constants.js'; import { SUPPORTED_MARKDOWN_FILE_EXTENSIONS } from './constants.js';
import type { ModuleLoader } from './module-loader'; import type { ModuleLoader } from './module-loader';
import { prependForwardSlash, removeTrailingForwardSlash } from './path.js'; import { prependForwardSlash, removeTrailingForwardSlash } from './path.js';
@ -139,7 +139,9 @@ export function isEndpoint(file: URL, settings: AstroSettings): boolean {
} }
export function isModeServerWithNoAdapter(settings: AstroSettings): boolean { export function isModeServerWithNoAdapter(settings: AstroSettings): boolean {
return isServerLikeOutput(settings.config) && !settings.adapter; return (
(settings.config.output === 'server' || isHybridOutput(settings.config)) && !settings.adapter
);
} }
export function relativeToSrcDir(config: AstroConfig, idOrUrl: URL | string) { export function relativeToSrcDir(config: AstroConfig, idOrUrl: URL | string) {

View file

@ -18,7 +18,7 @@ import type { PageBuildData } from '../core/build/types';
import { buildClientDirectiveEntrypoint } from '../core/client-directive/index.js'; import { buildClientDirectiveEntrypoint } from '../core/client-directive/index.js';
import { mergeConfig } from '../core/config/config.js'; import { mergeConfig } from '../core/config/config.js';
import { info, type LogOptions } from '../core/logger/core.js'; import { info, type LogOptions } from '../core/logger/core.js';
import { isServerLikeOutput } from '../prerender/utils.js'; import { isHybridOutput } from '../prerender/utils.js';
import { mdxContentEntryType } from '../vite-plugin-markdown/content-entry-type.js'; import { mdxContentEntryType } from '../vite-plugin-markdown/content-entry-type.js';
async function withTakingALongTimeMsg<T>({ async function withTakingALongTimeMsg<T>({
@ -339,7 +339,8 @@ export async function runHookBuildGenerated({
buildConfig: BuildConfig; buildConfig: BuildConfig;
logging: LogOptions; logging: LogOptions;
}) { }) {
const dir = isServerLikeOutput(config) ? buildConfig.client : config.outDir; const dir =
config.output === 'server' || isHybridOutput(config) ? buildConfig.client : config.outDir;
for (const integration of config.integrations) { for (const integration of config.integrations) {
if (integration?.hooks?.['astro:build:generated']) { if (integration?.hooks?.['astro:build:generated']) {
@ -365,7 +366,8 @@ export async function runHookBuildDone({
routes: RouteData[]; routes: RouteData[];
logging: LogOptions; logging: LogOptions;
}) { }) {
const dir = isServerLikeOutput(config) ? buildConfig.client : config.outDir; const dir =
config.output === 'server' || isHybridOutput(config) ? buildConfig.client : config.outDir;
await fs.promises.mkdir(dir, { recursive: true }); await fs.promises.mkdir(dir, { recursive: true });
for (const integration of config.integrations) { for (const integration of config.integrations) {

View file

@ -1,9 +1,11 @@
// TODO: remove after the experimetal phase when
import type { AstroConfig } from '../@types/astro'; import type { AstroConfig } from '../@types/astro';
export function isServerLikeOutput(config: AstroConfig) { export function isHybridMalconfigured(config: AstroConfig) {
return config.output === 'server' || config.output === 'hybrid'; return config.experimental.hybridOutput ? config.output !== 'hybrid' : config.output === 'hybrid';
} }
export function getPrerenderDefault(config: AstroConfig) { export function isHybridOutput(config: AstroConfig) {
return config.output === 'hybrid'; return config.experimental.hybridOutput && config.output === 'hybrid';
} }

View file

@ -9,7 +9,7 @@ import { error } from '../core/logger/core.js';
import * as msg from '../core/messages.js'; import * as msg from '../core/messages.js';
import { removeTrailingForwardSlash } from '../core/path.js'; import { removeTrailingForwardSlash } from '../core/path.js';
import { eventError, telemetry } from '../events/index.js'; import { eventError, telemetry } from '../events/index.js';
import { isServerLikeOutput } from '../prerender/utils.js'; import { isHybridOutput } from '../prerender/utils.js';
import { runWithErrorHandling } from './controller.js'; import { runWithErrorHandling } from './controller.js';
import { handle500Response } from './response.js'; import { handle500Response } from './response.js';
import { handleRoute, matchRoute } from './route.js'; import { handleRoute, matchRoute } from './route.js';
@ -25,7 +25,7 @@ export async function handleRequest(
const { settings, loader: moduleLoader } = env; const { settings, loader: moduleLoader } = env;
const { config } = settings; const { config } = settings;
const origin = `${moduleLoader.isHttps() ? 'https' : 'http'}://${req.headers.host}`; const origin = `${moduleLoader.isHttps() ? 'https' : 'http'}://${req.headers.host}`;
const buildingToSSR = isServerLikeOutput(config); const buildingToSSR = config.output === 'server' || isHybridOutput(config);
const url = new URL(origin + req.url); const url = new URL(origin + req.url);
let pathname: string; let pathname: string;

View file

@ -17,7 +17,7 @@ import { getParamsAndProps, GetParamsAndPropsError } from '../core/render/index.
import { createRequest } from '../core/request.js'; import { createRequest } from '../core/request.js';
import { matchAllRoutes } from '../core/routing/index.js'; import { matchAllRoutes } from '../core/routing/index.js';
import { getSortedPreloadedMatches } from '../prerender/routing.js'; import { getSortedPreloadedMatches } from '../prerender/routing.js';
import { isServerLikeOutput } from '../prerender/utils.js'; import { isHybridOutput } from '../prerender/utils.js';
import { log404 } from './common.js'; import { log404 } from './common.js';
import { handle404Response, writeSSRResult, writeWebResponse } from './response.js'; import { handle404Response, writeSSRResult, writeWebResponse } from './response.js';
@ -59,7 +59,7 @@ export async function matchRoute(
routeCache, routeCache,
pathname: pathname, pathname: pathname,
logging, logging,
ssr: isServerLikeOutput(settings.config), ssr: settings.config.output === 'server' || isHybridOutput(settings.config),
}); });
if (paramsAndPropsRes !== GetParamsAndPropsError.NoMatchingStaticPath) { if (paramsAndPropsRes !== GetParamsAndPropsError.NoMatchingStaticPath) {
@ -132,7 +132,7 @@ export async function handleRoute(
const { config } = settings; const { config } = settings;
const filePath: URL | undefined = matchedRoute.filePath; const filePath: URL | undefined = matchedRoute.filePath;
const { route, preloadedComponent, mod } = matchedRoute; const { route, preloadedComponent, mod } = matchedRoute;
const buildingToSSR = isServerLikeOutput(config); const buildingToSSR = config.output === 'server' || isHybridOutput(config);
// Headers are only available when using SSR. // Headers are only available when using SSR.
const request = createRequest({ const request = createRequest({
@ -158,7 +158,7 @@ export async function handleRoute(
routeCache: env.routeCache, routeCache: env.routeCache,
pathname: pathname, pathname: pathname,
logging, logging,
ssr: isServerLikeOutput(config), ssr: config.output === 'server' || isHybridOutput(config),
}); });
const options: SSROptions = { const options: SSROptions = {

View file

@ -2,7 +2,7 @@ import { normalizePath, type Plugin as VitePlugin } from 'vite';
import type { AstroSettings } from '../@types/astro.js'; import type { AstroSettings } from '../@types/astro.js';
import { isEndpoint, isPage } from '../core/util.js'; import { isEndpoint, isPage } from '../core/util.js';
import { getPrerenderDefault } from '../prerender/utils.js'; import { isHybridOutput } from '../prerender/utils.js';
import { scan } from './scan.js'; import { scan } from './scan.js';
export default function astroScannerPlugin({ settings }: { settings: AstroSettings }): VitePlugin { export default function astroScannerPlugin({ settings }: { settings: AstroSettings }): VitePlugin {
@ -25,11 +25,11 @@ export default function astroScannerPlugin({ settings }: { settings: AstroSettin
const fileIsPage = isPage(fileURL, settings); const fileIsPage = isPage(fileURL, settings);
const fileIsEndpoint = isEndpoint(fileURL, settings); const fileIsEndpoint = isEndpoint(fileURL, settings);
if (!(fileIsPage || fileIsEndpoint)) return; if (!(fileIsPage || fileIsEndpoint)) return;
const defaultPrerender = getPrerenderDefault(settings.config); const hybridOutput = isHybridOutput(settings.config);
const pageOptions = await scan(code, id, settings.config.output === 'hybrid'); const pageOptions = await scan(code, id, hybridOutput);
if (typeof pageOptions.prerender === 'undefined') { if (typeof pageOptions.prerender === 'undefined') {
pageOptions.prerender = defaultPrerender; pageOptions.prerender = hybridOutput ? true : false;
} }
const { meta = {} } = this.getModuleInfo(id) ?? {}; const { meta = {} } = this.getModuleInfo(id) ?? {};

View file

@ -144,6 +144,9 @@ describe('Prerender', () => {
adapter: testAdapter(), adapter: testAdapter(),
base: '/blog', base: '/blog',
output: 'hybrid', output: 'hybrid',
experimental: {
hybridOutput: true,
},
vite: { vite: {
plugins: [vitePluginRemovePrerenderExport()], plugins: [vitePluginRemovePrerenderExport()],
}, },

View file

@ -133,6 +133,9 @@ describe('Route matching', () => {
userConfig: { userConfig: {
trailingSlash: 'never', trailingSlash: 'never',
output: 'hybrid', output: 'hybrid',
experimental: {
hybridOutput: true,
},
adapter: testAdapter(), adapter: testAdapter(),
}, },
disableTelemetry: true, disableTelemetry: true,

View file

@ -38,6 +38,9 @@ describe('Hybrid rendering', () => {
fixture = await loadFixture({ fixture = await loadFixture({
root: './fixtures/prerender/', root: './fixtures/prerender/',
output: 'hybrid', output: 'hybrid',
experimental: {
hybridOutput: true,
},
}); });
await fixture.build(); await fixture.build();
}); });

View file

@ -3,7 +3,7 @@ import { ssgBuild } from './build/ssg.js';
import type { ImageService, SSRImageService, TransformOptions } from './loaders/index.js'; import type { ImageService, SSRImageService, TransformOptions } from './loaders/index.js';
import type { LoggerLevel } from './utils/logger.js'; import type { LoggerLevel } from './utils/logger.js';
import { joinPaths, prependForwardSlash, propsToFilename } from './utils/paths.js'; import { joinPaths, prependForwardSlash, propsToFilename } from './utils/paths.js';
import { isServerLikeOutput } from './utils/prerender.js'; import { isHybridOutput } from './utils/prerender.js';
import { createPlugin } from './vite-plugin-astro-image.js'; import { createPlugin } from './vite-plugin-astro-image.js';
export { getImage } from './lib/get-image.js'; export { getImage } from './lib/get-image.js';
@ -85,7 +85,7 @@ export default function integration(options: IntegrationOptions = {}): AstroInte
vite: getViteConfiguration(command === 'dev'), vite: getViteConfiguration(command === 'dev'),
}); });
if (command === 'dev' || isServerLikeOutput(config)) { if (command === 'dev' || config.output === 'server' || isHybridOutput(config)) {
injectRoute({ injectRoute({
pattern: ROUTE_PATTERN, pattern: ROUTE_PATTERN,
entryPoint: '@astrojs/image/endpoint', entryPoint: '@astrojs/image/endpoint',

View file

@ -1,5 +1,5 @@
import type { AstroConfig } from 'astro'; import type { AstroConfig } from 'astro';
export function isServerLikeOutput(config: AstroConfig) { export function isHybridOutput(config: AstroConfig) {
return config.output === 'server' || config.output === 'hybrid'; return config.experimental.hybridOutput && config.output === 'hybrid';
} }

View file

@ -6,6 +6,13 @@ const isHybridMode = process.env.PRERENDER === "false";
/** @type {import('astro').AstroConfig} */ /** @type {import('astro').AstroConfig} */
const partialConfig = { const partialConfig = {
output: isHybridMode ? "hybrid" : "server", output: isHybridMode ? "hybrid" : "server",
...(isHybridMode
? ({
experimental: {
hybridOutput: true,
},
})
: ({})),
}; };
export default defineConfig({ export default defineConfig({

View file

@ -46,6 +46,9 @@ describe('Mixed Hybrid rendering with SSR', () => {
fixture = await loadFixture({ fixture = await loadFixture({
root: new URL('./fixtures/prerender/', import.meta.url).toString(), root: new URL('./fixtures/prerender/', import.meta.url).toString(),
output: 'hybrid', output: 'hybrid',
experimental: {
hybridOutput: true,
},
adapter: netlifyAdapter({ adapter: netlifyAdapter({
dist: new URL('./fixtures/prerender/dist/', import.meta.url), dist: new URL('./fixtures/prerender/dist/', import.meta.url),
}), }),

View file

@ -140,6 +140,9 @@ describe('Hybrid rendering', () => {
base: '/some-base', base: '/some-base',
root: './fixtures/prerender/', root: './fixtures/prerender/',
output: 'hybrid', output: 'hybrid',
experimental: {
hybridOutput: true,
},
adapter: nodejs({ mode: 'standalone' }), adapter: nodejs({ mode: 'standalone' }),
}); });
await fixture.build(); await fixture.build();
@ -196,6 +199,9 @@ describe('Hybrid rendering', () => {
fixture = await loadFixture({ fixture = await loadFixture({
root: './fixtures/prerender/', root: './fixtures/prerender/',
output: 'hybrid', output: 'hybrid',
experimental: {
hybridOutput: true,
},
adapter: nodejs({ mode: 'standalone' }), adapter: nodejs({ mode: 'standalone' }),
}); });
await fixture.build(); await fixture.build();

View file

@ -1,5 +1,5 @@
import type { AstroConfig } from 'astro'; import type { AstroConfig } from 'astro';
export function isServerLikeOutput(config: AstroConfig) { export function isHybridOutput(config: AstroConfig) {
return config.output === 'server' || config.output === 'hybrid'; return config.experimental.hybridOutput && config.output === 'hybrid';
} }

View file

@ -8,7 +8,7 @@ import {
} from '../image/shared.js'; } from '../image/shared.js';
import { exposeEnv } from '../lib/env.js'; import { exposeEnv } from '../lib/env.js';
import { emptyDir, getVercelOutput, writeJson } from '../lib/fs.js'; import { emptyDir, getVercelOutput, writeJson } from '../lib/fs.js';
import { isServerLikeOutput } from '../lib/prerender.js'; import { isHybridOutput } from '../lib/prerender.js';
import { getRedirects } from '../lib/redirects.js'; import { getRedirects } from '../lib/redirects.js';
const PACKAGE_NAME = '@astrojs/vercel/static'; const PACKAGE_NAME = '@astrojs/vercel/static';
@ -55,7 +55,7 @@ export default function vercelStatic({
setAdapter(getAdapter()); setAdapter(getAdapter());
_config = config; _config = config;
if (isServerLikeOutput(config)) { if (config.output === 'server' || isHybridOutput(config)) {
throw new Error(`${PACKAGE_NAME} should be used with output: 'static'`); throw new Error(`${PACKAGE_NAME} should be used with output: 'static'`);
} }
}, },

View file

@ -27,6 +27,9 @@ describe('Serverless hybrid rendering', () => {
fixture = await loadFixture({ fixture = await loadFixture({
root: './fixtures/serverless-prerender/', root: './fixtures/serverless-prerender/',
output: 'hybrid', output: 'hybrid',
experimental: {
hybridOutput: true,
},
}); });
}); });