astro
Version:
Astro is a modern site builder with web best practices, performance, and DX front-of-mind.
108 lines (107 loc) • 3.94 kB
JavaScript
import { performance } from "node:perf_hooks";
import { fileURLToPath } from "node:url";
import { mergeConfig, preview } from "vite";
import { getClientOutputDirectory } from "../../prerender/utils.js";
import * as msg from "../messages/runtime.js";
import { getResolvedHostForHttpServer } from "./util.js";
import { vitePluginAstroPreview } from "./vite-plugin-astro-preview.js";
import { BuildTimeAstroVersionProvider } from "../../cli/infra/build-time-astro-version-provider.js";
import { piccoloreTextStyler } from "../../cli/infra/piccolore-text-styler.js";
export default async function createStaticPreviewServer(settings, logger) {
const startServerTime = performance.now();
let previewServer;
try {
// Build the Astro-specific preview config
const astroPreviewConfig = {
configFile: false,
base: settings.config.base,
appType: "mpa",
build: { outDir: fileURLToPath(getClientOutputDirectory(settings)) },
root: fileURLToPath(settings.config.root),
preview: {
host: settings.config.server.host,
port: settings.config.server.port,
headers: settings.config.server.headers,
open: settings.config.server.open
},
plugins: [vitePluginAstroPreview(settings)]
};
// Merge user's vite config (from astro.config.mjs `vite` field) as the base,
// then apply Astro's overrides on top. This ensures vite.preview.* settings
// are respected while Astro-specific values (like configFile: false) always win.
// Plugins are excluded from the user config since Astro manages its own plugin set.
const { plugins: _plugins, ...userViteConfig } = settings.config.vite ?? {};
const mergedViteConfig = mergeConfig(userViteConfig, astroPreviewConfig);
// Apply allowedHosts after merging to avoid Vite's array concatenation behavior.
// If the user explicitly set server.allowedHosts in Astro config (boolean or non-empty
// array), that takes precedence. Otherwise, the user's vite.preview.allowedHosts from
// settings.config.vite (merged above) is preserved.
const { allowedHosts } = settings.config.server;
if (typeof allowedHosts === "boolean" || Array.isArray(allowedHosts) && allowedHosts.length > 0) {
mergedViteConfig.preview ??= {};
mergedViteConfig.preview.allowedHosts = allowedHosts;
}
previewServer = await preview(mergedViteConfig);
} catch (err) {
if (err instanceof Error) {
logger.error(null, err.stack || err.message);
}
throw err;
}
// Set up shortcuts
const customShortcuts = [
(
// Disable default Vite shortcuts that don't work well with Astro
{
key: "r",
description: ""
}),
{
key: "u",
description: ""
},
{
key: "c",
description: ""
},
{
key: "s",
description: ""
}
];
previewServer.bindCLIShortcuts({ customShortcuts });
// Log server start URLs
logger.info("SKIP_FORMAT", msg.serverStart({
startupTime: performance.now() - startServerTime,
resolvedUrls: previewServer.resolvedUrls ?? {
local: [],
network: []
},
host: settings.config.server.host,
base: settings.config.base,
astroVersionProvider: new BuildTimeAstroVersionProvider(),
textStyler: piccoloreTextStyler
}));
// Resolves once the server is closed
function closed() {
return new Promise((resolve, reject) => {
previewServer.httpServer.addListener("close", resolve);
previewServer.httpServer.addListener("error", reject);
});
}
// Read the actual bound port from the HTTP server, not the configured port.
// This is important when port 0 is used (OS-assigned port).
const address = previewServer.httpServer.address();
const actualPort = address && typeof address === "object" ? address.port : settings.config.server.port;
return {
host: getResolvedHostForHttpServer(settings.config.server.host),
port: actualPort,
urls: previewServer.resolvedUrls ?? {
local: [],
network: []
},
closed,
server: previewServer.httpServer,
stop: previewServer.close.bind(previewServer)
};
}