Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(netlify): incremental static regeneration + swr #540

Merged
merged 9 commits into from
Oct 11, 2022
6 changes: 6 additions & 0 deletions src/presets/netlify.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import type { Nitro } from '../types'
// Netlify functions
export const netlify = defineNitroPreset({
extends: 'aws-lambda',
entry: '#internal/nitro/entries/netlify',
output: {
dir: '{{ rootDir }}/.netlify/functions-internal',
publicDir: '{{ rootDir }}/dist'
Expand Down Expand Up @@ -80,6 +81,11 @@ async function writeRedirects (nitro: Nitro) {
const redirectsPath = join(nitro.options.output.publicDir, '_redirects')
let contents = '/* /.netlify/functions/server 200'

// Rewrite SWR and static paths to builder functions
for (const [key] of Object.entries(nitro.options.routes).filter(([_, value]) => value.swr || value.static)) {
contents = `${key.replace('/**', '/*')}\t/.netlify/builders/server 200\n` + contents
}

for (const [key, value] of Object.entries(nitro.options.routes).filter(([_, value]) => value.redirect)) {
const redirect = typeof value.redirect === 'string' ? { to: value.redirect } : value.redirect
// TODO: update to 307 when netlify support 307/308
Expand Down
59 changes: 59 additions & 0 deletions src/runtime/entries/netlify.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import '#internal/nitro/virtual/polyfill'
import type { Handler, HandlerResponse, HandlerContext, HandlerEvent } from '@netlify/functions/dist/main'
import type { APIGatewayProxyEventHeaders } from 'aws-lambda'
import { withQuery } from 'ufo'
import { createRouter as createMatcher } from 'radix3'
import { nitroApp } from '../app'
import { useRuntimeConfig } from '../config'

export const handler: Handler = async function handler (event, context) {
const config = useRuntimeConfig()
const routerOptions = createMatcher({ routes: config.nitro.routes })

const query = { ...event.queryStringParameters, ...event.multiValueQueryStringParameters }
const url = withQuery(event.path, query)
const routeOptions = routerOptions.lookup(url) || {}
danielroe marked this conversation as resolved.
Show resolved Hide resolved

if (routeOptions.static || routeOptions.swr) {
const builder = await import('@netlify/functions').then(r => r.builder || r.default.builder)
const ttl = typeof routeOptions.swr === 'number' ? routeOptions.swr : 60
const swrHandler = routeOptions.swr
? ((event, context) => lambda(event, context).then(r => ({ ...r, ttl }))) as Handler
: lambda
return builder(swrHandler)(event, context) as any
}

return lambda(event, context)
}

async function lambda (event: HandlerEvent, context: HandlerContext): Promise<HandlerResponse> {
const query = { ...event.queryStringParameters, ...(event).multiValueQueryStringParameters }
const url = withQuery((event).path, query)
const method = (event).httpMethod || 'get'

const r = await nitroApp.localCall({
event,
url,
context,
headers: normalizeIncomingHeaders(event.headers),
method,
query,
body: event.body // TODO: handle event.isBase64Encoded
})

return {
statusCode: r.status,
headers: normalizeOutgoingHeaders(r.headers),
body: r.body.toString()
}
}

function normalizeIncomingHeaders (headers?: APIGatewayProxyEventHeaders) {
return Object.fromEntries(Object.entries(headers || {}).map(([key, value]) => [key.toLowerCase(), value!]))
}

function normalizeOutgoingHeaders (headers: Record<string, string | string[] | undefined>) {
return Object.fromEntries(Object.entries(headers)
.filter(([key]) => !['set-cookie'].includes(key))
.map(([k, v]) => [k, Array.isArray(v) ? v.join(',') : v!]))
}
1 change: 1 addition & 0 deletions src/types/nitro.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ export interface NitroConfig extends DeepPartial<NitroOptions> {

export interface NitroRouteOption {
swr?: boolean | number
static?: boolean
redirect?: string | { to: string, statusCode?: 301 | 302 | 307 | 308 }
headers?: Record<string, string>
cors?: boolean
Expand Down
1 change: 1 addition & 0 deletions test/presets/netlify.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ describe('nitro:preset:netlify', async () => {
/rules/nested/* /base 301
/rules/redirect/obj https://nitro.unjs.io/ 308
/rules/redirect /base 301
/rules/static /.netlify/builders/server 200
/* /.netlify/functions/server 200"
`)
/* eslint-enable no-tabs */
Expand Down
1 change: 1 addition & 0 deletions test/tests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ export async function setupTest (preset) {
'/rules/headers': { headers: { 'cache-control': 's-maxage=60' } },
'/rules/cors': { cors: true, headers: { 'access-control-allowed-methods': 'GET' } },
'/rules/redirect': { redirect: '/base' },
'/rules/static': { static: true },
'/rules/redirect/obj': {
redirect: { to: 'https://nitro.unjs.io/', statusCode: 308 }
},
Expand Down