astro
Version:
Astro is a modern site builder with web best practices, performance, and DX front-of-mind.
139 lines (138 loc) • 5.79 kB
JavaScript
import fs from "node:fs";
import { performance } from "node:perf_hooks";
import colors from "piccolore";
import { getMajor, getMinor, getPatch, isGreaterThan } from "verkit";
import { getDataStoreChunkSize, getDataStoreDir, getDataStoreFile } from "../../content/paths.js";
import { globalContentLayer } from "../../content/instance.js";
import { attachContentServerListeners, attachDataStoreInvalidation } from "../../content/index.js";
import { MutableDataStore } from "../../content/mutable-data-store.js";
import { globalContentConfigObserver } from "../../content/utils.js";
import { telemetry } from "../../events/index.js";
import * as msg from "../messages/runtime.js";
import { newVersionAvailable } from "../messages/node.js";
import { ensureProcessNodeEnv } from "../util.js";
import { startContainer } from "./container.js";
import { createContainerWithAutomaticRestart } from "./restart.js";
import { fetchLatestAstroVersion, MAX_PATCH_DISTANCE, shouldCheckForUpdates } from "./update-check.js";
import { BuildTimeAstroVersionProvider } from "../../cli/infra/build-time-astro-version-provider.js";
import { piccoloreTextStyler } from "../../cli/infra/piccolore-text-styler.js";
/**
* Runs Astro’s development server. This is a local HTTP server that doesn’t bundle assets.
* It uses Hot Module Replacement (HMR) to update your browser as you save changes in your editor.
*
* @experimental The JavaScript API is experimental
*/
export default async function dev(inlineConfig) {
ensureProcessNodeEnv("development");
const devStart = performance.now();
await telemetry.record([]);
// Create a container which sets up the Vite server.
const restart = await createContainerWithAutomaticRestart({
inlineConfig,
fs
});
const logger = restart.container.logger;
const currentVersion = "7.4.2" ?? "0.0.0";
const isPrerelease = currentVersion.includes("-");
if (!isPrerelease) {
try {
// Don't await this, we don't want to block the dev server from starting
shouldCheckForUpdates(restart.container.settings.preferences).then(async (shouldCheck) => {
if (shouldCheck) {
const version = await fetchLatestAstroVersion(restart.container.settings.preferences);
if (isGreaterThan(version, currentVersion)) {
// Only update the latestAstroVersion if the latest version is greater than the current version, that way we don't need to check that again
// whenever we check for the latest version elsewhere
restart.container.settings.latestAstroVersion = version;
const sameMajor = getMajor(version) === getMajor(currentVersion);
const sameMinor = getMinor(version) === getMinor(currentVersion);
const patchDistance = getPatch(version) - getPatch(currentVersion);
if (sameMajor && sameMinor && patchDistance < MAX_PATCH_DISTANCE) {
// Don't bother the user with a log if they're only a few patch versions behind
// We can still tell them in the dev toolbar, which has a more opt-in nature
return;
}
logger.warn("SKIP_FORMAT", await newVersionAvailable({ latestVersion: version }));
}
}
}).catch(() => {});
} catch {}
}
let store;
try {
const chunkSize = getDataStoreChunkSize(restart.container.settings);
if (chunkSize !== undefined) {
const dataStoreDir = getDataStoreDir(restart.container.settings, true);
store = await MutableDataStore.fromDir(dataStoreDir, chunkSize, logger);
} else {
const dataStoreFile = getDataStoreFile(restart.container.settings, true);
store = await MutableDataStore.fromFile(dataStoreFile);
}
} catch (err) {
logger.error("content", err.message);
}
if (!store) {
logger.error("content", "Failed to create data store");
} else {
// Invalidate the content virtual modules directly when the store is
// written, rather than relying on the file watcher to observe the write.
// On Windows the watcher can miss it, leaving dev serving stale content.
attachDataStoreInvalidation(store, restart.container.viteServer, restart.container.settings);
}
await attachContentServerListeners(restart.container);
const config = globalContentConfigObserver.get();
if (config.status === "error") {
logger.error("content", config.error.message);
}
if (config.status === "loaded" && store) {
const contentLayer = globalContentLayer.init({
settings: restart.container.settings,
logger,
watcher: restart.container.viteServer.watcher,
store
});
contentLayer.watchContentConfig();
await contentLayer.sync();
} else if (config.status !== "does-not-exist") {
logger.warn("content", "Content config not loaded");
}
// Start listening to the port
const devServerAddressInfo = await startContainer(restart.container);
restart.bindCLIShortcuts();
logger.info("SKIP_FORMAT", msg.serverStart({
startupTime: performance.now() - devStart,
resolvedUrls: restart.container.viteServer.resolvedUrls || {
local: [],
network: []
},
host: restart.container.settings.config.server.host,
base: restart.container.settings.config.base,
astroVersionProvider: new BuildTimeAstroVersionProvider(),
textStyler: piccoloreTextStyler
}));
if (isPrerelease) {
logger.warn("SKIP_FORMAT", msg.prerelease({ currentVersion }));
}
if (restart.container.viteServer.config.server?.fs?.strict === false) {
logger.warn("SKIP_FORMAT", msg.fsStrictWarning());
}
logger.info(null, colors.green("watching for file changes..."));
return {
address: devServerAddressInfo,
get resolvedUrls() {
return restart.container.viteServer.resolvedUrls || {
local: [],
network: []
};
},
get watcher() {
return restart.container.viteServer.watcher;
},
handle(req, res) {
return restart.container.handle(req, res);
},
async stop() {
await restart.container.close();
}
};
}