Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
685 changes: 307 additions & 378 deletions pnpm-lock.yaml

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion src/build/vite/dev.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { watch as fsWatch } from "node:fs";
import { join } from "pathe";
import { debounce } from "perfect-debounce";
import { scanHandlers } from "../../scan.ts";
import { getEnvRunner } from "./env.ts";

// https://vite.dev/guide/api-environment-runtimes.html#modulerunner

Expand Down Expand Up @@ -131,7 +132,7 @@ export async function configureViteDevServer(
// Vite upgrade. TODO: Is there a better way?
return;
}
ctx.devWorker?.upgrade(req, socket, head);
getEnvRunner(ctx).upgrade?.(req, socket, head);
});
}

Expand Down
22 changes: 8 additions & 14 deletions src/build/vite/env.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,19 @@
import type { EnvironmentOptions } from "vite";
import type { NitroPluginContext, ServiceConfig } from "./types.ts";

import { NodeDevWorker } from "../../dev/worker.ts";
import { NodeEnvRunner } from "../../runner/node.ts";
import { join, resolve } from "node:path";
import { runtimeDependencies, runtimeDir } from "nitro/meta";
import { resolveModulePath } from "exsolve";
import { createFetchableDevEnvironment } from "./dev.ts";
import { isAbsolute } from "pathe";

export function createDevWorker(ctx: NitroPluginContext) {
return new NodeDevWorker({
export function getEnvRunner(ctx: NitroPluginContext) {
return (ctx._envRunner ??= new NodeEnvRunner({
name: "nitro-vite",
entry: resolve(runtimeDir, "internal/vite/dev-worker.mjs"),
hooks: {},
data: {
server: true,
globals: {
__NITRO_RUNTIME_CONFIG__: ctx.nitro!.options.runtimeConfig,
},
},
});
entry: resolve(runtimeDir, "internal/vite/node-runner.mjs"),
data: { server: true },
}));
}

export function createNitroEnvironment(
Expand Down Expand Up @@ -52,7 +46,7 @@ export function createNitroEnvironment(
createFetchableDevEnvironment(
envName,
envConfig,
ctx.devWorker!,
getEnvRunner(ctx),
resolve(runtimeDir, "internal/vite/dev-entry.mjs")
),
},
Expand Down Expand Up @@ -85,7 +79,7 @@ export function createServiceEnvironment(
createFetchableDevEnvironment(
envName,
envConfig,
ctx.devWorker!,
getEnvRunner(ctx),
tryResolve(serviceConfig.entry)
),
},
Expand Down
19 changes: 14 additions & 5 deletions src/build/vite/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { createNitro, prepare } from "../../builder.ts";
import { getViteRollupConfig } from "./rollup.ts";
import { buildEnvironments, prodSetup } from "./prod.ts";
import {
createDevWorker,
getEnvRunner,
createNitroEnvironment,
createServiceEnvironments,
} from "./env.ts";
Expand Down Expand Up @@ -432,16 +432,25 @@ async function setupNitroContext(
ctx.rollupConfig.config
);

// Create dev worker
if (ctx.nitro.options.dev && !ctx.devWorker) {
ctx.devWorker = createDevWorker(ctx);
ctx.nitro.fetch = (req) => ctx.devWorker!.fetch(req);
// Warm up env runner for dev
if (ctx.nitro.options.dev) {
getEnvRunner(ctx);
}

// Attach nitro.fetch to env runner
ctx.nitro.fetch = (req) => getEnvRunner(ctx).fetch(req);

// Create dev app
if (ctx.nitro.options.dev && !ctx.devApp) {
ctx.devApp = new NitroDevApp(ctx.nitro);
}

// Cleanup resources after close {
ctx.nitro.hooks.hook("close", async () => {
if (ctx._envRunner) {
await ctx._envRunner.close();
}
});
}

function getEntry(input: InputOption | undefined): string | undefined {
Expand Down
4 changes: 2 additions & 2 deletions src/build/vite/types.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { OutputBundle } from "rollup";
import type { getViteRollupConfig } from "./rollup.ts";
import type { DevWorker, Nitro, NitroConfig, NitroModule } from "nitro/types";
import type { EnvRunner, Nitro, NitroConfig, NitroModule } from "nitro/types";
import type { NitroDevApp } from "../../dev/app.ts";

declare module "vite" {
Expand Down Expand Up @@ -60,12 +60,12 @@ export interface NitroPluginContext {
nitro?: Nitro;
pluginConfig: NitroPluginConfig;
rollupConfig?: ReturnType<typeof getViteRollupConfig>;
devWorker?: DevWorker;
devApp?: NitroDevApp;
services: Record<string, ServiceConfig>;

_isRolldown?: boolean;
_initialized?: boolean;
_envRunner?: EnvRunner;
_publicDistDir?: string;
_entryPoints: Record<string, string>;
_serviceBundles: Record<string, OutputBundle>;
Expand Down
2 changes: 1 addition & 1 deletion src/dev/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { stat } from "node:fs/promises";
import { createReadStream } from "node:fs";
import { createGzip, createBrotliCompress } from "node:zlib";
import { createVFSHandler } from "./vfs.ts";
import { createHTTPProxy } from "./proxy.ts";
import { createHTTPProxy } from "../runner/proxy.ts";

import devErrorHandler, {
defaultHandler as devErrorHandlerInternal,
Expand Down
38 changes: 19 additions & 19 deletions src/dev/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@ import type { IncomingMessage, OutgoingMessage } from "node:http";
import type { Duplex } from "node:stream";
import type { FSWatcher } from "chokidar";
import type { ServerOptions, Server } from "srvx";
import { NodeDevWorker } from "./worker.ts";
import type { DevWorkerData } from "./worker.ts";
import { NodeEnvRunner } from "../runner/node.ts";
import type { EnvRunnerData } from "../runner/node.ts";
import type {
Nitro,
DevMessageListener,
DevRPCHooks,
DevWorker,
RunnerMessageListener,
RunnerRPCHooks,
EnvRunner,
} from "nitro/types";

import { HTTPError } from "h3";
Expand All @@ -26,17 +26,17 @@ export function createDevServer(nitro: Nitro): NitroDevServer {
return new NitroDevServer(nitro);
}

export class NitroDevServer extends NitroDevApp implements DevRPCHooks {
export class NitroDevServer extends NitroDevApp implements RunnerRPCHooks {
#entry: string;
#workerData: DevWorkerData = {};
#workerData: EnvRunnerData = {};
#listeners: Server[] = [];
#watcher?: FSWatcher;
#workers: DevWorker[] = [];
#workers: EnvRunner[] = [];
#workerIdCtr: number = 0;
#workerError?: unknown;
#building?: boolean = true; // Assume initial build will start soon
#buildError?: unknown;
#messageListeners: Set<DevMessageListener> = new Set();
#messageListeners: Set<RunnerMessageListener> = new Set();

constructor(nitro: Nitro) {
super(nitro, async (event) => {
Expand Down Expand Up @@ -115,6 +115,12 @@ export class NitroDevServer extends NitroDevApp implements DevRPCHooks {
statusText: "No worker available.",
});
}
if (!worker.upgrade) {
throw new HTTPError({
status: 501,
statusText: "Worker does not support upgrades.",
});
}
return worker.upgrade(req, socket, head);
}

Expand Down Expand Up @@ -157,16 +163,10 @@ export class NitroDevServer extends NitroDevApp implements DevRPCHooks {
for (const worker of this.#workers) {
worker.close();
}
const worker = new NodeDevWorker({
const worker = new NodeEnvRunner({
name: `Nitro_${this.#workerIdCtr++}`,
entry: this.#entry,
data: {
...this.#workerData,
globals: {
__NITRO_RUNTIME_CONFIG__: this.nitro.options.runtimeConfig,
...this.#workerData.globals,
},
},
data: this.#workerData,
hooks: {
onClose: (worker, cause) => {
this.#workerError = cause;
Expand Down Expand Up @@ -196,14 +196,14 @@ export class NitroDevServer extends NitroDevApp implements DevRPCHooks {
}
}

onMessage(listener: DevMessageListener) {
onMessage(listener: RunnerMessageListener) {
this.#messageListeners.add(listener);
for (const worker of this.#workers) {
worker.onMessage(listener);
}
}

offMessage(listener: DevMessageListener) {
offMessage(listener: RunnerMessageListener) {
this.#messageListeners.delete(listener);
for (const worker of this.#workers) {
worker.offMessage(listener);
Expand Down
34 changes: 17 additions & 17 deletions src/dev/worker.ts → src/runner/node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ import type { IncomingMessage, OutgoingMessage } from "node:http";
import type { Duplex } from "node:stream";
import type { HTTPProxy } from "./proxy.ts";
import type {
DevMessageListener,
DevWorker,
RunnerMessageListener,
EnvRunner,
WorkerAddress,
WorkerHooks,
} from "nitro/types";
Expand All @@ -15,18 +15,17 @@ import consola from "consola";
import { isCI, isTest } from "std-env";
import { createHTTPProxy, fetchAddress } from "./proxy.ts";

export interface DevWorkerData {
export interface EnvRunnerData {
name?: string;
globals?: Record<string, unknown>;
[key: string]: unknown;
}

export class NodeDevWorker implements DevWorker {
export class NodeEnvRunner implements EnvRunner {
closed: boolean = false;

#name: string;
#entry: string;
#data?: DevWorkerData;
#data?: EnvRunnerData;
#hooks: Partial<WorkerHooks>;
#worker?: Worker & { _exitCode?: number };
#address?: WorkerAddress;
Expand All @@ -35,14 +34,14 @@ export class NodeDevWorker implements DevWorker {

constructor(opts: {
name: string;
hooks: WorkerHooks;
entry: string;
data?: DevWorkerData;
hooks?: WorkerHooks;
data?: EnvRunnerData;
}) {
this.#name = opts.name;
this.#entry = opts.entry;
this.#data = opts.data;
this.#hooks = opts.hooks;
this.#hooks = opts.hooks || {};

this.#proxy = createHTTPProxy();
this.#messageListeners = new Set();
Expand All @@ -65,7 +64,9 @@ export class NodeDevWorker implements DevWorker {
await new Promise((r) => setTimeout(r, 100 * Math.pow(2, i)));
}
if (!(this.#address && this.#proxy)) {
return new Response("Dev worker is unavailable", { status: 503 });
return new Response("Node env runner worker is unavailable", {
status: 503,
});
}
return fetchAddress(this.#address, input, init);
}
Expand All @@ -91,17 +92,17 @@ export class NodeDevWorker implements DevWorker {
sendMessage(message: unknown) {
if (!this.#worker) {
throw new Error(
"Dev worker should be initialized before sending messages."
"Node env worker should be initialized before sending messages."
);
}
this.#worker.postMessage(message);
}

onMessage(listener: DevMessageListener) {
onMessage(listener: RunnerMessageListener) {
this.#messageListeners.add(listener);
}

offMessage(listener: DevMessageListener) {
offMessage(listener: RunnerMessageListener) {
this.#messageListeners.delete(listener);
}

Expand All @@ -121,7 +122,7 @@ export class NodeDevWorker implements DevWorker {
[Symbol.for("nodejs.util.inspect.custom")]() {
// eslint-disable-next-line unicorn/no-nested-ternary
const status = this.closed ? "closed" : this.ready ? "ready" : "pending";
return `NodeDevWorker#${this.#name}(${status})`;
return `NodeEnvRunner#${this.#name}(${status})`;
}

// #endregion
Expand Down Expand Up @@ -197,9 +198,8 @@ export class NodeDevWorker implements DevWorker {
const gracefulShutdownTimeoutMs =
Number.parseInt(process.env.NITRO_SHUTDOWN_TIMEOUT || "", 10) || 5000;
const timeout = setTimeout(() => {
if (process.env.DEBUG) {
consola.warn(`force closing dev worker...`);
}
consola.warn(`force closing node env runner worker...`);
resolve();
}, gracefulShutdownTimeoutMs);

this.#worker?.on("message", (message) => {
Expand Down
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -155,11 +155,6 @@ parentPort.on("message", (payload) => {

async function reload() {
try {
// Apply globals
for (const [key, value] of Object.entries(workerData.globals || {})) {
globalThis[key] = value;
}
// Reload all envs
await Promise.all(Object.values(envs).map((env) => env?.reload()));
} catch (error) {
console.error(error);
Expand Down Expand Up @@ -195,6 +190,10 @@ if (workerData.server) {
parentPort.on("message", async (message) => {
if (message?.type === "full-reload") {
await reload();
} else if (message?.event === "shutdown") {
server.close(() => {
parentPort.postMessage({ event: "exit" });
});
}
});
await listen(server);
Expand Down Expand Up @@ -228,6 +227,32 @@ function httpError(status, message) {
}

async function renderError(req, error) {
if (req.headers.get("accept")?.includes("application/json")) {
return new Response(
JSON.stringify(
{
status: error.status || 500,
name: error.name || "Error",
message: error.message,
stack: (error.stack || "")
.split("\n")
.splice(1)
.map((l) => l.trim()),
},
null,
2
),
{
status: error.status || 500,
headers: {
"Content-Type": "application/json",
"Cache-Control": "no-store, max-age=0, must-revalidate",
Pragma: "no-cache",
Expires: "0",
},
}
);
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
const { Youch } = await import("youch");
const youch = new Youch();
return new Response(await youch.toHTML(error), {
Expand Down
Loading
Loading