diff --git a/.gitignore b/.gitignore index ed8c82122e..205ed8b8f5 100644 --- a/.gitignore +++ b/.gitignore @@ -65,3 +65,4 @@ Temporary Items .env .netlify .vercel +staticwebapp.config.json diff --git a/src/prerender.ts b/src/prerender.ts index 0c669bf5d9..702c65abae 100644 --- a/src/prerender.ts +++ b/src/prerender.ts @@ -21,6 +21,7 @@ export async function prerender (nitro: Nitro) { } // Build with prerender preset nitro.logger.info('Initializing prerenderer') + nitro._prerenderedRoutes = [] const nitroRenderer = await createNitro({ ...nitro.options._config, rootDir: nitro.options.rootDir, @@ -89,6 +90,7 @@ export async function prerender (nitro: Nitro) { const filePath = join(nitro.options.output.publicDir, _route.fileName) await writeFile(filePath, Buffer.from(_route.data)) + nitro._prerenderedRoutes.push(_route) // Crawl route links if (!_route.error && isImplicitHTML) { diff --git a/src/presets/azure.ts b/src/presets/azure.ts index be47dbef3a..042462aaad 100644 --- a/src/presets/azure.ts +++ b/src/presets/azure.ts @@ -1,5 +1,4 @@ import fse from 'fs-extra' -import { globby } from 'globby' import { join, resolve } from 'pathe' import { writeFile } from '../utils' import { defineNitroPreset } from '../preset' @@ -20,7 +19,7 @@ export const azure = defineNitroPreset({ } }) -async function writeRoutes (nitro) { +async function writeRoutes (nitro: Nitro) { const host = { version: '2.0' } @@ -48,8 +47,9 @@ async function writeRoutes (nitro) { } } - const indexPath = resolve(nitro.options.output.publicDir, 'index.html') - const indexFileExists = fse.existsSync(indexPath) + const routeFiles = nitro._prerenderedRoutes || [] + + const indexFileExists = routeFiles.some(route => route.fileName === '/index.html') if (!indexFileExists) { config.routes.unshift( { @@ -63,36 +63,29 @@ async function writeRoutes (nitro) { ) } - const folderFiles = await globby([ - join(nitro.options.output.publicDir, 'index.html'), - join(nitro.options.output.publicDir, '**/index.html') - ]) - const prefix = nitro.options.output.publicDir.length const suffix = '/index.html'.length - folderFiles.forEach(file => + for (const { fileName } of routeFiles) { + if (!fileName.endsWith('/index.html')) { continue } + config.routes.unshift({ - route: file.slice(prefix, -suffix) || '/', - rewrite: file.slice(prefix) + route: fileName.slice(0, -suffix) || '/', + rewrite: fileName }) - ) + } - const otherFiles = await globby([join(nitro.options.output.publicDir, '**/*.html'), join(nitro.options.output.publicDir, '*.html')]) - otherFiles.forEach((file) => { - if (file.endsWith('index.html')) { - return - } - const route = file.slice(prefix, -'.html'.length) + for (const { fileName } of routeFiles) { + if (!fileName.endsWith('.html') || fileName.endsWith('index.html')) { continue } + + const route = fileName.slice(0, -'.html'.length) const existingRouteIndex = config.routes.findIndex(_route => _route.route === route) if (existingRouteIndex > -1) { config.routes.splice(existingRouteIndex, 1) } - config.routes.unshift( - { - route, - rewrite: file.slice(prefix) - } - ) - }) + config.routes.unshift({ + route, + rewrite: fileName + }) + } const functionDefinition = { entryPoint: 'handle', @@ -113,12 +106,12 @@ async function writeRoutes (nitro) { ] } - await writeFile(resolve(nitro.options.output.serverDir, 'function.json'), JSON.stringify(functionDefinition)) - await writeFile(resolve(nitro.options.output.serverDir, '../host.json'), JSON.stringify(host)) + await writeFile(resolve(nitro.options.output.serverDir, 'function.json'), JSON.stringify(functionDefinition, null, 2)) + await writeFile(resolve(nitro.options.output.serverDir, '../host.json'), JSON.stringify(host, null, 2)) const stubPackageJson = resolve(nitro.options.output.serverDir, '../package.json') await writeFile(stubPackageJson, JSON.stringify({ private: true })) - await writeFile(resolve(nitro.options.rootDir, 'staticwebapp.config.json'), JSON.stringify(config)) + await writeFile(resolve(nitro.options.rootDir, 'staticwebapp.config.json'), JSON.stringify(config, null, 2)) if (!indexFileExists) { - await writeFile(indexPath, '') + await writeFile(resolve(nitro.options.output.publicDir, 'index.html'), '') } } diff --git a/src/presets/vercel.ts b/src/presets/vercel.ts index c19ff0d5eb..8e73ae1149 100644 --- a/src/presets/vercel.ts +++ b/src/presets/vercel.ts @@ -1,5 +1,6 @@ import { resolve } from 'pathe' import { defu } from 'defu' +import { withoutLeadingSlash } from 'ufo' import { writeFile } from '../utils' import { defineNitroPreset } from '../preset' import type { Nitro } from '../types' @@ -11,7 +12,7 @@ export const vercel = defineNitroPreset({ entry: '#internal/nitro/entries/vercel', output: { dir: '{{ rootDir }}/.vercel/output', - serverDir: '{{ output.dir }}/functions/index.func', + serverDir: '{{ output.dir }}/functions/__nitro.func', publicDir: '{{ output.dir }}/static' }, commands: { @@ -21,28 +22,7 @@ export const vercel = defineNitroPreset({ hooks: { async 'compiled' (nitro: Nitro) { const buildConfigPath = resolve(nitro.options.output.dir, 'config.json') - const buildConfig = defu(nitro.options.vercel?.config, { - version: 3, - routes: [ - ...nitro.options.publicAssets - .filter(asset => !asset.fallthrough) - .map(asset => asset.baseURL) - .map(baseURL => ({ - src: baseURL + '(.*)', - headers: { - 'cache-control': 'public,max-age=31536000,immutable' - }, - continue: true - })), - { - handle: 'filesystem' - }, - { - src: '/(.*)', - dest: '/' - } - ] - }) + const buildConfig = generateBuildConfig(nitro) await writeFile(buildConfigPath, JSON.stringify(buildConfig, null, 2)) const functionConfigPath = resolve(nitro.options.output.serverDir, '.vc-config.json') @@ -62,7 +42,7 @@ export const vercelEdge = defineNitroPreset({ entry: '#internal/nitro/entries/vercel-edge', output: { dir: '{{ rootDir }}/.vercel/output', - serverDir: '{{ output.dir }}/functions/index.func', + serverDir: '{{ output.dir }}/functions/__nitro.func', publicDir: '{{ output.dir }}/static' }, commands: { @@ -77,28 +57,7 @@ export const vercelEdge = defineNitroPreset({ hooks: { async 'compiled' (nitro: Nitro) { const buildConfigPath = resolve(nitro.options.output.dir, 'config.json') - const buildConfig = defu(nitro.options.vercel?.config, { - version: 3, - routes: [ - ...nitro.options.publicAssets - .filter(asset => !asset.fallthrough) - .map(asset => asset.baseURL) - .map(baseURL => ({ - src: baseURL + '(.*)', - headers: { - 'cache-control': 'public,max-age=31536000,immutable' - }, - continue: true - })), - { - handle: 'filesystem' - }, - { - src: '/(.*)', - dest: '/' - } - ] - }) + const buildConfig = generateBuildConfig(nitro) await writeFile(buildConfigPath, JSON.stringify(buildConfig, null, 2)) const functionConfigPath = resolve(nitro.options.output.serverDir, '.vc-config.json') @@ -110,3 +69,35 @@ 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( + (nitro._prerenderedRoutes?.filter(r => r.fileName !== r.route) || []) + .map(({ route, fileName }) => + [withoutLeadingSlash(fileName), { path: withoutLeadingSlash(route) }] + ) + ), + routes: [ + ...nitro.options.publicAssets + .filter(asset => !asset.fallthrough) + .map(asset => asset.baseURL) + .map(baseURL => ({ + src: baseURL + '(.*)', + headers: { + 'cache-control': 'public,max-age=31536000,immutable' + }, + continue: true + })), + { + handle: 'filesystem' + }, + { + src: '/(.*)', + dest: '/__nitro' + } + ] + }) +} diff --git a/src/types/nitro.ts b/src/types/nitro.ts index e5d6e33654..3dceab9d9b 100644 --- a/src/types/nitro.ts +++ b/src/types/nitro.ts @@ -24,6 +24,9 @@ export interface Nitro { logger: Consola storage: Storage close: () => Promise + + /* @internal */ + _prerenderedRoutes?: PrerenderGenerateRoute[] } export interface PrerenderRoute { diff --git a/test/presets/vercel.test.ts b/test/presets/vercel.test.ts index 2e0442a5d8..3bafc10af1 100644 --- a/test/presets/vercel.test.ts +++ b/test/presets/vercel.test.ts @@ -7,7 +7,7 @@ import { setupTest, startServer, testNitro } from '../tests' describe('nitro:preset:vercel', async () => { const ctx = await setupTest('vercel') testNitro(ctx, async () => { - const handle = await import(resolve(ctx.outDir, 'functions/index.func/index.mjs')) + const handle = await import(resolve(ctx.outDir, 'functions/__nitro.func/index.mjs')) .then(r => r.default || r) await startServer(ctx, handle) return async ({ url }) => { @@ -21,7 +21,7 @@ describe.skip('nitro:preset:vercel-edge', async () => { const ctx = await setupTest('vercel-edge') testNitro(ctx, async () => { // TODO: Add add-event-listener - const entry = resolve(ctx.outDir, 'functions/index.func/index.mjs') + const entry = resolve(ctx.outDir, 'functions/__nitro.func/index.mjs') const entryCode = await fsp.readFile(entry, 'utf8') const runtime = new EdgeRuntime({ initialCode: entryCode }) return async ({ url }) => {