From 88ce2de96ef5bab1e945e539e61581cd33aaf54b Mon Sep 17 00:00:00 2001 From: Daniel Roe Date: Tue, 15 Nov 2022 16:21:47 +0000 Subject: [PATCH] feat(vercel): incremental static generation + swr (#545) --- src/presets/vercel.ts | 26 ++++++++++++++++++++++++-- src/runtime/entries/vercel.ts | 16 ++++++++++++++-- test/presets/netlify.test.ts | 2 ++ test/presets/vercel.test.ts | 8 ++++++++ test/tests.ts | 2 ++ 5 files changed, 50 insertions(+), 4 deletions(-) diff --git a/src/presets/vercel.ts b/src/presets/vercel.ts index a5c26393a6..467aeb7460 100644 --- a/src/presets/vercel.ts +++ b/src/presets/vercel.ts @@ -1,4 +1,5 @@ -import { resolve } from 'pathe' +import fsp from 'fs/promises' +import { dirname, relative, resolve } from 'pathe' import { defu } from 'defu' import { withoutLeadingSlash } from 'ufo' import { writeFile } from '../utils' @@ -34,6 +35,18 @@ export const vercel = defineNitroPreset({ shouldAddHelpers: false } await writeFile(functionConfigPath, JSON.stringify(functionConfig, null, 2)) + + // Write prerender functions + for (const [key, value] of Object.entries(nitro.options.routeRules).filter(([_, value]) => value.cache && (value.cache.swr || value.cache.static))) { + if (!value.cache) { continue } // for type support + const funcPrefix = resolve(nitro.options.output.serverDir, '..' + generateEndpoint(key)) + await fsp.mkdir(dirname(funcPrefix), { recursive: true }) + await fsp.symlink('./' + relative(dirname(funcPrefix), nitro.options.output.serverDir), funcPrefix + '.func', 'junction') + await writeFile(funcPrefix + '.prerender-config.json', JSON.stringify({ + expiration: value.cache.static ? false : typeof value.cache.swr === 'number' ? value.cache.swr : 60, + allowQuery: key.includes('/**') ? ['url'] : undefined + })) + } } } }) @@ -72,7 +85,6 @@ export const vercelEdge = defineNitroPreset({ }) function generateBuildConfig (nitro: Nitro) { - // const overrides = generateOverrides(nitro._prerenderedRoutes?.filter(r => r.fileName !== r.route) || []) return defu(nitro.options.vercel?.config, { version: 3, overrides: Object.fromEntries( @@ -112,6 +124,12 @@ function generateBuildConfig (nitro: Nitro) { { handle: 'filesystem' }, + ...Object.entries(nitro.options.routeRules) + .filter(([key, value]) => value.cache && (value.cache.swr || value.cache.static) && key.includes('/**')) + .map(([key]) => ({ + src: key.replace(/^(.*)\/\*\*/, '(?$1/.*)'), + dest: generateEndpoint(key) + '?url=$url' + })), { src: '/(.*)', dest: '/__nitro' @@ -119,3 +137,7 @@ function generateBuildConfig (nitro: Nitro) { ] }) } + +function generateEndpoint (url: string) { + return url.includes('/**') ? '/__nitro-' + withoutLeadingSlash(url.replace(/\/\*\*.*/, '').replace(/[^a-z]/g, '-')) : url +} diff --git a/src/runtime/entries/vercel.ts b/src/runtime/entries/vercel.ts index 6fabdac0e0..ecdfbda822 100644 --- a/src/runtime/entries/vercel.ts +++ b/src/runtime/entries/vercel.ts @@ -1,5 +1,17 @@ import '#internal/nitro/virtual/polyfill' -import { toNodeListener } from 'h3' +import { toNodeListener, NodeListener } from 'h3' +import { parseQuery } from 'ufo' import { nitroApp } from '../app' -export default toNodeListener(nitroApp.h3App) +const handler = toNodeListener(nitroApp.h3App) + +export default function (req, res) { + const query = req.headers['x-now-route-matches'] as string + if (query) { + const { url } = parseQuery(query) + if (url) { + req.url = url as string + } + } + return handler(req, res) +} diff --git a/test/presets/netlify.test.ts b/test/presets/netlify.test.ts index 464b0bbaff..9cc98dc8aa 100644 --- a/test/presets/netlify.test.ts +++ b/test/presets/netlify.test.ts @@ -37,6 +37,8 @@ describe('nitro:preset:netlify', async () => { /rules/nested/* /base 302 /rules/redirect/obj https://nitro.unjs.io/ 301 /rules/redirect /base 302 + /rules/swr-ttl/* /.netlify/builders/server 200 + /rules/swr/* /.netlify/builders/server 200 /rules/static /.netlify/builders/server 200 /* /.netlify/functions/server 200" `) diff --git a/test/presets/vercel.test.ts b/test/presets/vercel.test.ts index 0e35f6a5d3..f83936031e 100644 --- a/test/presets/vercel.test.ts +++ b/test/presets/vercel.test.ts @@ -95,6 +95,14 @@ describe('nitro:preset:vercel', async () => { { "handle": "filesystem", }, + { + "dest": "/__nitro--rules-swr?url=$url", + "src": "(?/rules/swr/.*)", + }, + { + "dest": "/__nitro--rules-swr-ttl?url=$url", + "src": "(?/rules/swr-ttl/.*)", + }, { "dest": "/__nitro", "src": "/(.*)", diff --git a/test/tests.ts b/test/tests.ts index dc9a5946f1..7a0cc04d3a 100644 --- a/test/tests.ts +++ b/test/tests.ts @@ -39,6 +39,8 @@ export async function setupTest (preset) { '/rules/cors': { cors: true, headers: { 'access-control-allowed-methods': 'GET' } }, '/rules/redirect': { redirect: '/base' }, '/rules/static': { static: true }, + '/rules/swr/**': { swr: true }, + '/rules/swr-ttl/**': { swr: 60 }, '/rules/redirect/obj': { redirect: { to: 'https://nitro.unjs.io/', statusCode: 308 } },