astro
Version:
Astro is a modern site builder with web best practices, performance, and DX front-of-mind.
85 lines (84 loc) • 4 kB
JavaScript
import fs from "node:fs";
import { createRequire } from "node:module";
import { fileURLToPath, pathToFileURL } from "node:url";
import { AstroIntegrationLogger } from "../logger/core.js";
import { telemetry } from "../../events/index.js";
import { eventCliSession } from "../../events/session.js";
import { runHookConfigDone, runHookConfigSetup } from "../../integrations/hooks.js";
import { resolveConfig } from "../config/config.js";
import { loadOrCreateNodeLogger } from "../logger/load.js";
import { createSettings } from "../config/settings.js";
import { createRoutesList } from "../routing/create-manifest.js";
import { getClientOutputDirectory, getPrerenderDefault } from "../../prerender/utils.js";
import { ensureProcessNodeEnv } from "../util.js";
import createStaticPreviewServer from "./static-preview-server.js";
import { getResolvedHostForHttpServer } from "./util.js";
/**
* Starts a local server to serve your static dist/ directory. This command is useful for previewing
* your build locally, before deploying it. It is not designed to be run in production.
*
* @experimental The JavaScript API is experimental
*/
export default async function preview(inlineConfig) {
ensureProcessNodeEnv("production");
const { userConfig, astroConfig } = await resolveConfig(inlineConfig ?? {}, "preview");
const logger = await loadOrCreateNodeLogger(astroConfig, inlineConfig ?? {});
telemetry.record(eventCliSession("preview", userConfig));
const _settings = await createSettings(astroConfig, inlineConfig.logLevel, fileURLToPath(astroConfig.root));
const settings = await runHookConfigSetup({
settings: _settings,
command: "preview",
logger
});
// Create a route manifest and determine buildOutput from actual routes.
// Route scanning sets settings.buildOutput to 'server' if any route is non-prerendered.
settings.buildOutput = getPrerenderDefault(settings.config) ? "static" : "server";
await createRoutesList({
settings,
cwd: inlineConfig.root
}, logger);
await runHookConfigDone({
settings,
logger,
command: "preview"
});
if (settings.buildOutput === "static" && !settings.adapter?.previewEntrypoint) {
const clientOutDir = getClientOutputDirectory(settings);
if (!fs.existsSync(clientOutDir)) {
const outDirPath = fileURLToPath(clientOutDir);
throw new Error(`[preview] The output directory ${outDirPath} does not exist. Did you run \`astro build\`?`);
}
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.`);
}
// We need to use require.resolve() here so that advanced package managers like pnpm
// don't treat this as a dependency of Astro itself. This correctly resolves the
// preview entrypoint of the integration package, relative to the user's project root.
const require = createRequire(settings.config.root);
const previewEntrypointUrl = pathToFileURL(require.resolve(settings.adapter.previewEntrypoint.toString())).href;
const previewModule = await import(previewEntrypointUrl);
if (typeof previewModule.default !== "function") {
throw new Error(`[preview] ${settings.adapter.name} cannot preview your app.`);
}
const server = await previewModule.default({
outDir: settings.config.outDir,
client: settings.config.build.client,
server: settings.config.build.server,
serverEntrypoint: new URL(settings.config.build.serverEntry, settings.config.build.server),
host: getResolvedHostForHttpServer(settings.config.server.host),
port: settings.config.server.port,
base: settings.config.base,
logger: new AstroIntegrationLogger(logger.options, settings.adapter.name),
headers: settings.config.server.headers,
allowedHosts: settings.config.server.allowedHosts,
open: settings.config.server.open,
root: settings.config.root
});
return server;
}