Skip to content

Commit 4efaffe

Browse files
pi0RihanArfan
authored andcommitted
perf: use vfs for nitro app dynamic code (#4251)
1 parent 36937eb commit 4efaffe

5 files changed

Lines changed: 234 additions & 144 deletions

File tree

src/build/virtual/_all.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import type { Nitro } from "nitro/types";
22

3+
import app from "./app.ts";
34
import database from "./database.ts";
45
import errorHandler from "./error-handler.ts";
56
import featureFlags from "./feature-flags.ts";
@@ -22,6 +23,7 @@ type VirtualTemplate = {
2223

2324
export function virtualTemplates(nitro: Nitro, _polyfills: string[]): VirtualTemplate[] {
2425
const nitroTemplates = [
26+
app,
2527
database,
2628
errorHandler,
2729
featureFlags,

src/build/virtual/app.ts

Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
import type { Nitro } from "nitro/types";
2+
3+
export default function app(nitro: Nitro) {
4+
return {
5+
id: "#nitro/virtual/app",
6+
template: () => {
7+
const hasRoutes = nitro.routing.routes.hasRoutes();
8+
const hasRouteRules = nitro.routing.routeRules.hasRoutes();
9+
const hasRoutedMiddleware = nitro.routing.routedMiddleware.hasRoutes();
10+
const hasGlobalMiddleware = nitro.routing.globalMiddleware.length > 0;
11+
const hasPlugins = nitro.options.plugins.length > 0;
12+
const hasHooks = nitro.options.features?.runtimeHooks ?? hasPlugins;
13+
const hasGetMiddleware = hasRouteRules || hasRoutedMiddleware;
14+
const hasAsyncContext = !!nitro.options.experimental.asyncContext;
15+
16+
const routingImports = [
17+
hasRoutes && "findRoute",
18+
hasRoutedMiddleware && "findRoutedMiddleware",
19+
hasGlobalMiddleware && "globalMiddleware",
20+
].filter(Boolean);
21+
22+
const imports: string[] = [];
23+
const code: string[] = [];
24+
25+
imports.push(
26+
`import { H3Core } from "h3";`,
27+
`import errorHandler from "#nitro/virtual/error-handler";`
28+
);
29+
30+
// --- createNitroApp() ---
31+
32+
code.push(``, `export function createNitroApp() {`);
33+
34+
if (hasHooks) {
35+
imports.push(`import { HookableCore } from "hookable";`);
36+
code.push(` const hooks = new HookableCore();`);
37+
}
38+
39+
code.push(``, `const captureError = (error, errorCtx) => {`);
40+
if (hasHooks) {
41+
code.push(
42+
` const promise = hooks.callHook("error", error, errorCtx)?.catch?.((hookError) => {`,
43+
` console.error("Error while capturing another error", hookError);`,
44+
` });`
45+
);
46+
}
47+
code.push(
48+
` if (errorCtx?.event) {`,
49+
` const errors = errorCtx.event.req.context?.nitro?.errors;`,
50+
` if (errors) {`,
51+
` errors.push({ error, context: errorCtx });`,
52+
` }`
53+
);
54+
if (hasHooks) {
55+
code.push(
56+
` if (promise && typeof errorCtx.event.req.waitUntil === "function") {`,
57+
` errorCtx.event.req.waitUntil(promise);`,
58+
` }`
59+
);
60+
}
61+
code.push(` }`, ` };`);
62+
63+
code.push(``, ` const h3App = createH3App({`, ` onError(error, event) {`);
64+
if (hasHooks) {
65+
code.push(` captureError(error, { event });`);
66+
}
67+
code.push(` return errorHandler(error, event);`, ` },`, ` });`);
68+
69+
if (hasHooks) {
70+
code.push(
71+
``,
72+
` h3App.config.onRequest = (event) => {`,
73+
` return hooks.callHook("request", event)?.catch?.((error) => {`,
74+
` captureError(error, { event, tags: ["request"] });`,
75+
` });`,
76+
` };`,
77+
` h3App.config.onResponse = (res, event) => {`,
78+
` return hooks.callHook("response", res, event)?.catch?.((error) => {`,
79+
` captureError(error, { event, tags: ["response"] });`,
80+
` });`,
81+
` };`
82+
);
83+
}
84+
85+
code.push(
86+
``,
87+
` let appHandler = (req) => {`,
88+
` req.context ||= {};`,
89+
` req.context.nitro = req.context.nitro || { errors: [] };`,
90+
` return h3App.fetch(req);`,
91+
` };`
92+
);
93+
94+
if (hasAsyncContext) {
95+
imports.push(`import { nitroAsyncContext } from "#nitro/runtime/context";`);
96+
code.push(
97+
``,
98+
` const originalHandler = appHandler;`,
99+
` appHandler = (req) => {`,
100+
` return nitroAsyncContext.callAsync({ request: req }, () => originalHandler(req));`,
101+
` };`
102+
);
103+
}
104+
105+
code.push(
106+
``,
107+
` return {`,
108+
` fetch: appHandler,`,
109+
` h3: h3App,`,
110+
` hooks: ${hasHooks ? "hooks" : "undefined"},`,
111+
` captureError,`,
112+
` };`,
113+
`}`
114+
);
115+
116+
// --- initNitroPlugins() ---
117+
118+
code.push(``, `export function initNitroPlugins(app) {`);
119+
if (hasPlugins) {
120+
imports.push(`import { plugins } from "#nitro/virtual/plugins";`);
121+
code.push(
122+
` for (const plugin of plugins) {`,
123+
` try {`,
124+
` plugin(app);`,
125+
` } catch (error) {`,
126+
` app.captureError?.(error, { tags: ["plugin"] });`,
127+
` throw error;`,
128+
` }`,
129+
` }`
130+
);
131+
}
132+
code.push(` return app;`, `}`);
133+
134+
// --- createH3App() ---
135+
136+
if (routingImports.length) {
137+
imports.push(`import { ${routingImports.join(", ")} } from "#nitro/virtual/routing";`);
138+
}
139+
140+
code.push(``, `function createH3App(config) {`, ` const h3App = new H3Core(config);`);
141+
if (hasRoutes) {
142+
code.push(
143+
` h3App["~findRoute"] = (event) => findRoute(event.req.method, event.url.pathname);`
144+
);
145+
}
146+
if (hasGlobalMiddleware) {
147+
code.push(` h3App["~middleware"].push(...globalMiddleware);`);
148+
}
149+
if (hasGetMiddleware) {
150+
code.push(
151+
` h3App["~getMiddleware"] = (event, route) => {`,
152+
` const pathname = event.url.pathname;`,
153+
` const method = event.req.method;`,
154+
` const middleware = [];`
155+
);
156+
if (hasRouteRules) {
157+
imports.push(`import { getRouteRules } from "#nitro/runtime/app";`);
158+
code.push(
159+
` const routeRules = getRouteRules(method, pathname);`,
160+
` event.context.routeRules = routeRules?.routeRules;`,
161+
` if (routeRules?.routeRuleMiddleware.length) {`,
162+
` middleware.push(...routeRules.routeRuleMiddleware);`,
163+
` }`
164+
);
165+
}
166+
if (hasGlobalMiddleware) {
167+
code.push(` middleware.push(...h3App["~middleware"]);`);
168+
}
169+
if (hasRoutedMiddleware) {
170+
code.push(
171+
` middleware.push(...findRoutedMiddleware(method, pathname).map((r) => r.data));`
172+
);
173+
}
174+
if (hasRoutes) {
175+
code.push(
176+
` if (route?.data?.middleware?.length) {`,
177+
` middleware.push(...route.data.middleware);`,
178+
` }`
179+
);
180+
}
181+
code.push(` return middleware;`, ` };`);
182+
}
183+
code.push(` return h3App;`, `}`);
184+
185+
return [...imports, ...code].join("\n");
186+
},
187+
};
188+
}

src/runtime/internal/app.ts

Lines changed: 6 additions & 141 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,12 @@
1-
import type {
2-
CaptureError,
3-
MatchedRouteRules,
4-
NitroApp,
5-
NitroAsyncContext,
6-
NitroRuntimeHooks,
7-
} from "nitro/types";
1+
import type { MatchedRouteRules, NitroApp, NitroRuntimeHooks } from "nitro/types";
82
import type { ServerRequest, ServerRequestContext } from "srvx";
9-
import type { H3Config, H3EventContext, Middleware, WebSocketHooks } from "h3";
10-
import { H3Core, toRequest } from "h3";
3+
import type { H3EventContext, Middleware, WebSocketHooks } from "h3";
4+
import { toRequest } from "h3";
115
import { HookableCore } from "hookable";
12-
import { nitroAsyncContext } from "./context.ts";
136

147
// IMPORTANT: virtual imports and user code should be imported last to avoid initialization order issues
15-
import errorHandler from "#nitro/virtual/error-handler";
16-
import { plugins } from "#nitro/virtual/plugins";
17-
import {
18-
findRoute,
19-
findRouteRules,
20-
globalMiddleware,
21-
findRoutedMiddleware,
22-
} from "#nitro/virtual/routing";
23-
import {
24-
hasRouteRules,
25-
hasRoutedMiddleware,
26-
hasGlobalMiddleware,
27-
hasRoutes,
28-
hasHooks,
29-
hasPlugins,
30-
} from "#nitro/virtual/feature-flags";
8+
import { findRouteRules } from "#nitro/virtual/routing";
9+
import { createNitroApp, initNitroPlugins } from "#nitro/virtual/app";
3110

3211
declare global {
3312
var __nitro__:
@@ -45,9 +24,7 @@ export function useNitroApp(): NitroApp {
4524
instance = (useNitroApp as any)._instance = createNitroApp();
4625
globalThis.__nitro__ = globalThis.__nitro__ || {};
4726
globalThis.__nitro__[APP_ID] = instance;
48-
if (hasPlugins) {
49-
initNitroPlugins(instance);
50-
}
27+
initNitroPlugins(instance);
5128
return instance;
5229
}
5330

@@ -93,118 +70,6 @@ export function fetch(
9370
return globalThis.fetch(resource, init);
9471
}
9572

96-
function createNitroApp(): NitroApp {
97-
const hooks = hasHooks ? new HookableCore<NitroRuntimeHooks>() : undefined;
98-
99-
const captureError: CaptureError = (error, errorCtx) => {
100-
const promise =
101-
hasHooks &&
102-
hooks!.callHook("error", error, errorCtx)?.catch?.((hookError: any) => {
103-
console.error("Error while capturing another error", hookError);
104-
});
105-
if (errorCtx?.event) {
106-
const errors = errorCtx.event.req.context?.nitro?.errors;
107-
if (errors) {
108-
errors.push({ error, context: errorCtx });
109-
}
110-
if (hasHooks && promise && typeof errorCtx.event.req.waitUntil === "function") {
111-
errorCtx.event.req.waitUntil(promise);
112-
}
113-
}
114-
};
115-
116-
const h3App = createH3App({
117-
onError(error, event) {
118-
hasHooks && captureError(error, { event });
119-
return errorHandler(error, event);
120-
},
121-
});
122-
123-
if (hasHooks) {
124-
h3App.config.onRequest = (event) => {
125-
return hooks!.callHook("request", event)?.catch?.((error: any) => {
126-
captureError(error, { event, tags: ["request"] });
127-
});
128-
};
129-
h3App.config.onResponse = (res, event) => {
130-
return hooks!.callHook("response", res, event)?.catch?.((error: any) => {
131-
captureError(error, { event, tags: ["response"] });
132-
});
133-
};
134-
}
135-
136-
let appHandler = (req: ServerRequest): Response | Promise<Response> => {
137-
req.context ||= {};
138-
req.context.nitro = req.context.nitro || { errors: [] };
139-
return h3App.fetch(req);
140-
};
141-
142-
// Experimental async context support
143-
if (import.meta._asyncContext) {
144-
const originalHandler = appHandler;
145-
appHandler = (req: ServerRequest): Promise<Response> => {
146-
const asyncCtx: NitroAsyncContext = { request: req as Request };
147-
return nitroAsyncContext.callAsync(asyncCtx, () => originalHandler(req));
148-
};
149-
}
150-
151-
const app: NitroApp = {
152-
fetch: appHandler,
153-
h3: h3App,
154-
hooks,
155-
captureError,
156-
};
157-
158-
return app;
159-
}
160-
161-
function initNitroPlugins(app: NitroApp) {
162-
for (const plugin of plugins) {
163-
try {
164-
plugin(app as NitroApp & { hooks: NonNullable<NitroApp["hooks"]> });
165-
} catch (error: any) {
166-
app.captureError?.(error, { tags: ["plugin"] });
167-
throw error;
168-
}
169-
}
170-
return app;
171-
}
172-
173-
function createH3App(config: H3Config) {
174-
// Create H3 app
175-
const h3App = new H3Core(config);
176-
177-
// Compiled route matching
178-
hasRoutes && (h3App["~findRoute"] = (event) => findRoute(event.req.method, event.url.pathname));
179-
180-
hasGlobalMiddleware && h3App["~middleware"].push(...globalMiddleware);
181-
182-
if (hasRouteRules || hasRoutedMiddleware) {
183-
h3App["~getMiddleware"] = (event, route) => {
184-
const needsRouting = hasRouteRules || hasRoutedMiddleware;
185-
const pathname = needsRouting ? event.url.pathname : undefined;
186-
const method = needsRouting ? event.req.method : undefined;
187-
const middleware: Middleware[] = [];
188-
if (hasRouteRules) {
189-
const routeRules = getRouteRules(method!, pathname!);
190-
event.context.routeRules = routeRules?.routeRules;
191-
if (routeRules?.routeRuleMiddleware.length) {
192-
middleware.push(...routeRules.routeRuleMiddleware);
193-
}
194-
}
195-
hasGlobalMiddleware && middleware.push(...h3App["~middleware"]);
196-
hasRoutedMiddleware &&
197-
middleware.push(...findRoutedMiddleware(method!, pathname!).map((r) => r.data));
198-
if (hasRoutes && route?.data?.middleware?.length) {
199-
middleware.push(...route.data.middleware);
200-
}
201-
return middleware;
202-
};
203-
}
204-
205-
return h3App;
206-
}
207-
20873
export function getRouteRules(
20974
method: string,
21075
pathname: string

0 commit comments

Comments
 (0)