UNPKG

one

Version:

One is a new React Framework that makes Vite serve both native and web.

191 lines (183 loc) 8.32 kB
import { join, resolve } from "node:path"; import FSExtra from "fs-extra"; import { isMatching, P } from "ts-pattern"; import { vercelBuildOutputConfigBase } from "./config/vc-build-output-config-base.mjs"; import { serverlessVercelPackageJson } from "./config/vc-package-base.mjs"; import { createApiServerlessFunction } from "./generate/createApiServerlessFunction.mjs"; import { createSsrServerlessFunction } from "./generate/createSsrServerlessFunction.mjs"; import { getPathFromRoute } from "./getPathFromRoute.mjs"; const { copy, ensureDir, existsSync, remove, writeJSON } = FSExtra; async function moveAllFiles(src, dest) { try { await copy(src, dest, { overwrite: true, errorOnExist: false }); } catch (err) { console.error("Error moving files:", err); } } function getMiddlewaresByNamedRegex(buildInfoForWriting) { const outDir = buildInfoForWriting.outDir || "dist"; const prefix = `${outDir}/middlewares/`; return buildInfoForWriting.manifest.allRoutes.filter(r => r.middlewares && r.middlewares.length > 0).map(r => [r.namedRegex, r.middlewares.map(m => m.contextKey.startsWith(prefix) ? m.contextKey.substring(prefix.length) : m.contextKey)]).sort((a, b) => b[0].length - a[0].length); } const buildVercelOutputDirectory = async ({ apiOutput, buildInfoForWriting, clientDir, oneOptionsRoot, postBuildLogs }) => { const outDir = buildInfoForWriting.outDir || "dist"; const vercelOutputDir = resolve(join(oneOptionsRoot, ".vercel/output")); if (existsSync(vercelOutputDir)) { await remove(vercelOutputDir); } 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, compiledRoute.code, oneOptionsRoot, postBuildLogs, outDir); } else { console.warn("\n \u{1F528}[one.build][vercel] apiRoute missing code compilation for", route.file); } } } const vercelOutputFunctionsDir = join(oneOptionsRoot, ".vercel/output/functions"); await ensureDir(vercelOutputFunctionsDir); for (const route of buildInfoForWriting.manifest.pageRoutes) { switch (route.type) { case "ssr": { const builtPageRoute = routeToBuildInfo[route.file]; if (builtPageRoute) { postBuildLogs.push(`[one.build][vercel] generate serverless function for ${route.page} with ${route.type}`); await createSsrServerlessFunction(route, buildInfoForWriting, oneOptionsRoot, postBuildLogs); } break; } default: break; } } const distMiddlewareDir = resolve(join(oneOptionsRoot, outDir, "middlewares")); if (existsSync(distMiddlewareDir)) { const vercelMiddlewareDir = resolve(join(oneOptionsRoot, ".vercel/output/functions/_middleware.func")); await ensureDir(vercelMiddlewareDir); postBuildLogs.push(`[one.build][vercel] copying middlewares from ${distMiddlewareDir} to ${vercelMiddlewareDir}`); await moveAllFiles(resolve(join(oneOptionsRoot, outDir, "middlewares")), vercelMiddlewareDir); const vercelMiddlewarePackageJsonFilePath = resolve(join(vercelMiddlewareDir, "package.json")); postBuildLogs.push(`[one.build][vercel] writing package.json to ${vercelMiddlewarePackageJsonFilePath}`); await writeJSON(vercelMiddlewarePackageJsonFilePath, serverlessVercelPackageJson); const wrappedMiddlewareEntryPointFilename = "_wrapped_middleware.js"; const wrappedMiddlewareEntryPointPath = resolve(join(vercelMiddlewareDir, wrappedMiddlewareEntryPointFilename)); const middlewaresByNamedRegex = getMiddlewaresByNamedRegex(buildInfoForWriting); const middlewaresToVariableNameMap = middlewaresByNamedRegex.reduce((acc, [namedRegex, middlewares]) => { ; (Array.isArray(middlewares) ? middlewares : [middlewares]).forEach(middleware => { const middlewareVariableName = middleware.replace(/\.[a-z]+$/, "").replaceAll("/", "_"); acc[middleware] = middlewareVariableName; }); return acc; }, {}); await FSExtra.writeFile(wrappedMiddlewareEntryPointPath, ` const middlewaresByNamedRegex = ${JSON.stringify(middlewaresByNamedRegex)} ${Object.entries(middlewaresToVariableNameMap).map(([path, variableName]) => `import ${variableName} from './${path}'`).join("\n")} function getMiddleware(path) { switch (path){ ${Object.entries(middlewaresToVariableNameMap).map(([path, variableName]) => `case '${path}': return ${variableName}`).join("\n")} default: return null } } const next = (e) => { const t = new Headers(null == e ? void 0 : e.headers) t.set('x-middleware-next', '1') return new Response(null, { ...e, headers: t }) } const wrappedMiddlewareFunction = (request, event) => { const url = new URL(request.url) const pathname = url.pathname // Find matching middlewares for this request const matchingMiddlewares = [...middlewaresByNamedRegex .filter(([namedRegex]) => new RegExp(namedRegex).test(pathname)) .reduce((prev, current) => prev.length > current[1]?.length ? prev : current[1], [])]; // Import and execute the middleware function const boundNext = () => { if (matchingMiddlewares.length === 0) { return next(request) } const middleware = getMiddleware(matchingMiddlewares.shift()) return middleware ? middleware({request, event, next: boundNext}) : next(request) }; return boundNext() } export { wrappedMiddlewareFunction as default } `); const middlewareVercelConfigFilePath = resolve(join(vercelMiddlewareDir, ".vc-config.json")); postBuildLogs.push(`[one.build][vercel] writing .vc-config.json to ${middlewareVercelConfigFilePath}`); await writeJSON(middlewareVercelConfigFilePath, { runtime: "edge", // Seems that middlewares only work with edge runtime entrypoint: wrappedMiddlewareEntryPointFilename }); } const vercelOutputStaticDir = resolve(join(oneOptionsRoot, ".vercel/output/static")); await ensureDir(vercelOutputStaticDir); postBuildLogs.push(`[one.build][vercel] copying static files from ${clientDir} to ${vercelOutputStaticDir}`); await moveAllFiles(clientDir, vercelOutputStaticDir); const ssrLoaderRoutes = buildInfoForWriting.manifest.pageRoutes.filter(r => r.type === "ssr").map(r => { const pagePath = getPathFromRoute(r) || "/"; const cleanPath = pagePath.slice(1).replace(/\//g, "_"); const loaderPattern = cleanPath.replace(/:([^_]+)/g, "(?<$1>[^_]+)"); const src = `^/assets/${loaderPattern}(?:_refetch_\\d+)?_\\d+_vxrn_loader\\.js$`; let dest = `${pagePath}?__loader=1`; const paramMatches = cleanPath.match(/:([^_]+)/g); if (paramMatches) { for (const match of paramMatches) { const paramName = match.slice(1); dest += `&${paramName}=$${paramName}`; } } return { src, dest }; }); const vercelConfigFilePath = resolve(join(oneOptionsRoot, ".vercel/output", "config.json")); await writeJSON(vercelConfigFilePath, { ...vercelBuildOutputConfigBase, routes: [...vercelBuildOutputConfigBase.routes, ...(existsSync(distMiddlewareDir) ? [{ src: "/(.*)", middlewarePath: "_middleware", continue: true }] : []), { handle: "rewrite" }, // SSR loader routes must come before dynamic page routes ...ssrLoaderRoutes, ...buildInfoForWriting.manifest.allRoutes.filter(r => r.routeKeys && Object.keys(r.routeKeys).length > 0).map(r => ({ src: r.namedRegex, dest: `${getPathFromRoute(r) || "/"}?${Object.entries(r.routeKeys).map(([k, v]) => `${k}=$${v}`).join("&")}` }))] }); postBuildLogs.push(`[one.build] wrote vercel config to: ${vercelConfigFilePath}`); }; export { buildVercelOutputDirectory }; //# sourceMappingURL=buildVercelOutputDirectory.mjs.map