UNPKG

one

Version:

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

455 lines (447 loc) 17.3 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 buildPage_exports = {}; __export(buildPage_exports, { buildPage: () => buildPage, printBuildTimings: () => printBuildTimings }); module.exports = __toCommonJS(buildPage_exports); var import_node_path = require("node:path"); var import_fs_extra = __toESM(require("fs-extra"), 1); var constants = __toESM(require("../constants.cjs"), 1); var import_constants = require("../constants.cjs"); var import_cleanUrl = require("../utils/cleanUrl.cjs"); var import_isResponse = require("../utils/isResponse.cjs"); var import_toAbsolute = require("../utils/toAbsolute.cjs"); var import_replaceLoader = require("../vite/replaceLoader.cjs"); const { readFile, outputFile } = import_fs_extra.default; function urlPathToFilePath(urlPath) { const parts = urlPath.replace(/^\//, "").split("/"); return (0, import_node_path.join)(...parts); } const buildTiming = process.env.ONE_BUILD_TIMING === "1"; const timings = {}; function recordTiming(label, ms) { if (!buildTiming) return; (timings[label] ||= []).push(ms); } function printBuildTimings() { if (!buildTiming) return; console.info("\n\u{1F4CA} Build timing breakdown:"); for (const [label, times] of Object.entries(timings)) { const total = times.reduce((a, b) => a + b, 0); const avg = total / times.length; console.info(` ${label}: ${avg.toFixed(1)}ms avg, ${total.toFixed(0)}ms total (${times.length} calls)`); } } async function buildPage(serverEntry, path, relativeId, params, foundRoute, clientManifestEntry, staticDir, clientDir, builtMiddlewares, serverJsPath, preloads, allCSS, layoutCSS, routePreloads, allCSSContents, criticalPreloads, deferredPreloads, useAfterLCP, useAfterLCPAggressive) { let t0 = performance.now(); const render = await getRender(serverEntry); recordTiming("getRender", performance.now() - t0); const htmlPath = `${path.endsWith("/") ? `${removeTrailingSlash(path)}/index` : path}.html`; const clientJsPath = clientManifestEntry ? (0, import_node_path.join)(clientDir, clientManifestEntry.file) : ""; const htmlOutPath = (0, import_toAbsolute.toAbsolute)((0, import_node_path.join)(staticDir, htmlPath)); const preloadPath = (0, import_cleanUrl.getPreloadPath)(path); const cssPreloadPath = (0, import_cleanUrl.getPreloadCSSPath)(path); let loaderPath = ""; let loaderData = {}; try { const routeImports = []; const routeRegistrations = []; let routeIndex = 0; for (const [routeKey, bundlePath] of Object.entries(routePreloads)) { const varName = `_r${routeIndex++}`; routeImports.push(`import * as ${varName} from "${bundlePath}"`); routeRegistrations.push(`registerPreloadedRoute("${routeKey}", ${varName})`); } const registrationCalls = routeRegistrations.map(call => call.replace("registerPreloadedRoute(", "window.__oneRegisterPreloadedRoute(")); const preloadContent = [ // import all route modules ...routeImports, // static imports for cache warming (original behavior) ...preloads.map(preload => `import "${preload}"`), // register all route modules using window global ...registrationCalls].join("\n"); t0 = performance.now(); await import_fs_extra.default.writeFile((0, import_node_path.join)(clientDir, urlPathToFilePath(preloadPath)), preloadContent); recordTiming("writePreload", performance.now() - t0); const uniqueCSS = [...new Set(allCSS)]; const cssPreloadContent = ` const CSS_TIMEOUT = 1000 const cssUrls = ${JSON.stringify(uniqueCSS)} // Global cache for loaded CSS - avoids DOM queries and tracks across navigations const loaded = (window.__oneLoadedCSS ||= new Set()) // Prefetch CSS without applying - called on link hover export function prefetchCSS() { cssUrls.forEach(href => { if (loaded.has(href)) return if (document.querySelector(\`link[href="\${href}"]\`)) return const link = document.createElement('link') link.rel = 'prefetch' link.as = 'style' link.href = href document.head.appendChild(link) }) } // Inject CSS to apply styles - called on actual navigation export function injectCSS() { return Promise.all(cssUrls.map(href => { // Skip if already loaded if (loaded.has(href)) return Promise.resolve() // Remove any prefetch link for this href const prefetchLink = document.querySelector(\`link[rel="prefetch"][href="\${href}"]\`) if (prefetchLink) prefetchLink.remove() // Skip if stylesheet already exists in DOM if (document.querySelector(\`link[rel="stylesheet"][href="\${href}"]\`)) { loaded.add(href) return Promise.resolve() } return new Promise(resolve => { const link = document.createElement('link') link.rel = 'stylesheet' link.href = href const timeoutId = setTimeout(() => { console.warn('[one] CSS load timeout:', href) loaded.add(href) resolve() }, CSS_TIMEOUT) link.onload = link.onerror = () => { clearTimeout(timeoutId) loaded.add(href) resolve() } document.head.appendChild(link) }) })) } // For backwards compatibility, also prefetch on import prefetchCSS() `; t0 = performance.now(); await import_fs_extra.default.writeFile((0, import_node_path.join)(clientDir, urlPathToFilePath(cssPreloadPath)), cssPreloadContent); recordTiming("writeCSSPreload", performance.now() - t0); t0 = performance.now(); const exported = await import((0, import_toAbsolute.toAbsolute)(serverJsPath)); recordTiming("importServerModule", performance.now() - t0); const loaderProps = { path, params }; const matches = []; t0 = performance.now(); if (foundRoute.layouts?.length) { const layoutResults = await Promise.all(foundRoute.layouts.map(async layout => { try { const layoutServerPath = layout.loaderServerPath; if (!layoutServerPath) { return { contextKey: layout.contextKey, loaderData: void 0 }; } const serverDir = (0, import_node_path.join)(clientDir, "..", "server"); const layoutExported = await import((0, import_toAbsolute.toAbsolute)((0, import_node_path.join)(serverDir, layoutServerPath))); const layoutLoaderData = await layoutExported?.loader?.(loaderProps); return { contextKey: layout.contextKey, loaderData: layoutLoaderData }; } catch (err) { if ((0, import_isResponse.isResponse)(err)) { throw err; } console.warn(`[one] Warning: layout loader failed for ${layout.contextKey}:`, err); return { contextKey: layout.contextKey, loaderData: void 0 }; } })); for (const result of layoutResults) { matches.push({ routeId: result.contextKey, pathname: path, params: params || {}, loaderData: result.loaderData }); } } recordTiming("layoutLoaders", performance.now() - t0); t0 = performance.now(); let loaderRedirectInfo = null; if (exported.loader && foundRoute.type !== "ssr") { try { loaderData = (await exported.loader?.(loaderProps)) ?? null; } catch (err) { if ((0, import_isResponse.isResponse)(err)) { loaderRedirectInfo = extractRedirectInfo(err); } else { throw err; } } if (!loaderRedirectInfo && loaderData && ((0, import_isResponse.isResponse)(loaderData) || loaderData instanceof Response || loaderData?.constructor?.name === "Response")) { loaderRedirectInfo = extractRedirectInfo(loaderData); loaderData = {}; } if (clientJsPath) { const loaderPartialPath = (0, import_node_path.join)(clientDir, urlPathToFilePath((0, import_cleanUrl.getLoaderPath)(path))); const uncachedNativePath = loaderPartialPath.replace(constants.LOADER_JS_POSTFIX, import_constants.LOADER_JS_POSTFIX_UNCACHED).replace(/\.js$/, ".native.js"); if (loaderRedirectInfo) { const redirectData = JSON.stringify({ __oneRedirect: loaderRedirectInfo.path, __oneRedirectStatus: loaderRedirectInfo.status }); await outputFile(loaderPartialPath, `export function loader(){return ${redirectData}}`); const nativeCjs = `exports.loader = function(){return ${redirectData}}`; await outputFile(loaderPartialPath.replace(/\.js$/, ".native.js"), nativeCjs); await outputFile(uncachedNativePath, nativeCjs); loaderPath = (0, import_cleanUrl.getLoaderPath)(path); loaderData = {}; } else { const code = await readFile(clientJsPath, "utf-8"); const withLoader = // super dirty to quickly make ssr loaders work until we have better ` if (typeof document === 'undefined') globalThis.document = {} ` + (0, import_replaceLoader.replaceLoader)({ code, loaderData }); await outputFile(loaderPartialPath, withLoader); const nativeCjs = `exports.loader = function(){return ${JSON.stringify(loaderData)}}`; await outputFile(loaderPartialPath.replace(/\.js$/, ".native.js"), nativeCjs); await outputFile(uncachedNativePath, nativeCjs); loaderPath = (0, import_cleanUrl.getLoaderPath)(path); } } } recordTiming("pageLoader", performance.now() - t0); matches.push({ routeId: foundRoute.file, pathname: path, params: params || {}, loaderData }); if (foundRoute.type !== "ssr") { globalThis["__vxrnresetState"]?.(); if (foundRoute.type === "ssg") { const renderPreloads = criticalPreloads || preloads; const renderDeferredPreloads = useAfterLCPAggressive ? [] : deferredPreloads; t0 = performance.now(); let html = await render({ path, preloads: renderPreloads, deferredPreloads: renderDeferredPreloads, loaderProps, loaderData, css: allCSS, cssContents: allCSSContents, mode: "ssg", routePreloads, matches }); recordTiming("ssrRender", performance.now() - t0); if (useAfterLCP) { html = applyAfterLCPScriptLoad(html, preloads); } t0 = performance.now(); await outputFile(htmlOutPath, html); recordTiming("writeHTML", performance.now() - t0); } else if (foundRoute.type === "spa") { const needsSpaShell = foundRoute.layouts?.some(layout => layout.layoutRenderMode === "ssg" || layout.layoutRenderMode === "ssr"); if (needsSpaShell) { globalThis["__vxrnresetState"]?.(); const renderPreloads = criticalPreloads || preloads; const renderDeferredPreloads = deferredPreloads || []; const layoutMatches = matches.slice(0, -1); t0 = performance.now(); let html = await render({ path, preloads: renderPreloads, deferredPreloads: renderDeferredPreloads, loaderProps, // don't pass loaderData for spa-shell - the page loader runs on client // passing {} here would make useLoaderState think data is preloaded loaderData: void 0, css: allCSS, cssContents: allCSSContents, mode: "spa-shell", routePreloads, matches: layoutMatches }); recordTiming("spaShellRender", performance.now() - t0); if (useAfterLCP) { html = applyAfterLCPScriptLoad(html, preloads); } t0 = performance.now(); await outputFile(htmlOutPath, html); recordTiming("writeHTML", performance.now() - t0); } else { let renderCSSTag = function (file, index) { const content = allCSSContents?.[index]; if (content) { return ` <style>${content}</style>`; } return ` <link rel="stylesheet" href=${file} />`; }; const layoutCSSSet = new Set(layoutCSS); const layoutCssOutput = allCSS.map((file, i) => layoutCSSSet.has(file) ? renderCSSTag(file, i) : "").filter(Boolean).join("\n"); const pageCssOutput = allCSS.map((file, i) => !layoutCSSSet.has(file) ? renderCSSTag(file, i) : "").filter(Boolean).join("\n"); const criticalScripts = (criticalPreloads || preloads).map(preload => ` <script type="module" src="${preload}"></script>`).join("\n"); await outputFile(htmlOutPath, `<!DOCTYPE html><html><head> ${constants.getSpaHeaderElements({ serverContext: { loaderProps, loaderData } })} ${layoutCssOutput} ${criticalScripts} ${pageCssOutput} </head><body></body></html>`); } } } } catch (err) { const errMsg = err instanceof Error ? `${err.message} ${err.stack}` : `${err}`; console.error(`Error building static page at ${path} with id ${relativeId}: ${errMsg} loaderData: ${JSON.stringify(loaderData || null, null, 2)} params: ${JSON.stringify(params || null, null, 2)}`); console.error(err); throw err; } const middlewares = (foundRoute.middlewares || []).map(x => builtMiddlewares[x.contextKey]); const cleanPath = path === "/" ? path : removeTrailingSlash(path); return { type: foundRoute.type, css: allCSS, layoutCSS, cssContents: allCSSContents, routeFile: foundRoute.file, middlewares, cleanPath, preloadPath, cssPreloadPath, loaderPath, clientJsPath, serverJsPath, htmlPath, loaderData, params, path, preloads, criticalPreloads, deferredPreloads }; } async function getRender(serverEntry) { try { const serverImport = await import(serverEntry); const render = serverImport.default.render || // for an unknown reason this is necessary serverImport.default.default?.render; if (typeof render !== "function") { throw new Error(`didn't find render function in entry: ${serverEntry}`); } return render; } catch (err) { console.error(`\u274C Error importing the root entry:`); console.error(` This error happened in the built file: ${serverEntry}`); console.error(err["stack"]); throw err; } } function removeTrailingSlash(path) { return path.endsWith("/") ? path.slice(0, path.length - 1) : path; } function extractRedirectInfo(response) { if (response.status >= 300 && response.status < 400) { const location = response.headers.get("location"); if (location) { try { const url = new URL(location); return { path: url.pathname + url.search + url.hash, status: response.status }; } catch { return { path: location, status: response.status }; } } } return null; } function applyAfterLCPScriptLoad(html, preloads) { html = html.replace(/<script\s+type="module"[^>]*async[^>]*><\/script>/gi, ""); const loaderScript = ` <script> (function() { var scripts = ${JSON.stringify(preloads)}; function loadScripts() { scripts.forEach(function(src) { var script = document.createElement('script'); script.type = 'module'; script.src = src; document.head.appendChild(script); }); } function waitIdle(n) { if (n <= 0) { requestAnimationFrame(function() { requestAnimationFrame(loadScripts); }); return; } setTimeout(function() { setTimeout(function() { waitIdle(n - 1); }, 0); }, 0); } waitIdle(5); })(); </script>`; html = html.replace("</head>", `${loaderScript}</head>`); return html; }