UNPKG

one

Version:

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

1,114 lines (1,101 loc) 42.8 kB
var __create = Object.create; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __getProtoOf = Object.getPrototypeOf; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( // If the importer is in node compatibility mode or this is not an ESM // file that has been converted to a CommonJS file using a Babel- // compatible transform (i.e. "__esModule" has not been set), then set // "default" to the CommonJS "module.exports" for node compatibility. isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, mod)); var __toCommonJS = mod => __copyProps(__defProp({}, "__esModule", { value: true }), mod); var build_exports = {}; __export(build_exports, { build: () => build }); module.exports = __toCommonJS(build_exports); var import_node_module = require("node:module"); var import_node_os = require("node:os"); var import_node_path = __toESM(require("node:path"), 1); var import_resolve = require("@vxrn/resolve"); var import_fs_extra = __toESM(require("fs-extra"), 1); var import_micromatch = __toESM(require("micromatch"), 1); var import_vite = require("vite"); var import_vxrn = require("vxrn"); var constants = __toESM(require("../constants.cjs"), 1); var import_setServerGlobals = require("../server/setServerGlobals.cjs"); var import_getPathnameFromFilePath = require("../utils/getPathnameFromFilePath.cjs"); var import_getRouterRootFromOneOptions = require("../utils/getRouterRootFromOneOptions.cjs"); var import_isRolldown = require("../utils/isRolldown.cjs"); var import_toAbsolute = require("../utils/toAbsolute.cjs"); var import_buildVercelOutputDirectory = require("../vercel/build/buildVercelOutputDirectory.cjs"); var import_getManifest = require("../vite/getManifest.cjs"); var import_loadConfig = require("../vite/loadConfig.cjs"); var import_one_server_only = require("../vite/one-server-only.cjs"); var import_buildPage = require("./buildPage.cjs"); var import_checkNodeVersion = require("./checkNodeVersion.cjs"); var import_workerPool = require("./workerPool.cjs"); var import_generateSitemap = require("./generateSitemap.cjs"); var import_label_process = require("./label-process.cjs"); var import_pLimit = require("../utils/pLimit.cjs"); var import_criticalCSSPlugin = require("../vite/plugins/criticalCSSPlugin.cjs"); const import_meta = {}; const { ensureDir, writeJSON } = import_fs_extra.default; function normalizeDeploy(deploy) { if (!deploy) return void 0; if (typeof deploy === "string") return { target: deploy }; return deploy; } const GENERATED_CLOUDFLARE_WRANGLER_RULES = [{ type: "ESModule", globs: ["./server/**/*.js"], fallthrough: true }, { type: "ESModule", globs: ["./api/**/*.js"], fallthrough: true }, { type: "ESModule", globs: ["./middlewares/**/*.js"], fallthrough: true }, { type: "ESModule", globs: ["./assets/**/*.js"], fallthrough: true }]; function isPlainObject(value) { return !!value && typeof value === "object" && !Array.isArray(value); } function mergeJsonObjects(base, overrides) { const merged = { ...base }; for (const [key, value] of Object.entries(overrides)) { const baseValue = merged[key]; if (isPlainObject(baseValue) && isPlainObject(value)) { merged[key] = mergeJsonObjects(baseValue, value); } else { merged[key] = value; } } return merged; } function dedupeJsonValues(values) { const seen = /* @__PURE__ */new Set(); return values.filter(value => { const key = JSON.stringify(value); if (seen.has(key)) return false; seen.add(key); return true; }); } function mergeCloudflareCompatibilityFlags(flags) { const userFlags = Array.isArray(flags) ? flags.filter(flag => typeof flag === "string") : []; return dedupeJsonValues(["nodejs_compat", ...userFlags]); } function mergeCloudflareRules(rules) { const userRules = Array.isArray(rules) ? rules.filter(rule => isPlainObject(rule)) : []; return dedupeJsonValues([...GENERATED_CLOUDFLARE_WRANGLER_RULES, ...userRules]); } function parseJsonc(text) { let out = ""; let i = 0; let inString = false; let quote = ""; while (i < text.length) { const ch = text[i]; const next = text[i + 1]; if (inString) { if (ch === "\\") { out += ch + (next ?? ""); i += 2; continue; } if (ch === quote) inString = false; out += ch; i++; continue; } if (ch === '"' || ch === "'") { inString = true; quote = ch; out += ch; i++; continue; } if (ch === "/" && next === "/") { while (i < text.length && text[i] !== "\n") i++; continue; } if (ch === "/" && next === "*") { i += 2; while (i < text.length - 1 && !(text[i] === "*" && text[i + 1] === "/")) i++; i += 2; continue; } out += ch; i++; } return JSON.parse(out.replace(/,(\s*[}\]])/g, "$1")); } async function loadUserWranglerConfig(root) { const candidateRoots = [... /* @__PURE__ */new Set([root, process.cwd()])]; for (const candidateRoot of candidateRoots) { for (const fileName of ["wrangler.jsonc", "wrangler.json"]) { const configPath = (0, import_node_path.join)(candidateRoot, fileName); if (!(await import_fs_extra.default.pathExists(configPath))) { continue; } const contents = await import_fs_extra.default.readFile(configPath, "utf-8"); let parsed; try { parsed = parseJsonc(contents); } catch (err) { throw new Error(`Failed to parse ${(0, import_node_path.relative)(process.cwd(), configPath)}: ${err.message}`); } if (!isPlainObject(parsed)) { throw new Error(`Expected ${(0, import_node_path.relative)(process.cwd(), configPath)} to contain a top-level JSON object`); } return { path: configPath, config: parsed }; } } return null; } function createCloudflareWranglerConfig(projectName, userConfig) { const generatedConfig = { name: projectName, main: "worker.js", compatibility_date: "2024-12-05", compatibility_flags: ["nodejs_compat"], find_additional_modules: true, rules: GENERATED_CLOUDFLARE_WRANGLER_RULES, assets: { directory: "client", binding: "ASSETS", run_worker_first: true } }; const mergedConfig = userConfig ? mergeJsonObjects(generatedConfig, userConfig) : generatedConfig; mergedConfig.main = "worker.js"; mergedConfig.find_additional_modules = true; mergedConfig.compatibility_flags = mergeCloudflareCompatibilityFlags(mergedConfig.compatibility_flags); mergedConfig.rules = mergeCloudflareRules(mergedConfig.rules); mergedConfig.assets = { ...(isPlainObject(mergedConfig.assets) ? mergedConfig.assets : {}), directory: "client", binding: "ASSETS", run_worker_first: true }; return mergedConfig; } async function getCloudflareProjectName(root) { try { const pkg = JSON.parse(await import_fs_extra.default.readFile((0, import_node_path.join)(root, "package.json"), "utf-8")); if (pkg.name) { return pkg.name.replace(/^@[^/]+\//, ""); } } catch {} return "one-app"; } process.env.ONE_CACHE_KEY = constants.CACHE_KEY; const BUILD_CONCURRENCY = process.env.ONE_BUILD_CONCURRENCY ? Math.max(1, parseInt(process.env.ONE_BUILD_CONCURRENCY, 10)) : Math.max(1, Math.min((0, import_node_os.cpus)().length, 8)); function shouldUseWorkers(oneOptions) { if (process.env.ONE_BUILD_WORKERS === "0") return false; if (process.env.ONE_BUILD_WORKERS === "1") return true; return oneOptions?.build?.workers !== false; } process.on("uncaughtException", err => { console.error(err?.message || err); }); const HOOK_KEYS = ["resolveId", "load", "transform", "renderChunk", "generateBundle", "writeBundle", "buildStart", "buildEnd", "moduleParsed"]; function clonePluginHooks(config) { if (!config.plugins) return config; return { ...config, plugins: config.plugins.map(p => { if (!p || typeof p !== "object") return p; const cloned = { ...p }; for (const key of HOOK_KEYS) { if (cloned[key] && typeof cloned[key] === "object" && "handler" in cloned[key]) { cloned[key] = { ...cloned[key] }; } } return cloned; }) }; } async function build(args) { process.env.IS_VXRN_CLI = "true"; if (!process.env.NODE_ENV) { process.env.NODE_ENV = "production"; } else if (process.env.NODE_ENV !== "production") { console.warn(` \u26A0\uFE0F Warning: NODE_ENV is set to "${process.env.NODE_ENV}" (builds default to "production") `); } (0, import_label_process.labelProcess)("build"); (0, import_checkNodeVersion.checkNodeVersion)(); (0, import_setServerGlobals.setServerGlobals)(); const { oneOptions, config: viteLoadedConfig } = await (0, import_loadConfig.loadUserOneOptions)("build"); const routerRoot = (0, import_getRouterRootFromOneOptions.getRouterRootFromOneOptions)(oneOptions); if (oneOptions.web?.defaultRenderMode) { process.env.ONE_DEFAULT_RENDER_MODE = oneOptions.web.defaultRenderMode; } const deployConfig = normalizeDeploy(oneOptions.web?.deploy); if (!process.env.ONE_SERVER_URL && deployConfig) { const url = deployConfig.url ?? (deployConfig.target === "cloudflare" ? `https://${await getCloudflareProjectName(process.cwd())}.workers.dev` : void 0); if (url) { process.env.ONE_SERVER_URL = url; console.info(` \u2601\uFE0F ONE_SERVER_URL: ${url} `); } } const outDir = viteLoadedConfig?.config?.build?.outDir ?? "dist"; const manifest = (0, import_getManifest.getManifest)({ routerRoot, ignoredRouteFiles: oneOptions.router?.ignoredRouteFiles }); const serverOutputFormat = oneOptions.build?.server === false ? "esm" : oneOptions.build?.server?.outputFormat ?? "esm"; const buildStartTime = performance.now(); const vxrnOutput = await (0, import_vxrn.build)({ skipEnv: args.skipEnv ?? oneOptions.skipEnv, server: oneOptions.server, build: { analyze: true, server: oneOptions.build?.server === false ? false : { outputFormat: serverOutputFormat } } }, args); const bundleTime = performance.now() - buildStartTime; console.info(` \u23F1\uFE0F vite bundle: ${(bundleTime / 1e3).toFixed(2)}s `); if (!vxrnOutput || args.platform !== "web") { return; } const options = await (0, import_vxrn.fillOptions)(vxrnOutput.options, { mode: "prod" }); const { optimizeDeps } = (0, import_vxrn.getOptimizeDeps)("build"); const { rolldownOptions: _rolldownOptions, ...optimizeDepsNoRolldown } = optimizeDeps; const clonedWebBuildConfig = clonePluginHooks(vxrnOutput.webBuildConfig); const apiBuildConfig = (0, import_vite.mergeConfig)( // feels like this should build off the *server* build config not web clonedWebBuildConfig, { configFile: false, appType: "custom", optimizeDeps: optimizeDepsNoRolldown, environments: { client: { optimizeDeps: { rolldownOptions: _rolldownOptions } } } }); async function buildCustomRoutes(subFolder, routes) { const input = routes.reduce((entries, { page, file }) => { entries[page.slice(1) + ".js"] = (0, import_node_path.join)(routerRoot, file); return entries; }, {}); const outputFormat = oneOptions?.build?.api?.outputFormat ?? serverOutputFormat; const treeshake = oneOptions?.build?.api?.treeshake; const mergedConfig = (0, import_vite.mergeConfig)(apiBuildConfig, { appType: "custom", configFile: false, // plugins: [ // nodeExternals({ // exclude: optimizeDeps.include, // }) as any, // ], define: vxrnOutput.processEnvDefines, ssr: { noExternal: true, external: ["react", "react-dom"], optimizeDeps: optimizeDepsNoRolldown }, environments: { ssr: { optimizeDeps: { rolldownOptions: _rolldownOptions } } }, build: { ssr: true, emptyOutDir: false, outDir: `${outDir}/${subFolder}`, copyPublicDir: false, minify: false, rolldownOptions: { treeshake: treeshake ?? { moduleSideEffects: false }, plugins: [ // otherwise rollup is leaving commonjs-only top level imports... outputFormat === "esm" ? import_vxrn.rollupRemoveUnusedImportsPlugin : null].filter(Boolean), // too many issues // treeshake: { // moduleSideEffects: false, // }, // prevents it from shaking out the exports preserveEntrySignatures: "strict", input, external: [], output: { entryFileNames: "[name]", exports: "auto", ...(outputFormat === "esm" ? { format: "esm", esModule: true } : { format: "cjs", // Preserve folder structure and use .cjs extension entryFileNames: chunkInfo => { const name = chunkInfo.name.replace(/\.js$/, ".cjs"); return name; }, chunkFileNames: chunkInfo => { const dir = import_node_path.default.dirname(chunkInfo.name); const name = import_node_path.default.basename(chunkInfo.name, import_node_path.default.extname(chunkInfo.name)); return import_node_path.default.join(dir, `${name}-[hash].cjs`); }, assetFileNames: assetInfo => { const name = assetInfo.name ?? ""; const dir = import_node_path.default.dirname(name); const baseName = import_node_path.default.basename(name, import_node_path.default.extname(name)); const ext = import_node_path.default.extname(name); return import_node_path.default.join(dir, `${baseName}-[hash]${ext}`); } }) } } } }); const userApiBuildConf = oneOptions.build?.api?.config; const finalApiBuildConf = userApiBuildConf ? (0, import_vite.mergeConfig)(mergedConfig, userApiBuildConf) : mergedConfig; const output = await (0, import_vite.build)( // allow user merging api build config finalApiBuildConf); return output; } const builtMiddlewares = {}; const apiPromise = manifest.apiRoutes.length ? (console.info(` \u{1F528} build api routes `), buildCustomRoutes("api", manifest.apiRoutes)) : Promise.resolve(null); const middlewarePromise = manifest.middlewareRoutes.length ? (console.info(` \u{1F528} build middlewares `), buildCustomRoutes("middlewares", manifest.middlewareRoutes)) : Promise.resolve(null); const [apiOutput, middlewareBuildInfo] = await Promise.all([apiPromise, middlewarePromise]); if (middlewareBuildInfo) { for (const middleware of manifest.middlewareRoutes) { const absoluteRoot = (0, import_node_path.resolve)(process.cwd(), options.root); const fullPath = (0, import_node_path.join)(absoluteRoot, routerRoot, middleware.file); const outChunks = middlewareBuildInfo.output.filter(x => x.type === "chunk"); const chunk = outChunks.find(x => x.facadeModuleId === fullPath); if (!chunk) throw new Error(`internal err finding middleware`); builtMiddlewares[middleware.file] = (0, import_node_path.join)(outDir, "middlewares", chunk.fileName); } } globalThis["require"] = (0, import_node_module.createRequire)((0, import_node_path.join)(import_meta.url, "..")); const assets = []; const builtRoutes = []; const sitemapData = []; const collectImportsCache = /* @__PURE__ */new Map(); const cssFileContentsCache = /* @__PURE__ */new Map(); const criticalCSSOutputPaths = (0, import_criticalCSSPlugin.getCriticalCSSOutputPaths)(vxrnOutput.clientManifest); const limit = (0, import_pLimit.pLimit)(BUILD_CONCURRENCY); const useWorkers = shouldUseWorkers(oneOptions); const workerPool = useWorkers ? (0, import_workerPool.getWorkerPool)(BUILD_CONCURRENCY) : null; if (workerPool) { const serializableOptions = JSON.parse(JSON.stringify(oneOptions, (_key, value) => typeof value === "function" ? void 0 : value)); await workerPool.initialize(serializableOptions); } const staticStartTime = performance.now(); const modeLabel = useWorkers ? `workers: ${workerPool?.size}` : `concurrency: ${BUILD_CONCURRENCY}`; console.info(` \u{1F528} build static routes (${modeLabel}) `); const staticDir = (0, import_node_path.join)(`${outDir}/static`); const clientDir = (0, import_node_path.join)(`${outDir}/client`); await ensureDir(staticDir); if (!vxrnOutput.serverOutput) { throw new Error(`No server output`); } const clientChunksBySource = /* @__PURE__ */new Map(); if (vxrnOutput.clientOutput) { for (const chunk of vxrnOutput.clientOutput) { if (chunk.type === "chunk" && chunk.facadeModuleId) { clientChunksBySource.set(chunk.facadeModuleId, { fileName: chunk.fileName, imports: chunk.imports || [] }); } } } const outputEntries = [...vxrnOutput.serverOutput.entries()]; const layoutServerPaths = /* @__PURE__ */new Map(); for (const [, output] of outputEntries) { if (output.type === "asset") continue; const id = output.facadeModuleId || ""; const file = import_node_path.default.basename(id); if (file.startsWith("_layout") && id.includes(`/${routerRoot}/`)) { const relativePath = (0, import_node_path.relative)(process.cwd(), id).replace(`${routerRoot}/`, ""); const contextKey = `./${relativePath}`; layoutServerPaths.set(contextKey, output.fileName); } } const routeByPath = /* @__PURE__ */new Map(); for (const route of manifest.pageRoutes) { if (route.file) { const routePath = `${routerRoot}${route.file.slice(1)}`; routeByPath.set(routePath, route); } } for (const [, output] of outputEntries) { if (output.type === "asset") { assets.push(output); } } const moduleIdToServerChunk = /* @__PURE__ */new Map(); for (const [, output] of outputEntries) { if (output.type === "asset") continue; const moduleIds = output.moduleIds || (output.facadeModuleId ? [output.facadeModuleId] : []); for (const moduleId of moduleIds) { moduleIdToServerChunk.set(moduleId, output.fileName); } } for (const foundRoute of manifest.pageRoutes) { let collectImports = function (entry, { type = "js" } = {}) { const { imports = [], css } = entry; const cacheKey = `${entry.file || imports.join(",")}:${type}`; const cached = collectImportsCache.get(cacheKey); if (cached) return cached; const result = [...new Set([...(type === "js" ? imports : css || []), ...imports.flatMap(name => { const found = vxrnOutput.clientManifest[name]; if (!found) { console.warn(`No found imports`, name, vxrnOutput.clientManifest); } return collectImports(found, { type }); })].flat().filter(x => x && (type === "css" || x.endsWith(".js"))).map(x => type === "css" ? x : x.startsWith("assets/") ? x : `assets/${x.slice(1)}`))]; collectImportsCache.set(cacheKey, result); return result; }; if (!foundRoute.file) { continue; } const routeModulePath = (0, import_node_path.join)((0, import_node_path.resolve)(process.cwd(), options.root), routerRoot, foundRoute.file.slice(2)); const serverFileName = moduleIdToServerChunk.get(routeModulePath); if (!serverFileName) { if (foundRoute.type === "spa") { continue; } console.warn(`[one] No server chunk found for route: ${foundRoute.file}`); continue; } const onlyBuild = vxrnOutput.buildArgs?.only; if (onlyBuild) { const relativeId2 = foundRoute.file.slice(1); if (!import_micromatch.default.contains(relativeId2, onlyBuild)) { continue; } } const clientChunk = clientChunksBySource.get(routeModulePath); const manifestKey = `${routerRoot}${foundRoute.file.slice(1)}`; const clientManifestEntry = vxrnOutput.clientManifest[manifestKey]; if (!clientChunk && foundRoute.type !== "spa" && foundRoute.type !== "ssg") { console.warn(`No client chunk found for route: ${routeModulePath}`); continue; } foundRoute.loaderServerPath = serverFileName; const relativeId = foundRoute.file.replace(/^\.\//, "/"); if (foundRoute.layouts) { for (const layout of foundRoute.layouts) { const serverPath = layoutServerPaths.get(layout.contextKey); if (serverPath) { layout.loaderServerPath = serverPath; } } } const entryImports = collectImports(clientManifestEntry || {}); const layoutEntries = foundRoute.layouts?.flatMap(layout => { const clientKey = `${routerRoot}${layout.contextKey.slice(1)}`; const found = vxrnOutput.clientManifest[clientKey]; return found ? found : []; }) ?? []; const layoutImports = layoutEntries.flatMap(entry => { return [entry.file, ...collectImports(entry)]; }); const routePreloads = {}; const rootLayoutKey = `${routerRoot}/_layout.tsx`; const rootLayoutEntry = vxrnOutput.clientManifest[rootLayoutKey]; if (rootLayoutEntry) { routePreloads[`/${rootLayoutKey}`] = `/${rootLayoutEntry.file}`; } if (foundRoute.layouts) { for (const layout of foundRoute.layouts) { const clientKey = `${routerRoot}${layout.contextKey.slice(1)}`; const entry = vxrnOutput.clientManifest[clientKey]; if (entry) { routePreloads[`/${clientKey}`] = `/${entry.file}`; } } } if (clientChunk) { const routeKey = `/${routerRoot}${foundRoute.file.slice(1)}`; routePreloads[routeKey] = `/${clientChunk.fileName}`; } else if (clientManifestEntry) { const routeKey = `/${routerRoot}${foundRoute.file.slice(1)}`; routePreloads[routeKey] = `/${clientManifestEntry.file}`; } const preloadSetupFilePreloads = (() => { if (!oneOptions.setupFile) return []; const clientSetupFile = typeof oneOptions.setupFile === "string" ? oneOptions.setupFile : oneOptions.setupFile.client; if (!clientSetupFile) return []; const needle = clientSetupFile.replace(/^\.\//, ""); for (const file in vxrnOutput.clientManifest) { if (file === needle) { const entry = vxrnOutput.clientManifest[file]; return [entry.file // getting 404s for preloading the imports as well? // ...(entry.imports as string[]) ]; } } return []; })(); const allPreloads = [... /* @__PURE__ */new Set([...preloadSetupFilePreloads, // add the route entry js (like ./app/index.ts) - prefer direct chunk lookup ...(clientChunk ? [clientChunk.fileName] : clientManifestEntry ? [clientManifestEntry.file] : []), // add the virtual entry vxrnOutput.clientManifest["virtual:one-entry"].file, ...entryImports, ...layoutImports])].map(path => `/${path}`); const scriptLoadingMode = oneOptions.web?.experimental_scriptLoading; const useDeferredLoading = scriptLoadingMode === "defer-non-critical"; const useAggressiveLCP = scriptLoadingMode === "after-lcp-aggressive"; const needsSeparatedPreloads = useDeferredLoading || useAggressiveLCP; const criticalPreloads = needsSeparatedPreloads ? [... /* @__PURE__ */new Set([...preloadSetupFilePreloads, // add the virtual entry (framework bootstrap) vxrnOutput.clientManifest["virtual:one-entry"].file, // add the route entry js (like ./app/index.ts) - prefer direct chunk lookup ...(clientChunk ? [clientChunk.fileName] : clientManifestEntry ? [clientManifestEntry.file] : []), // add layout files (but not their deep imports) ...layoutEntries.map(entry => entry.file)])].map(path => `/${path}`) : void 0; const deferredPreloads = needsSeparatedPreloads ? [... /* @__PURE__ */new Set([...entryImports, ...layoutEntries.flatMap(entry => collectImports(entry))])].filter(path => !criticalPreloads.includes(`/${path}`)).map(path => `/${path}`) : void 0; const preloads2 = needsSeparatedPreloads ? [...criticalPreloads, ...deferredPreloads] : allPreloads; const allEntries = [clientManifestEntry, ...layoutEntries].filter(Boolean); const layoutCSS = [...new Set(layoutEntries.flatMap(entry => collectImports(entry, { type: "css" })).map(path => `/${path}`))]; const allCSS = [... /* @__PURE__ */new Set([...layoutCSS, // css from page entry ...(clientManifestEntry ? collectImports(clientManifestEntry, { type: "css" }).map(path => `/${path}`) : []), // root-level css (handles cssCodeSplit: false) ...Object.entries(vxrnOutput.clientManifest).filter(([key]) => key.endsWith(".css")).map(([, entry]) => `/${entry.file}`)])]; const hasCriticalCSS = allCSS.some(p => criticalCSSOutputPaths.has(p)); const needsCSSContents = oneOptions.web?.inlineLayoutCSS || hasCriticalCSS; let allCSSContents; if (needsCSSContents) { allCSSContents = await Promise.all(allCSS.map(async cssPath => { if (!oneOptions.web?.inlineLayoutCSS && !criticalCSSOutputPaths.has(cssPath)) { return ""; } const cached = cssFileContentsCache.get(cssPath); if (cached !== void 0) return cached; const filePath = (0, import_node_path.join)(clientDir, cssPath); try { const content = await import_fs_extra.default.readFile(filePath, "utf-8"); cssFileContentsCache.set(cssPath, content); return content; } catch (err) { console.warn(`[one] Warning: Could not read CSS file ${filePath}`); cssFileContentsCache.set(cssPath, ""); return ""; } })); } if (process.env.DEBUG) { console.info("[one] building routes", { foundRoute, layoutEntries, allEntries, allCSS }); } const serverJsPath = (0, import_node_path.join)(`${outDir}/server`, serverFileName); let exported; try { exported = await import((0, import_toAbsolute.toAbsolute)(serverJsPath)); } catch (err) { console.error(`Error importing page (original error)`, err); throw new Error(`Error importing page: ${serverJsPath}`, { cause: err }); } foundRoute.hasLoader = typeof exported.loader === "function"; const isDynamic = !!Object.keys(foundRoute.routeKeys).length; if (foundRoute.type === "ssg" && isDynamic && !foundRoute.page.includes("+not-found") && !foundRoute.page.includes("_sitemap") && !exported.generateStaticParams) { throw new Error(`[one] Error: Missing generateStaticParams Route ${foundRoute.page} of type ${foundRoute.type} must export generateStaticParams so build can complete. See docs on generateStaticParams: https://onestack.dev/docs/routing-exports#generatestaticparams `); } const paramsList = (await exported.generateStaticParams?.()) ?? [{}]; console.info(` [build] page ${relativeId} (with ${paramsList.length} routes) `); if (process.env.DEBUG) { console.info(`paramsList`, JSON.stringify(paramsList, null, 2)); } const routeSitemapExport = exported.sitemap; const isAfterLCPMode = scriptLoadingMode === "after-lcp" || scriptLoadingMode === "after-lcp-aggressive"; const useAfterLCP = foundRoute.type === "ssg" && isAfterLCPMode; const useAfterLCPAggressive = foundRoute.type === "ssg" && scriptLoadingMode === "after-lcp-aggressive"; const shouldCollectSitemap = foundRoute.type !== "api" && foundRoute.type !== "layout" && !foundRoute.isNotFound && !foundRoute.page.includes("+not-found") && !foundRoute.page.includes("_sitemap"); const pageBuilds = paramsList.map(params => { const path = (0, import_getPathnameFromFilePath.getPathnameFromFilePath)(relativeId, params, foundRoute.type === "ssg"); if (workerPool) { console.info(` \u21A6 route ${path}`); return workerPool.buildPage({ serverEntry: vxrnOutput.serverEntry, path, relativeId, params, foundRoute, clientManifestEntry, staticDir, clientDir, builtMiddlewares, serverJsPath, preloads: preloads2, allCSS, layoutCSS, routePreloads, allCSSContents, criticalPreloads, deferredPreloads, useAfterLCP, useAfterLCPAggressive }).then(built => ({ built, path })).catch(err => { console.warn(` \u26A0 skipping page ${path}: ${err.message}`); return null; }); } return limit(async () => { console.info(` \u21A6 route ${path}`); try { const built = await (0, import_one_server_only.runWithAsyncLocalContext)(async () => { return await (0, import_buildPage.buildPage)(vxrnOutput.serverEntry, path, relativeId, params, foundRoute, clientManifestEntry, staticDir, clientDir, builtMiddlewares, serverJsPath, preloads2, allCSS, layoutCSS, routePreloads, allCSSContents, criticalPreloads, deferredPreloads, useAfterLCP, useAfterLCPAggressive); }); return { built, path }; } catch (err) { console.warn(` \u26A0 skipping page ${path}: ${err.message}`); return null; } }); }); const results = (await Promise.all(pageBuilds)).filter(Boolean); for (const { built, path } of results) { builtRoutes.push(built); if (shouldCollectSitemap) { sitemapData.push({ path, routeExport: routeSitemapExport }); } } } if (workerPool) { await (0, import_workerPool.terminateWorkerPool)(); } const staticTime = performance.now() - staticStartTime; console.info(` \u23F1\uFE0F static routes: ${(staticTime / 1e3).toFixed(2)}s (${builtRoutes.length} pages) `); (0, import_buildPage.printBuildTimings)(); await moveAllFiles(staticDir, clientDir); await import_fs_extra.default.rm(staticDir, { force: true, recursive: true }); const routeMap = {}; const routeToBuildInfo = {}; const pathToRoute = {}; const preloads = {}; const cssPreloads = {}; const loaders = {}; for (const route of builtRoutes) { if (!route.cleanPath.includes("*")) { routeMap[route.cleanPath] = route.htmlPath; } const { // dont include loaderData it can be huge loaderData: _loaderData, ...rest } = route; routeToBuildInfo[route.routeFile] = rest; for (const p of getCleanPaths([route.path, route.cleanPath])) { pathToRoute[p] = route.routeFile; } preloads[route.preloadPath] = true; cssPreloads[route.cssPreloadPath] = true; loaders[route.loaderPath] = true; } function createBuildManifestRoute(route) { const { layouts, ...built } = route; if (layouts?.length) { ; built.layouts = layouts.map(layout => ({ contextKey: layout.contextKey, loaderServerPath: layout.loaderServerPath })); } const buildInfo = builtRoutes.find(x => x.routeFile === route.file); if (built.middlewares && buildInfo?.middlewares) { for (const [index, mw] of built.middlewares.entries()) { mw.contextKey = buildInfo.middlewares[index]; } } if (buildInfo) { built.loaderPath = buildInfo.loaderPath; } return built; } const buildInfoForWriting = { outDir, oneOptions, routeToBuildInfo, pathToRoute, manifest: { pageRoutes: manifest.pageRoutes.map(createBuildManifestRoute), apiRoutes: manifest.apiRoutes.map(createBuildManifestRoute), allRoutes: manifest.allRoutes.map(createBuildManifestRoute) }, routeMap, constants: JSON.parse(JSON.stringify({ ...constants })), preloads, cssPreloads, loaders, useRolldown: await (0, import_isRolldown.isRolldown)() }; await writeJSON((0, import_toAbsolute.toAbsolute)(`${outDir}/buildInfo.json`), buildInfoForWriting); await import_fs_extra.default.writeFile((0, import_node_path.join)(clientDir, "version.json"), JSON.stringify({ version: constants.CACHE_KEY })); console.info(` \u{1F6E1} skew protection: emitted version.json `); const sitemapConfig = oneOptions.web?.sitemap; if (sitemapConfig) { const sitemapOptions = typeof sitemapConfig === "boolean" ? {} : sitemapConfig; const sitemapXml = (0, import_generateSitemap.generateSitemap)(sitemapData, sitemapOptions); const sitemapPath = (0, import_node_path.join)(clientDir, "sitemap.xml"); await import_fs_extra.default.writeFile(sitemapPath, sitemapXml); console.info(` \u{1F4C4} generated sitemap.xml (${sitemapData.length} URLs) `); } const postBuildLogs = []; const platform = deployConfig?.target; if (platform) { postBuildLogs.push(`[one.build] platform ${platform}`); } switch (platform) { case "vercel": { const vercelJsonPath = (0, import_node_path.join)(options.root, "vercel.json"); if (import_fs_extra.default.existsSync(vercelJsonPath)) { try { const vercelConfig = JSON.parse(import_fs_extra.default.readFileSync(vercelJsonPath, "utf-8")); if (!vercelConfig.cleanUrls) { console.warn(` \u26A0\uFE0F Warning: Your vercel.json is missing "cleanUrls": true`); console.warn(` Without this, direct navigation to SSG pages will 404.`); console.warn(` Add "cleanUrls": true to your vercel.json to fix this. `); } } catch {} } await (0, import_buildVercelOutputDirectory.buildVercelOutputDirectory)({ apiOutput, buildInfoForWriting, clientDir, oneOptionsRoot: options.root, postBuildLogs }); break; } case "cloudflare": { const pageRouteMap = []; const apiRouteMap = []; const middlewareRouteMap = []; for (const [routeFile, info] of Object.entries(buildInfoForWriting.routeToBuildInfo)) { if (info.serverJsPath) { const importPath = "./" + info.serverJsPath.replace(new RegExp(`^${outDir}/`), ""); pageRouteMap.push(` '${routeFile}': () => import('${importPath}')`); } } for (const route of buildInfoForWriting.manifest.apiRoutes) { if (route.file) { const apiFileName = route.page.slice(1).replace(/\[/g, "_").replace(/\]/g, "_"); const importPath = `./api/${apiFileName}.js`; apiRouteMap.push(` '${route.page}': () => import('${importPath}')`); } } for (const [, builtPath] of Object.entries(builtMiddlewares)) { const importPath = "./" + builtPath.replace(new RegExp(`^${outDir}/`), ""); middlewareRouteMap.push(` '${builtPath}': () => import('${importPath}')`); } const workerSrcPath = (0, import_node_path.join)(options.root, outDir, "_worker-src.js"); const workerCode = `// Polyfill MessageChannel for React SSR (not available in Cloudflare Workers by default) if (typeof MessageChannel === 'undefined') { globalThis.MessageChannel = class MessageChannel { constructor() { this.port1 = { postMessage: () => {}, onmessage: null, close: () => {} } this.port2 = { postMessage: () => {}, onmessage: null, close: () => {} } } } } import { serve, setFetchStaticHtml } from 'one/serve-worker' // Lazy import map - modules load on-demand when route is matched const lazyRoutes = { serverEntry: () => import('./server/_virtual_one-entry.js'), pages: { ${pageRouteMap.join(",\n")} }, api: { ${apiRouteMap.join(",\n")} }, middlewares: { ${middlewareRouteMap.join(",\n")} } } const buildInfo = ${JSON.stringify(buildInfoForWriting)} let server export default { async fetch(request, env, ctx) { if (!server) { server = await serve(buildInfo, lazyRoutes) } // set up static HTML fetcher for this request (uses ASSETS binding) if (env.ASSETS) { setFetchStaticHtml(async (path) => { try { const url = new URL(request.url) url.pathname = path const assetResponse = await env.ASSETS.fetch(new Request(url)) if (assetResponse && assetResponse.ok) { return await assetResponse.text() } } catch (e) { // asset not found } return null }) } try { const response = await server.fetch(request) // no route matched or 404 \u2192 try static assets if (!response || response.status === 404) { if (env.ASSETS) { try { const assetResponse = await env.ASSETS.fetch(request) if (assetResponse && assetResponse.status !== 404) { return assetResponse } } catch (e) { // asset not found, continue with original response } } } return response } finally { setFetchStaticHtml(null) } } } `; await import_fs_extra.default.writeFile(workerSrcPath, workerCode); console.info("\n [cloudflare] Bundling worker..."); await (0, import_vite.build)({ root: options.root, mode: "production", logLevel: "warn", build: { outDir, emptyOutDir: false, // Use SSR mode with node target for proper Node.js module resolution ssr: workerSrcPath, rolldownOptions: { external: [ // React Native dev tools - not needed in production "@react-native/dev-middleware", "@react-native/debugger-shell", "metro", "metro-core", "metro-runtime", // Native modules that can't run in workers /\.node$/], output: { entryFileNames: "worker.js", format: "es", // Keep dynamic imports separate for lazy loading inlineDynamicImports: false } }, minify: true, target: "esnext" }, define: { "process.env.NODE_ENV": JSON.stringify("production"), "process.env.VITE_ENVIRONMENT": JSON.stringify("ssr") }, resolve: { conditions: ["workerd", "worker", "node", "module", "default"], alias: [ // rolldown can't parse react-native's Flow syntax; alias to react-native-web for ssr { find: /^react-native\/Libraries\/.*/, replacement: (0, import_resolve.resolvePath)("@vxrn/vite-plugin-metro/empty", options.root) }, { find: "react-native/package.json", replacement: (0, import_resolve.resolvePath)("react-native-web/package.json", options.root) }, { find: "react-native", replacement: (0, import_resolve.resolvePath)("react-native-web", options.root) }, { find: "react-native-safe-area-context", replacement: (0, import_resolve.resolvePath)("@vxrn/safe-area", options.root) }] }, ssr: { target: "node", noExternal: true } }); await import_fs_extra.default.remove(workerSrcPath); const projectName = await getCloudflareProjectName(options.root); const userWranglerConfig = await loadUserWranglerConfig(options.root); const wranglerConfig = createCloudflareWranglerConfig(projectName, userWranglerConfig?.config); if (userWranglerConfig) { console.info(` [cloudflare] Merging ${(0, import_node_path.relative)(options.root, userWranglerConfig.path)} into ${outDir}/wrangler.jsonc`); } await import_fs_extra.default.writeFile((0, import_node_path.join)(options.root, outDir, "wrangler.jsonc"), `${JSON.stringify(wranglerConfig, null, 2)} `); postBuildLogs.push(`Cloudflare worker bundled at ${outDir}/worker.js`); postBuildLogs.push(`To deploy: cd ${outDir} && wrangler deploy`); break; } } const securityScanOption = oneOptions.build?.securityScan; const securityScanLevel = securityScanOption === false ? null : securityScanOption === true || securityScanOption === void 0 ? "warn" : typeof securityScanOption === "string" ? securityScanOption : securityScanOption.level ?? "warn"; const securitySafePatterns = typeof securityScanOption === "object" && securityScanOption !== null ? securityScanOption.safePatterns : void 0; if (securityScanLevel) { const { runSecurityScan } = await import("./securityScan"); const passed = await runSecurityScan(clientDir, securityScanLevel, securitySafePatterns); if (!passed) { process.exit(1); } } if (postBuildLogs.length) { console.info(` `); postBuildLogs.forEach(log => { console.info(` \xB7 ${log}`); }); } console.info(` \u{1F49B} build complete `); } const TRAILING_INDEX_REGEX = /\/index(\.(web))?/; function getCleanPaths(possiblePaths) { return Array.from(new Set(Array.from(new Set(possiblePaths)).flatMap(p => { const paths = [p]; if (p.match(TRAILING_INDEX_REGEX)) { const pathWithTrailingIndexRemoved = p.replace(TRAILING_INDEX_REGEX, ""); paths.push(pathWithTrailingIndexRemoved); paths.push(pathWithTrailingIndexRemoved + "/"); } return paths; }))); } async function moveAllFiles(src, dest) { try { await import_fs_extra.default.copy(src, dest, { overwrite: true, errorOnExist: false }); } catch (err) { console.error("Error moving files:", err); } }