import EventEmitter from 'node:events';
import process from 'node:process';
import chokidar from 'chokidar';
import defu from 'defu';
import { toNodeListener } from 'h3';
import { createJiti } from 'jiti';
import { listen } from 'listhen';
import { resolve, join, relative } from 'pathe';
import { debounce } from 'perfect-debounce';
import { provider } from 'std-env';
import { joinURL } from 'ufo';
import { a as clearBuildDir } from '../shared/cli.BI-F07wL.mjs';
import { l as loadKit } from '../shared/cli.DlcAx0De.mjs';
import { a as logger } from '../shared/cli.CyjRwZnH.mjs';
import { l as loadNuxtManifest, r as resolveNuxtManifest, w as writeNuxtManifest } from '../shared/cli.CFGy0wx0.mjs';
import { Youch } from 'youch';
async function renderError(req, res, error) {
const youch = new Youch();
res.statusCode = 500;
res.setHeader("Content-Type", "text/html");
const html = await youch.toHTML(error, {
request: {
url: req.url,
method: req.method,
headers: req.headers
}
});
res.end(html);
}
async function createNuxtDevServer(options, listenOptions) {
const devServer = new NuxtDevServer(options);
devServer.listener = await listen(
devServer.handler,
listenOptions || {
port: options.port ?? 0,
hostname: "127.0.0.1",
showURL: false
}
);
devServer.listener._url = devServer.listener.url;
if (options.devContext.proxy?.url) {
devServer.listener.url = options.devContext.proxy.url;
}
if (options.devContext.proxy?.urls) {
const _getURLs = devServer.listener.getURLs.bind(devServer.listener);
devServer.listener.getURLs = async () => Array.from(
/* @__PURE__ */ new Set([...options.devContext.proxy.urls, ...await _getURLs()])
);
}
return devServer;
}
const RESTART_RE = /^(?:nuxt\.config\.[a-z0-9]+|\.nuxtignore|\.nuxtrc|\.config\/nuxt(?:\.config)?\.[a-z0-9]+)$/;
class NuxtDevServer extends EventEmitter {
constructor(options) {
super();
this.options = options;
this.loadDebounced = debounce(this.load);
let _initResolve;
const _initPromise = new Promise((resolve2) => {
_initResolve = resolve2;
});
this.once("ready", () => {
_initResolve();
});
this._jiti = createJiti(options.cwd);
this.handler = async (req, res) => {
if (this._loadingError) {
this._renderError(req, res);
return;
}
await _initPromise;
if (this._handler) {
this._handler(req, res);
} else {
this._renderLoadingScreen(req, res);
}
};
this.listener = void 0;
}
_handler;
_distWatcher;
_currentNuxt;
_loadingMessage;
_jiti;
_loadingError;
loadDebounced;
handler;
listener;
_renderError(req, res) {
renderError(req, res, this._loadingError);
}
async _renderLoadingScreen(req, res) {
res.statusCode = 503;
res.setHeader("Content-Type", "text/html");
const loadingTemplate = this.options.loadingTemplate || this._currentNuxt?.options.devServer.loadingTemplate || await this._jiti.import("@nuxt/ui-templates").then((r) => r.loading).catch(() => {
}) || ((params) => `
${params.loading}
`);
res.end(
loadingTemplate({
loading: this._loadingMessage || "Loading..."
})
);
}
async init() {
await this.load();
await this._watchConfig();
}
async load(reload, reason) {
try {
await this._load(reload, reason);
this._loadingError = void 0;
} catch (error) {
logger.error(`Cannot ${reload ? "restart" : "start"} nuxt: `, error);
this._handler = void 0;
this._loadingError = error;
this._loadingMessage = "Error while loading Nuxt. Please check console and fix errors.";
this.emit("loading:error", error);
}
}
async _load(reload, reason) {
const action = reload ? "Restarting" : "Starting";
this._loadingMessage = `${reason ? `${reason}. ` : ""}${action} Nuxt...`;
this._handler = void 0;
this.emit("loading", this._loadingMessage);
if (reload) {
logger.info(this._loadingMessage);
}
if (this._currentNuxt) {
await this._currentNuxt.close();
}
if (this._distWatcher) {
await this._distWatcher.close();
}
const kit = await loadKit(this.options.cwd);
const devServerDefaults = _getDevServerDefaults({}, await this.listener.getURLs().then((r) => r.map((r2) => r2.url)));
this._currentNuxt = await kit.loadNuxt({
cwd: this.options.cwd,
dev: true,
ready: false,
envName: this.options.envName,
dotenv: {
cwd: this.options.cwd,
fileName: this.options.dotenv.fileName
},
defaults: defu(this.options.defaults, devServerDefaults),
overrides: {
logLevel: this.options.logLevel,
...this.options.overrides,
vite: {
clearScreen: this.options.clear,
...this.options.overrides.vite
}
}
});
if (!process.env.NUXI_DISABLE_VITE_HMR) {
this._currentNuxt.hooks.hook("vite:extend", ({ config }) => {
if (config.server) {
config.server.hmr = {
protocol: void 0,
...config.server.hmr,
port: void 0,
host: void 0,
server: this.listener.server
};
}
});
}
this._currentNuxt.hooks.hookOnce("close", () => {
this.listener.server.removeAllListeners("upgrade");
});
if (!reload) {
const previousManifest = await loadNuxtManifest(this._currentNuxt.options.buildDir);
const newManifest = resolveNuxtManifest(this._currentNuxt);
const promise = writeNuxtManifest(this._currentNuxt, newManifest);
this._currentNuxt.hooks.hookOnce("ready", async () => {
await promise;
});
if (previousManifest && newManifest && previousManifest._hash !== newManifest._hash) {
await clearBuildDir(this._currentNuxt.options.buildDir);
}
}
await this._currentNuxt.ready();
const unsub = this._currentNuxt.hooks.hook("restart", async (options) => {
unsub();
if (options?.hard) {
this.emit("restart");
return;
}
await this.load(true);
});
if (this._currentNuxt.server && "upgrade" in this._currentNuxt.server) {
this.listener.server.on(
"upgrade",
async (req, socket, head) => {
const nuxt = this._currentNuxt;
if (!nuxt)
return;
const viteHmrPath = joinURL(
nuxt.options.app.baseURL.startsWith("./") ? nuxt.options.app.baseURL.slice(1) : nuxt.options.app.baseURL,
nuxt.options.app.buildAssetsDir
);
if (req.url.startsWith(viteHmrPath)) {
return;
}
await nuxt.server.upgrade(req, socket, head);
}
);
}
await this._currentNuxt.hooks.callHook("listen", this.listener.server, this.listener);
const addr = this.listener.address;
this._currentNuxt.options.devServer.host = addr.address;
this._currentNuxt.options.devServer.port = addr.port;
this._currentNuxt.options.devServer.url = _getAddressURL(addr, !!this.listener.https);
this._currentNuxt.options.devServer.https = this.options.devContext.proxy?.https;
if (this.listener.https && !process.env.NODE_TLS_REJECT_UNAUTHORIZED) {
logger.warn("You might need `NODE_TLS_REJECT_UNAUTHORIZED=0` environment variable to make https work.");
}
await Promise.all([
kit.writeTypes(this._currentNuxt).catch(console.error),
kit.buildNuxt(this._currentNuxt)
]);
this._distWatcher = chokidar.watch(resolve(this._currentNuxt.options.buildDir, "dist"), {
ignoreInitial: true,
depth: 0
});
this._distWatcher.on("unlinkDir", () => {
this.loadDebounced(true, ".nuxt/dist directory has been removed");
});
this._handler = toNodeListener(this._currentNuxt.server.app);
this.emit("ready", addr);
}
async _watchConfig() {
const configWatcher = chokidar.watch([this.options.cwd, join(this.options.cwd, ".config")], {
ignoreInitial: true,
depth: 0
});
configWatcher.on("all", (event, _file) => {
if (event === "all" || event === "ready" || event === "error" || event === "raw") {
return;
}
const file = relative(this.options.cwd, _file);
if (file === (this.options.dotenv.fileName || ".env")) {
this.emit("restart");
}
if (RESTART_RE.test(file)) {
this.loadDebounced(true, `${file} updated`);
}
});
}
}
function _getAddressURL(addr, https) {
const proto = https ? "https" : "http";
let host = addr.address.includes(":") ? `[${addr.address}]` : addr.address;
if (host === "[::]") {
host = "localhost";
}
const port = addr.port || 3e3;
return `${proto}://${host}:${port}/`;
}
function _getDevServerOverrides(listenOptions) {
if (listenOptions.public || provider === "codesandbox") {
return {
devServer: { cors: { origin: "*" } },
vite: { server: { allowedHosts: true } }
};
}
return {};
}
function _getDevServerDefaults(listenOptions, urls = []) {
const defaultConfig = {};
if (urls) {
defaultConfig.vite = { server: { allowedHosts: urls.map((u) => new URL(u).hostname) } };
}
if (listenOptions.hostname) {
const protocol = listenOptions.https ? "https" : "http";
defaultConfig.devServer = { cors: { origin: [`${protocol}://${listenOptions.hostname}`, ...urls] } };
defaultConfig.vite = defu(defaultConfig.vite, { server: { allowedHosts: [listenOptions.hostname] } });
}
return defaultConfig;
}
const dev = {
__proto__: null,
_getDevServerDefaults: _getDevServerDefaults,
_getDevServerOverrides: _getDevServerOverrides,
createNuxtDevServer: createNuxtDevServer
};
export { _getDevServerOverrides as _, _getDevServerDefaults as a, createNuxtDevServer as c, dev as d, renderError as r };