Skip to content

Commit

Permalink
Merge branch 'main' into feat/firebase-functions-v2
Browse files Browse the repository at this point in the history
  • Loading branch information
JamieCurnow authored Jul 13, 2023
2 parents 4c4a81b + bb79391 commit a22b4f1
Show file tree
Hide file tree
Showing 22 changed files with 158 additions and 33 deletions.
37 changes: 37 additions & 0 deletions docs/content/1.guide/7.plugins.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,3 +45,40 @@ export default defineNuxtConfig({
})
```
::

## Examples

### Graceful Shutdown

You can use plugins to register a hook that resolves when Nitro is closed.

```ts
export default defineNitroPlugin((nitro) => {
nitro.hooks.hookOnce("close", async () => {
// Will run when nitro is closed
console.log("Closing nitro server...")
await new Promise((resolve) => setTimeout(resolve, 500));
console.log("Task is done!");
});
})
```

### Renderer Response

You can use plugins to register a hook that modifies the renderer response.

::alert
This **only works** for render handler defined with [`renderer`](https://nitro.unjs.io/config#renderer) and won't be called for other api/server routes.
In [Nuxt](https://nuxt.com/) this hook will be called for Server Side Rendered pages
::

```ts
export default defineNitroPlugin((nitro) => {

nitro.hooks.hook('render:response', (response) => {
// Inspect or Modify the renderer response here
console.log(response)
})
})
```

46 changes: 46 additions & 0 deletions src/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -366,6 +366,12 @@ export async function loadOptions(
// Resolve plugin paths
options.plugins = options.plugins.map((p) => resolvePath(p, options));

// Export conditions
options.exportConditions = _resolveExportConditions(
options.exportConditions,
{ dev: options.dev, node: options.node }
);

// Add open-api endpoint
if (options.dev && options.experimental.openAPI) {
options.handlers.push({
Expand Down Expand Up @@ -458,3 +464,43 @@ export function normalizeRouteRules(
}
return normalizedRules;
}

function _resolveExportConditions(
conditions: string[] = [],
opts: { dev: boolean; node: boolean }
) {
const resolvedConditions: string[] = [];

// 1. Add dev or production
resolvedConditions.push(opts.dev ? "development" : "production");

// 2. Add user specified conditions
resolvedConditions.push(...conditions);

// 3. Add runtime conditions (node or web)
if (opts.node) {
resolvedConditions.push("node");
} else {
// https://runtime-keys.proposal.wintercg.org/
resolvedConditions.push(
"wintercg",
"worker",
"web",
"browser",
"workerd",
"edge-light",
"lagon",
"netlify",
"edge-routine",
"deno"
);
}

// 4. Add default conditions
resolvedConditions.push("import", "module", "default");

// Dedup with preserving order
return resolvedConditions.filter(
(c, i) => resolvedConditions.indexOf(c) === i
);
}
10 changes: 2 additions & 8 deletions src/presets/bun.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,8 @@ import { defineNitroPreset } from "../preset";
export const bun = defineNitroPreset({
extends: "node-server",
entry: "#internal/nitro/entries/bun",
externals: {
traceInclude: ["ofetch", "uncrypto", "node-fetch-native"].map((id) =>
resolvePathSync(id, {
url: import.meta.url,
conditions: ["bun"],
})
),
},
// https://bun.sh/docs/runtime/modules#resolution
exportConditions: ["bun", "worker", "module", "node", "default", "browser"],
commands: {
preview: "bun run ./server/index.mjs",
},
Expand Down
1 change: 1 addition & 0 deletions src/presets/cloudflare-module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import type { Nitro } from "../types";
export const cloudflareModule = defineNitroPreset({
extends: "base-worker",
entry: "#internal/nitro/entries/cloudflare-module",
exportConditions: ["workerd"],
commands: {
preview: "npx wrangler dev ./server/index.mjs --site ./public --local",
deploy: "npx wrangler deploy",
Expand Down
1 change: 1 addition & 0 deletions src/presets/cloudflare-pages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import type { Nitro } from "../types";
export const cloudflarePages = defineNitroPreset({
extends: "cloudflare",
entry: "#internal/nitro/entries/cloudflare-pages",
exportConditions: ["workerd"],
commands: {
preview: "npx wrangler pages dev ./",
deploy: "npx wrangler pages deploy ./",
Expand Down
1 change: 1 addition & 0 deletions src/presets/cloudflare.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import type { Nitro } from "../types";
export const cloudflare = defineNitroPreset({
extends: "base-worker",
entry: "#internal/nitro/entries/cloudflare",
exportConditions: ["workerd"],
commands: {
preview: "npx wrangler dev ./server/index.mjs --site ./public --local",
deploy: "npx wrangler deploy",
Expand Down
1 change: 1 addition & 0 deletions src/presets/deno-deploy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { defineNitroPreset } from "../preset";

export const denoDeploy = defineNitroPreset({
entry: "#internal/nitro/entries/deno-deploy",
exportConditions: ["deno"],
node: false,
noExternals: true,
serveStatic: "deno",
Expand Down
1 change: 1 addition & 0 deletions src/presets/deno-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { ImportMetaRe } from "../rollup/plugins/import-meta";
export const denoServer = defineNitroPreset({
extends: "node-server",
entry: "#internal/nitro/entries/deno-server",
exportConditions: ["deno"],
commands: {
preview: "deno task --config ./deno.json start",
},
Expand Down
1 change: 1 addition & 0 deletions src/presets/lagon.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export interface LagonFunctionConfig {
export const lagon = defineNitroPreset({
extends: "base-worker",
entry: "#internal/nitro/entries/lagon",
exportConditions: ["lagon"],
commands: {
preview: "npm run dev --prefix ./",
deploy: "npm run deploy --prefix ./",
Expand Down
1 change: 1 addition & 0 deletions src/presets/netlify.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ export const netlifyBuilder = defineNitroPreset({
export const netlifyEdge = defineNitroPreset({
extends: "base-worker",
entry: "#internal/nitro/entries/netlify-edge",
exportConditions: ["netlify"],
output: {
serverDir: "{{ rootDir }}/.netlify/edge-functions/server",
publicDir: "{{ rootDir }}/dist",
Expand Down
1 change: 1 addition & 0 deletions src/presets/vercel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ export const vercel = defineNitroPreset({
export const vercelEdge = defineNitroPreset({
extends: "base-worker",
entry: "#internal/nitro/entries/vercel-edge",
exportConditions: ["edge-light"],
output: {
dir: "{{ rootDir }}/.vercel/output",
serverDir: "{{ output.dir }}/functions/__nitro.func",
Expand Down
18 changes: 3 additions & 15 deletions src/rollup/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@ import { defu } from "defu";
// import terser from "@rollup/plugin-terser"; // TODO: Investigate jiti issue
import type { RollupWasmOptions } from "@rollup/plugin-wasm";
import commonjs from "@rollup/plugin-commonjs";
import { nodeResolve } from "@rollup/plugin-node-resolve";
import alias from "@rollup/plugin-alias";
import json from "@rollup/plugin-json";
import wasmPlugin from "@rollup/plugin-wasm";
import inject from "@rollup/plugin-inject";
import { nodeResolve } from "@rollup/plugin-node-resolve";
import { isWindows } from "std-env";
import { visualizer } from "rollup-plugin-visualizer";
import * as unenv from "unenv";
Expand Down Expand Up @@ -404,13 +404,7 @@ export const plugins = [
processCwd: nitro.options.rootDir,
exportsOnly: true,
},
exportConditions: [
"default",
nitro.options.dev ? "development" : "production",
"module",
"node",
"import",
],
exportConditions: nitro.options.exportConditions,
})
)
);
Expand All @@ -425,13 +419,7 @@ export const plugins = [
modulePaths: nitro.options.nodeModulesDirs,
// 'module' is intentionally not supported because of externals
mainFields: ["main"],
exportConditions: [
"default",
nitro.options.dev ? "development" : "production",
"module",
"node",
"import",
],
exportConditions: nitro.options.exportConditions,
})
);

Expand Down
8 changes: 4 additions & 4 deletions src/rollup/plugins/externals.ts
Original file line number Diff line number Diff line change
Expand Up @@ -181,10 +181,10 @@ export function externals(opts: NodeExternalsOptions): Plugin {
}

// Trace used files using nft
const _fileTrace = await nodeFileTrace(
[...trackedExternals],
opts.traceOptions
);
const _fileTrace = await nodeFileTrace([...trackedExternals], {
conditions: opts.exportConditions,
...opts.traceOptions,
});

// Read package.json with cache
const packageJSONCache = new Map(); // pkgDir => contents
Expand Down
13 changes: 13 additions & 0 deletions src/runtime/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,18 +62,31 @@ function createNitroApp(): NitroApp {
eventHandler((event) => {
// Init nitro context
event.context.nitro = event.context.nitro || {};

// Support platform context provided by local fetch
const envContext = (event.node.req as any).__unenv__;
if (envContext) {
Object.assign(event.context, envContext);
}

// Assign bound fetch to context
event.fetch = (req, init) =>
fetchWithEvent(event, req, init, { fetch: localFetch });
event.$fetch = ((req, init) =>
fetchWithEvent(event, req, init as RequestInit, {
fetch: $fetch,
})) as $Fetch<unknown, NitroFetchRequest>;

// https://github.com/unjs/nitro/issues/1420
event.waitUntil = (promise) => {
if (!event.context.nitro._waitUntilPromises) {
event.context.nitro._waitUntilPromises = [];
}
event.context.nitro._waitUntilPromises.push(promise);
if (envContext?.waitUntil) {
envContext.waitUntil(promise);
}
};
})
);

Expand Down
24 changes: 18 additions & 6 deletions src/runtime/cache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
defineEventHandler,
createEvent,
EventHandler,
isEvent,
} from "h3";
import type { H3Event } from "h3";
import { parseURL } from "ufo";
Expand Down Expand Up @@ -38,10 +39,10 @@ const defaultCacheOptions = {
maxAge: 1,
};

export function defineCachedFunction<T = any>(
fn: (...args) => T | Promise<T>,
export function defineCachedFunction<T, ArgsT extends unknown[] = unknown[]>(
fn: (...args: ArgsT) => T | Promise<T>,
opts: CacheOptions<T> = {}
) {
): (...args: ArgsT) => Promise<T> {
opts = { ...defaultCacheOptions, ...opts };

const pending: { [key: string]: Promise<T> } = {};
Expand All @@ -55,7 +56,8 @@ export function defineCachedFunction<T = any>(
async function get(
key: string,
resolver: () => T | Promise<T>,
shouldInvalidateCache?: boolean
shouldInvalidateCache?: boolean,
waitUntil?: (promise: Promise<void>) => void
): Promise<CacheEntry<T>> {
// Use extension for key to avoid conflicting with parent namespace (foo/bar and foo/bar/baz)
const cacheKey = [opts.base, group, name, key + ".json"]
Expand Down Expand Up @@ -110,9 +112,12 @@ export function defineCachedFunction<T = any>(
entry.integrity = integrity;
delete pending[key];
if (validate(entry)) {
useStorage()
const promise = useStorage()
.setItem(cacheKey, entry)
.catch((error) => console.error("[nitro] [cache]", error));
if (waitUntil) {
waitUntil(promise);
}
}
}
};
Expand All @@ -136,7 +141,14 @@ export function defineCachedFunction<T = any>(
}
const key = await (opts.getKey || getKey)(...args);
const shouldInvalidateCache = opts.shouldInvalidateCache?.(...args);
const entry = await get(key, () => fn(...args), shouldInvalidateCache);
const waitUntil =
args[0] && isEvent(args[0]) ? args[0].waitUntil : undefined;
const entry = await get(
key,
() => fn(...args),
shouldInvalidateCache,
waitUntil
);
let value = entry.value;
if (opts.transform) {
value = (await opts.transform(entry, ...args)) || value;
Expand Down
1 change: 1 addition & 0 deletions src/runtime/entries/cloudflare-module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ export default {
return nitroApp.localFetch(url.pathname + url.search, {
context: {
cf: (request as any).cf,
waitUntil: (promise) => context.waitUntil(promise),
cloudflare: {
request,
env,
Expand Down
1 change: 1 addition & 0 deletions src/runtime/entries/cloudflare-pages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ export default {
return nitroApp.localFetch(url.pathname + url.search, {
context: {
cf: request.cf,
waitUntil: (promise) => context.waitUntil(promise),
cloudflare: {
request,
env,
Expand Down
1 change: 1 addition & 0 deletions src/runtime/entries/cloudflare.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ async function handleEvent(event: FetchEvent) {
context: {
// https://developers.cloudflare.com/workers//runtime-apis/request#incomingrequestcfproperties
cf: (event.request as any).cf,
waitUntil: (promise) => event.waitUntil(promise),
},
url: url.pathname + url.search,
host: url.hostname,
Expand Down
7 changes: 7 additions & 0 deletions src/types/h3.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,13 @@ declare module "h3" {
fetch: H3EventFetch;
/** @experimental Calls fetch with same context and request headers */
$fetch: H3Event$Fetch;
/** @experimental See https://github.com/unjs/nitro/issues/1420 */
waitUntil: (promise: Promise<unknown>) => void;
}
interface H3Context {
nitro: {
_waitUntilPromises?: Promise<unknown>[];
};
}
}

Expand Down
1 change: 1 addition & 0 deletions src/types/nitro.ts
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,7 @@ export interface NitroOptions extends PresetOptions {
analyze: false | PluginVisualizerOptions;
replace: Record<string, string | ((id: string) => string)>;
commonJS?: RollupCommonJSOptions;
exportConditions?: string[];

// Advanced
typescript: {
Expand Down
11 changes: 11 additions & 0 deletions test/fixture/routes/wait-until.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
const timeTakingOperation = async () => {
// console.log("wait-until.ts: timeTakingOperation() start");
await new Promise((resolve) => setTimeout(resolve, 100));
// console.log("wait-until.ts: timeTakingOperation() done");
};

export default eventHandler((event) => {
event.waitUntil(timeTakingOperation());

return "done";
});
5 changes: 5 additions & 0 deletions test/tests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -388,4 +388,9 @@ export function testNitro(
"versions?.nitro": [expect.any(String), expect.any(String)],
});
});

it("event.waitUntil", async () => {
const res = await callHandler({ url: "/wait-until" });
expect(res.data).toBe("done");
});
}

0 comments on commit a22b4f1

Please sign in to comment.