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
2 changes: 2 additions & 0 deletions src/build/virtual/_all.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { Nitro } from "nitro/types";

import app from "./app.ts";
import database from "./database.ts";
import errorHandler from "./error-handler.ts";
import featureFlags from "./feature-flags.ts";
Expand All @@ -22,6 +23,7 @@ type VirtualTemplate = {

export function virtualTemplates(nitro: Nitro, _polyfills: string[]): VirtualTemplate[] {
const nitroTemplates = [
app,
database,
errorHandler,
featureFlags,
Expand Down
188 changes: 188 additions & 0 deletions src/build/virtual/app.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
import type { Nitro } from "nitro/types";

export default function app(nitro: Nitro) {
return {
id: "#nitro/virtual/app",
template: () => {
const hasRoutes = nitro.routing.routes.hasRoutes();
const hasRouteRules = nitro.routing.routeRules.hasRoutes();
const hasRoutedMiddleware = nitro.routing.routedMiddleware.hasRoutes();
const hasGlobalMiddleware = nitro.routing.globalMiddleware.length > 0;
const hasPlugins = nitro.options.plugins.length > 0;
const hasHooks = nitro.options.features?.runtimeHooks ?? hasPlugins;
const hasGetMiddleware = hasRouteRules || hasRoutedMiddleware;
const hasAsyncContext = !!nitro.options.experimental.asyncContext;

const routingImports = [
hasRoutes && "findRoute",
hasRoutedMiddleware && "findRoutedMiddleware",
hasGlobalMiddleware && "globalMiddleware",
].filter(Boolean);

const imports: string[] = [];
const code: string[] = [];

imports.push(
`import { H3Core } from "h3";`,
`import errorHandler from "#nitro/virtual/error-handler";`
);

// --- createNitroApp() ---

code.push(``, `export function createNitroApp() {`);

if (hasHooks) {
imports.push(`import { HookableCore } from "hookable";`);
code.push(` const hooks = new HookableCore();`);
}

code.push(``, `const captureError = (error, errorCtx) => {`);
if (hasHooks) {
code.push(
` const promise = hooks.callHook("error", error, errorCtx)?.catch?.((hookError) => {`,
` console.error("Error while capturing another error", hookError);`,
` });`
);
}
code.push(
` if (errorCtx?.event) {`,
` const errors = errorCtx.event.req.context?.nitro?.errors;`,
` if (errors) {`,
` errors.push({ error, context: errorCtx });`,
` }`
);
if (hasHooks) {
code.push(
` if (promise && typeof errorCtx.event.req.waitUntil === "function") {`,
` errorCtx.event.req.waitUntil(promise);`,
` }`
);
}
code.push(` }`, ` };`);

code.push(``, ` const h3App = createH3App({`, ` onError(error, event) {`);
if (hasHooks) {
code.push(` captureError(error, { event });`);
}
code.push(` return errorHandler(error, event);`, ` },`, ` });`);

if (hasHooks) {
code.push(
``,
` h3App.config.onRequest = (event) => {`,
` return hooks.callHook("request", event)?.catch?.((error) => {`,
` captureError(error, { event, tags: ["request"] });`,
` });`,
` };`,
` h3App.config.onResponse = (res, event) => {`,
` return hooks.callHook("response", res, event)?.catch?.((error) => {`,
` captureError(error, { event, tags: ["response"] });`,
` });`,
` };`
);
}

code.push(
``,
` let appHandler = (req) => {`,
` req.context ||= {};`,
` req.context.nitro = req.context.nitro || { errors: [] };`,
` return h3App.fetch(req);`,
` };`
);

if (hasAsyncContext) {
imports.push(`import { nitroAsyncContext } from "#nitro/runtime/context";`);
code.push(
``,
` const originalHandler = appHandler;`,
` appHandler = (req) => {`,
` return nitroAsyncContext.callAsync({ request: req }, () => originalHandler(req));`,
` };`
);
}

code.push(
``,
` return {`,
` fetch: appHandler,`,
` h3: h3App,`,
` hooks: ${hasHooks ? "hooks" : "undefined"},`,
` captureError,`,
` };`,
`}`
);

// --- initNitroPlugins() ---

code.push(``, `export function initNitroPlugins(app) {`);
if (hasPlugins) {
imports.push(`import { plugins } from "#nitro/virtual/plugins";`);
code.push(
` for (const plugin of plugins) {`,
` try {`,
` plugin(app);`,
` } catch (error) {`,
` app.captureError?.(error, { tags: ["plugin"] });`,
` throw error;`,
` }`,
` }`
);
}
code.push(` return app;`, `}`);

// --- createH3App() ---

if (routingImports.length) {
imports.push(`import { ${routingImports.join(", ")} } from "#nitro/virtual/routing";`);
}

code.push(``, `function createH3App(config) {`, ` const h3App = new H3Core(config);`);
if (hasRoutes) {
code.push(
` h3App["~findRoute"] = (event) => findRoute(event.req.method, event.url.pathname);`
);
}
if (hasGlobalMiddleware) {
code.push(` h3App["~middleware"].push(...globalMiddleware);`);
}
if (hasGetMiddleware) {
code.push(
` h3App["~getMiddleware"] = (event, route) => {`,
` const pathname = event.url.pathname;`,
` const method = event.req.method;`,
` const middleware = [];`
);
if (hasRouteRules) {
imports.push(`import { getRouteRules } from "#nitro/runtime/app";`);
code.push(
` const routeRules = getRouteRules(method, pathname);`,
` event.context.routeRules = routeRules?.routeRules;`,
` if (routeRules?.routeRuleMiddleware.length) {`,
` middleware.push(...routeRules.routeRuleMiddleware);`,
` }`
);
}
if (hasGlobalMiddleware) {
code.push(` middleware.push(...h3App["~middleware"]);`);
}
if (hasRoutedMiddleware) {
code.push(
` middleware.push(...findRoutedMiddleware(method, pathname).map((r) => r.data));`
);
}
if (hasRoutes) {
code.push(
` if (route?.data?.middleware?.length) {`,
` middleware.push(...route.data.middleware);`,
` }`
);
}
code.push(` return middleware;`, ` };`);
}
code.push(` return h3App;`, `}`);

return [...imports, ...code].join("\n");
},
};
}
147 changes: 6 additions & 141 deletions src/runtime/internal/app.ts
Original file line number Diff line number Diff line change
@@ -1,33 +1,12 @@
import type {
CaptureError,
MatchedRouteRules,
NitroApp,
NitroAsyncContext,
NitroRuntimeHooks,
} from "nitro/types";
import type { MatchedRouteRules, NitroApp, NitroRuntimeHooks } from "nitro/types";
import type { ServerRequest, ServerRequestContext } from "srvx";
import type { H3Config, H3EventContext, Middleware, WebSocketHooks } from "h3";
import { H3Core, toRequest } from "h3";
import type { H3EventContext, Middleware, WebSocketHooks } from "h3";
import { toRequest } from "h3";
import { HookableCore } from "hookable";
import { nitroAsyncContext } from "./context.ts";

// IMPORTANT: virtual imports and user code should be imported last to avoid initialization order issues
import errorHandler from "#nitro/virtual/error-handler";
import { plugins } from "#nitro/virtual/plugins";
import {
findRoute,
findRouteRules,
globalMiddleware,
findRoutedMiddleware,
} from "#nitro/virtual/routing";
import {
hasRouteRules,
hasRoutedMiddleware,
hasGlobalMiddleware,
hasRoutes,
hasHooks,
hasPlugins,
} from "#nitro/virtual/feature-flags";
import { findRouteRules } from "#nitro/virtual/routing";
import { createNitroApp, initNitroPlugins } from "#nitro/virtual/app";

declare global {
var __nitro__:
Expand All @@ -45,9 +24,7 @@ export function useNitroApp(): NitroApp {
instance = (useNitroApp as any)._instance = createNitroApp();
globalThis.__nitro__ = globalThis.__nitro__ || {};
globalThis.__nitro__[APP_ID] = instance;
if (hasPlugins) {
initNitroPlugins(instance);
}
initNitroPlugins(instance);
return instance;
}

Expand Down Expand Up @@ -93,118 +70,6 @@ export function fetch(
return globalThis.fetch(resource, init);
}

function createNitroApp(): NitroApp {
const hooks = hasHooks ? new HookableCore<NitroRuntimeHooks>() : undefined;

const captureError: CaptureError = (error, errorCtx) => {
const promise =
hasHooks &&
hooks!.callHook("error", error, errorCtx)?.catch?.((hookError: any) => {
console.error("Error while capturing another error", hookError);
});
if (errorCtx?.event) {
const errors = errorCtx.event.req.context?.nitro?.errors;
if (errors) {
errors.push({ error, context: errorCtx });
}
if (hasHooks && promise && typeof errorCtx.event.req.waitUntil === "function") {
errorCtx.event.req.waitUntil(promise);
}
}
};

const h3App = createH3App({
onError(error, event) {
hasHooks && captureError(error, { event });
return errorHandler(error, event);
},
});

if (hasHooks) {
h3App.config.onRequest = (event) => {
return hooks!.callHook("request", event)?.catch?.((error: any) => {
captureError(error, { event, tags: ["request"] });
});
};
h3App.config.onResponse = (res, event) => {
return hooks!.callHook("response", res, event)?.catch?.((error: any) => {
captureError(error, { event, tags: ["response"] });
});
};
}

let appHandler = (req: ServerRequest): Response | Promise<Response> => {
req.context ||= {};
req.context.nitro = req.context.nitro || { errors: [] };
return h3App.fetch(req);
};

// Experimental async context support
if (import.meta._asyncContext) {
const originalHandler = appHandler;
appHandler = (req: ServerRequest): Promise<Response> => {
const asyncCtx: NitroAsyncContext = { request: req as Request };
return nitroAsyncContext.callAsync(asyncCtx, () => originalHandler(req));
};
}

const app: NitroApp = {
fetch: appHandler,
h3: h3App,
hooks,
captureError,
};

return app;
}

function initNitroPlugins(app: NitroApp) {
for (const plugin of plugins) {
try {
plugin(app as NitroApp & { hooks: NonNullable<NitroApp["hooks"]> });
} catch (error: any) {
app.captureError?.(error, { tags: ["plugin"] });
throw error;
}
}
return app;
}

function createH3App(config: H3Config) {
// Create H3 app
const h3App = new H3Core(config);

// Compiled route matching
hasRoutes && (h3App["~findRoute"] = (event) => findRoute(event.req.method, event.url.pathname));

hasGlobalMiddleware && h3App["~middleware"].push(...globalMiddleware);

if (hasRouteRules || hasRoutedMiddleware) {
h3App["~getMiddleware"] = (event, route) => {
const needsRouting = hasRouteRules || hasRoutedMiddleware;
const pathname = needsRouting ? event.url.pathname : undefined;
const method = needsRouting ? event.req.method : undefined;
const middleware: Middleware[] = [];
if (hasRouteRules) {
const routeRules = getRouteRules(method!, pathname!);
event.context.routeRules = routeRules?.routeRules;
if (routeRules?.routeRuleMiddleware.length) {
middleware.push(...routeRules.routeRuleMiddleware);
}
}
hasGlobalMiddleware && middleware.push(...h3App["~middleware"]);
hasRoutedMiddleware &&
middleware.push(...findRoutedMiddleware(method!, pathname!).map((r) => r.data));
if (hasRoutes && route?.data?.middleware?.length) {
middleware.push(...route.data.middleware);
}
return middleware;
};
}

return h3App;
}

export function getRouteRules(
method: string,
pathname: string
Expand Down
Loading
Loading