-
-
Notifications
You must be signed in to change notification settings - Fork 88
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(one): working Vercel builds using Build Output API v3
- Loading branch information
1 parent
a0da7cc
commit 134c7ed
Showing
26 changed files
with
2,736 additions
and
1,946 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
126 changes: 126 additions & 0 deletions
126
packages/one/src/vercel/build/buildVercelOutputDirectory.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,126 @@ | ||
import { join, resolve } from 'node:path' | ||
|
||
import FSExtra from 'fs-extra' | ||
import type { RollupOutput } from 'rollup' | ||
import { isMatching, P } from 'ts-pattern' | ||
|
||
import { createApiServerlessFunction } from './generate/createApiServerlessFunction' | ||
import { createSsrServerlessFunction } from './generate/createSsrServerlessFunction' | ||
import { serverlessVercelNodeJsConfig } from './config/vc-config-base' | ||
import { serverlessVercelPackageJson } from './config/vc-package-base' | ||
import { vercelBuildOutputConfig } from './config/vc-build-output-config-base' | ||
|
||
import type { One } from '../../vite/types' | ||
|
||
const { copy, ensureDir, writeJSON } = FSExtra | ||
|
||
async function moveAllFiles(src: string, dest: string) { | ||
try { | ||
await copy(src, dest, { overwrite: true, errorOnExist: false }) | ||
} catch (err) { | ||
console.error('Error moving files:', err) | ||
} | ||
} | ||
|
||
export const buildVercelOutputDirectory = async ({ | ||
apiOutput, | ||
buildInfoForWriting, | ||
clientDir, | ||
oneOptionsRoot, | ||
postBuildLogs, | ||
}: { | ||
apiOutput: RollupOutput | null | ||
buildInfoForWriting: One.BuildInfo | ||
clientDir: string | ||
oneOptionsRoot: string | ||
postBuildLogs: string[] | ||
}) => { | ||
const { routeToBuildInfo } = buildInfoForWriting | ||
if (apiOutput) { | ||
const compiltedApiRoutes = (apiOutput?.output ?? []).filter((o) => | ||
isMatching({ code: P.string, facadeModuleId: P.string }, o) | ||
) | ||
for (const route of buildInfoForWriting.manifest.apiRoutes) { | ||
const compiledRoute = compiltedApiRoutes.find((compiled) => { | ||
const flag = compiled.facadeModuleId.includes(route.file.replace('./', '')) | ||
return flag | ||
}) | ||
if (compiledRoute) { | ||
postBuildLogs.push( | ||
`[one.build][vercel] generating serverless function for apiRoute ${route.page}` | ||
) | ||
await createApiServerlessFunction( | ||
route.page, | ||
compiledRoute.code, | ||
oneOptionsRoot, | ||
postBuildLogs | ||
) | ||
} else { | ||
console.warn('\n 🔨[one.build][vercel] apiRoute missing code compilation for', route.file) | ||
} | ||
} | ||
} | ||
|
||
const vercelOutputFunctionsDir = join(oneOptionsRoot, 'dist', `.vercel/output/functions`) | ||
await ensureDir(vercelOutputFunctionsDir) | ||
|
||
for (const route of buildInfoForWriting.manifest.pageRoutes) { | ||
switch (route.type) { | ||
case 'ssr': { | ||
// Server Side Rendered | ||
const builtPageRoute = routeToBuildInfo[route.file] | ||
if (builtPageRoute) { | ||
postBuildLogs.push( | ||
`[one.build][vercel] generate serverless function for ${route.page} with ${route.type}` | ||
) | ||
await createSsrServerlessFunction( | ||
route.page, | ||
buildInfoForWriting, | ||
oneOptionsRoot, | ||
postBuildLogs | ||
) | ||
} | ||
break | ||
} | ||
default: | ||
// no-op, these will be copied from built dist/client into .vercel/output/static | ||
// postBuildLogs.push(`[one.build][vercel] pageRoute will be copied to .vercel/output/static for ${route.page} with ${route.type}`) | ||
break | ||
} | ||
} | ||
|
||
const vercelMiddlewareDir = join(oneOptionsRoot, 'dist', '.vercel/output/functions/_middleware') | ||
await ensureDir(vercelMiddlewareDir) | ||
postBuildLogs.push( | ||
`[one.build][vercel] copying middlewares from ${join(oneOptionsRoot, 'dist', 'middlewares')} to ${vercelMiddlewareDir}` | ||
) | ||
await moveAllFiles(resolve(join(oneOptionsRoot, 'dist', 'middlewares')), vercelMiddlewareDir) | ||
const vercelMiddlewarePackageJsonFilePath = resolve(join(vercelMiddlewareDir, 'index.js')) | ||
postBuildLogs.push( | ||
`[one.build][vercel] writing package.json to ${vercelMiddlewarePackageJsonFilePath}` | ||
) | ||
await writeJSON(vercelMiddlewarePackageJsonFilePath, serverlessVercelPackageJson) | ||
postBuildLogs.push( | ||
`[one.build][vercel] writing .vc-config.json to ${join(vercelMiddlewareDir, '.vc-config.json')}` | ||
) | ||
await writeJSON(resolve(join(vercelMiddlewareDir, '.vc-config.json')), { | ||
...serverlessVercelNodeJsConfig, | ||
handler: '_middleware.js', | ||
}) | ||
|
||
const vercelOutputStaticDir = resolve(join(oneOptionsRoot, 'dist', '.vercel/output/static')) | ||
await ensureDir(vercelOutputStaticDir) | ||
|
||
postBuildLogs.push( | ||
`[one.build][vercel] copying static files from ${clientDir} to ${vercelOutputStaticDir}` | ||
) | ||
await moveAllFiles(clientDir, vercelOutputStaticDir) | ||
|
||
// Documentation - Vercel Build Output v3 config.json | ||
// https://vercel.com/docs/build-output-api/v3/configuration#config.json-supported-properties | ||
const vercelConfigFilePath = resolve( | ||
join(oneOptionsRoot, 'dist', '.vercel/output', 'config.json') | ||
) | ||
await writeJSON(vercelConfigFilePath, vercelBuildOutputConfig) | ||
postBuildLogs.push(`[one.build] wrote vercel config to: ${vercelConfigFilePath}`) | ||
} |
12 changes: 12 additions & 0 deletions
12
packages/one/src/vercel/build/config/vc-build-output-config-base.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
// Documentation - Vercel Build Output v3 Config | ||
// https://vercel.com/docs/build-output-api/v3/configuration#config.json-supported-properties | ||
export const vercelBuildOutputConfig = { | ||
version: 3, | ||
// https://vercel.com/docs/build-output-api/v3/configuration#routes | ||
routes: [ | ||
{ | ||
src: '/(.*)', | ||
status: 200, | ||
}, | ||
], | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
// Documentation - Vercel Build Output v3 Node.js Config | ||
// https://vercel.com/docs/build-output-api/v3/primitives#node.js-config | ||
export const serverlessVercelNodeJsConfig = { | ||
environment: {}, | ||
runtime: 'nodejs20.x', | ||
handler: 'entrypoint/index.js', | ||
launcherType: 'Nodejs', | ||
shouldAddHelpers: true, | ||
shouldAddSourceMapSupport: true, | ||
// @TODO: We could support edge functions in the future. | ||
// Requires a larger discusion of how to handle edge functions in general. | ||
// +ssr-edge.tsx or +edge.tsx down the road. | ||
// https://vercel.com/docs/build-output-api/v3/primitives#edge-functions | ||
// runtime: 'edge', | ||
// regions: 'all', | ||
// @TODO: We could support ISR in the future as well. | ||
// Requires a larger discusion of how to handle ISR in general. | ||
// https://vercel.com/docs/build-output-api/v3/primitives#prerender-functions | ||
// We would need to generate the bypassToken and copy *.html fallback files to the *.func folder. | ||
// https://vercel.com/docs/build-output-api/v3/primitives#fallback-static-file | ||
// bypassToken?: string; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export const serverlessVercelPackageJson = { type: 'module' } |
66 changes: 66 additions & 0 deletions
66
packages/one/src/vercel/build/generate/createApiServerlessFunction.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
import { join, resolve } from 'node:path' | ||
|
||
import fs from 'fs-extra' | ||
|
||
import { serverlessVercelPackageJson } from '../config/vc-package-base' | ||
import { serverlessVercelNodeJsConfig } from '../config/vc-config-base' | ||
|
||
// Documentation - Vercel Build Output v3 | ||
// https://vercel.com/docs/build-output-api/v3#build-output-api-v3 | ||
export async function createApiServerlessFunction( | ||
pageName: string, | ||
code: string, | ||
oneOptionsRoot: string, | ||
postBuildLogs: string[] | ||
) { | ||
try { | ||
postBuildLogs.push(`[one.build][vercel.createSsrServerlessFunction] pageName: ${pageName}`) | ||
|
||
const funcFolder = join(oneOptionsRoot, 'dist', `.vercel/output/functions/${pageName}.func`) | ||
await fs.ensureDir(funcFolder) | ||
|
||
if (code.includes('react')) { | ||
postBuildLogs.push( | ||
`[one.build][vercel.createSsrServerlessFunction] detected react in depenency tree for ${pageName}` | ||
) | ||
await fs.copy( | ||
resolve(join(oneOptionsRoot, '..', '..', 'node_modules', 'react')), | ||
resolve(join(funcFolder, 'node_modules', 'react')) | ||
) | ||
} | ||
|
||
const distAssetsFolder = resolve(join(funcFolder, 'assets')) | ||
postBuildLogs.push( | ||
`[one.build][vercel.createSsrServerlessFunction] copy shared assets to ${distAssetsFolder}` | ||
) | ||
await fs.copy(resolve(join(oneOptionsRoot, 'dist', 'api', 'assets')), distAssetsFolder) | ||
|
||
await fs.ensureDir(resolve(join(funcFolder, 'entrypoint'))) | ||
const entrypointFilePath = resolve(join(funcFolder, 'entrypoint', 'index.js')) | ||
postBuildLogs.push( | ||
`[one.build][vercel.createSsrServerlessFunction] writing entrypoint to ${entrypointFilePath}` | ||
) | ||
await fs.writeFile(entrypointFilePath, code) | ||
|
||
const packageJsonFilePath = resolve(join(funcFolder, 'package.json')) | ||
postBuildLogs.push( | ||
`[one.build][vercel.createSsrServerlessFunction] writing package.json to ${packageJsonFilePath}` | ||
) | ||
await fs.writeJSON(packageJsonFilePath, serverlessVercelPackageJson) | ||
|
||
postBuildLogs.push( | ||
`[one.build][vercel.createSsrServerlessFunction] writing .vc-config.json to ${join(funcFolder, '.vc-config.json')}` | ||
) | ||
// Documentation - Vercel Build Output v3 Node.js Config | ||
// https://vercel.com/docs/build-output-api/v3/primitives#node.js-config | ||
return fs.writeJson(join(funcFolder, '.vc-config.json'), { | ||
...serverlessVercelNodeJsConfig, | ||
handler: 'entrypoint/index.js', | ||
}) | ||
} catch (e) { | ||
console.error( | ||
`[one.build][vercel.createSsrServerlessFunction] failed to generate func for ${pageName}`, | ||
e | ||
) | ||
} | ||
} |
Oops, something went wrong.