UNPKG

vike

Version:

The Framework *You* Control - Next.js & Nuxt alternative for unprecedented flexibility and dependability.

783 lines (782 loc) 42.8 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.runPrerender = runPrerender; // Failed attempt to run this file (i.e. pre-rendering) in a separate process: https://github.com/vikejs/vike/commit/48feda87012115b32a5c9701da354cb8c138dfd2 // - The issue is that prerenderContext needs to be serialized for being able to pass it from the child process to the parent process. // - The prerenderContext is used by vike-vercel const node_path_1 = __importDefault(require("node:path")); const index_js_1 = require("../../shared/route/index.js"); const utils_js_1 = require("./utils.js"); const renderPageAlreadyRouted_js_1 = require("../runtime/renderPage/renderPageAlreadyRouted.js"); const createPageContextServerSide_js_1 = require("../runtime/renderPage/createPageContextServerSide.js"); const picocolors_1 = __importDefault(require("@brillout/picocolors")); const node_os_1 = require("node:os"); const globalContext_js_1 = require("../runtime/globalContext.js"); const vite_1 = require("vite"); const getPageFiles_js_1 = require("../../shared/getPageFiles.js"); const getPageContextRequestUrl_js_1 = require("../../shared/getPageContextRequestUrl.js"); const resolveRouteString_js_1 = require("../../shared/route/resolveRouteString.js"); const getConfigValueRuntime_js_1 = require("../../shared/page-configs/getConfigValueRuntime.js"); const loadConfigValues_js_1 = require("../../shared/page-configs/loadConfigValues.js"); const error_page_js_1 = require("../../shared/error-page.js"); const abort_js_1 = require("../../shared/route/abort.js"); const loadPageConfigsLazyServerSide_js_1 = require("../runtime/renderPage/loadPageConfigsLazyServerSide.js"); const getHook_js_1 = require("../../shared/hooks/getHook.js"); const noRouteMatch_js_1 = require("../../shared/route/noRouteMatch.js"); const resolveVikeConfigInternal_js_1 = require("../vite/shared/resolveVikeConfigInternal.js"); const execHook_js_1 = require("../../shared/hooks/execHook.js"); const context_js_1 = require("./context.js"); const resolvePrerenderConfig_js_1 = require("./resolvePrerenderConfig.js"); const getOutDirs_js_1 = require("../vite/shared/getOutDirs.js"); const node_fs_1 = __importDefault(require("node:fs")); const getProxyForPublicUsage_js_1 = require("../../shared/getProxyForPublicUsage.js"); const resolveRedirects_js_1 = require("../runtime/renderPage/resolveRedirects.js"); const utils_js_2 = require("../runtime/utils.js"); const docLink = 'https://vike.dev/i18n#pre-rendering'; async function runPrerender(options = {}, trigger) { (0, context_js_1.setWasPrerenderRun)(trigger); checkOutdatedOptions(options); (0, utils_js_1.onSetupPrerender)(); (0, globalContext_js_1.setGlobalContext_isPrerendering)(); (0, getHook_js_1.getHook_setIsPrerenderering)(); const logLevel = !!options.onPagePrerender ? 'warn' : 'info'; if (logLevel === 'info') { console.log(`${picocolors_1.default.cyan(`vike v${utils_js_1.PROJECT_VERSION}`)} ${picocolors_1.default.green('pre-rendering HTML...')}`); } await disableReactStreaming(); const viteConfig = await (0, vite_1.resolveConfig)(options.viteConfig || {}, 'build', 'production'); const vikeConfig = await (0, resolveVikeConfigInternal_js_1.getVikeConfigInternal)(); const { outDirServer } = (0, getOutDirs_js_1.getOutDirs)(viteConfig); const prerenderConfigGlobal = (0, resolvePrerenderConfig_js_1.resolvePrerenderConfigGlobal)(vikeConfig); const { partial, noExtraDir, parallel, defaultLocalValue, isPrerenderingEnabled } = prerenderConfigGlobal; if (!isPrerenderingEnabled) { (0, utils_js_1.assert)(trigger !== 'auto-run'); /* TODO/v1-release: use this assertUsage() again. * - Make sure https://github.com/magne4000/vite-plugin-vercel/pull/156 is merged before using this assertUsage() again. (Otherwise vite-plugin-vercel will trigger this assertUsage() call.) * - Done: PR is merged as of June 20205 assertUsage( false, `You're executing ${pc.cyan(standaloneTrigger)} but you didn't enable pre-rendering. Use the ${pc.cyan('prerender')} setting (${pc.underline('https://vike.dev/prerender')}) to enable pre-rendering for at least one page.` ) */ return { viteConfig }; } const concurrencyLimit = (0, utils_js_1.pLimit)(parallel === false || parallel === 0 ? 1 : parallel === true || parallel === undefined ? (0, node_os_1.cpus)().length : parallel); await (0, globalContext_js_1.initGlobalContext_runPrerender)(); const { globalContext } = await (0, globalContext_js_1.getGlobalContextServerInternal)(); globalContext._pageFilesAll.forEach(assertExportNames); const prerenderContext = { pageContexts: [], output: [], _noExtraDir: noExtraDir, _pageContextInit: options.pageContextInit ?? null, _prerenderedPageContexts: {}, }; const doNotPrerenderList = []; await collectDoNoPrerenderList(vikeConfig._pageConfigs, doNotPrerenderList, defaultLocalValue, concurrencyLimit, globalContext); // Allow user to create `pageContext` for parameterized routes and/or bulk data fetching // https://vike.dev/onBeforePrerenderStart await callOnBeforePrerenderStartHooks(prerenderContext, globalContext, concurrencyLimit, doNotPrerenderList); // Create `pageContext` for each page with a static route const urlList = getUrlListFromPagesWithStaticRoute(globalContext, doNotPrerenderList); await createPageContexts(urlList, prerenderContext, globalContext, concurrencyLimit, false); // Create `pageContext` for 404 page const urlList404 = getUrlList404(globalContext); await createPageContexts(urlList404, prerenderContext, globalContext, concurrencyLimit, true); // Allow user to duplicate the list of `pageContext` for i18n // https://vike.dev/onPrerenderStart await callOnPrerenderStartHook(prerenderContext, globalContext, concurrencyLimit); let prerenderedCount = 0; // Write files as soon as pages finish rendering (instead of writing all files at once only after all pages have rendered). const onComplete = async (htmlFile) => { prerenderedCount++; const { pageId } = htmlFile.pageContext; (0, utils_js_1.assert)((typeof pageId === 'string' && pageId) || pageId === null); if (pageId) { prerenderContext._prerenderedPageContexts[pageId] = htmlFile.pageContext; } await writeFiles(htmlFile, viteConfig, options.onPagePrerender, prerenderContext, logLevel); }; await prerenderPages(prerenderContext, concurrencyLimit, onComplete); warnContradictoryNoPrerenderList(prerenderContext._prerenderedPageContexts, doNotPrerenderList); const { redirects, isPrerenderingEnabledForAllPages } = prerenderConfigGlobal; if (redirects !== null ? redirects : isPrerenderingEnabledForAllPages) { const showWarningUponDynamicRedirects = !prerenderConfigGlobal.partial; await prerenderRedirects(globalContext, onComplete, showWarningUponDynamicRedirects); } if (logLevel === 'info') { console.log(`${picocolors_1.default.green(`✓`)} ${prerenderedCount} HTML documents pre-rendered.`); } await warnMissingPages(prerenderContext._prerenderedPageContexts, globalContext, doNotPrerenderList, partial); const prerenderContextPublic = preparePrerenderContextForPublicUsage(prerenderContext); (0, utils_js_1.objectAssign)(vikeConfig.prerenderContext, prerenderContextPublic, true); if (prerenderConfigGlobal.isPrerenderingEnabledForAllPages && !prerenderConfigGlobal.keepDistServer) { node_fs_1.default.rmSync(outDirServer, { recursive: true }); } return { viteConfig }; } async function collectDoNoPrerenderList(pageConfigs, doNotPrerenderList, defaultLocalValue, concurrencyLimit, globalContext) { // V1 design pageConfigs.forEach((pageConfig) => { const prerenderConfigLocal = (0, resolvePrerenderConfig_js_1.resolvePrerenderConfigLocal)(pageConfig); const { pageId } = pageConfig; if (!prerenderConfigLocal) { if (!defaultLocalValue) { doNotPrerenderList.push({ pageId }); } } else { const { value } = prerenderConfigLocal; if (value === false) { doNotPrerenderList.push({ pageId }); } } }); // Old design // TODO/v1-release: remove await Promise.all(globalContext._pageFilesAll .filter((p) => { assertExportNames(p); if (!p.exportNames?.includes('doNotPrerender')) return false; (0, utils_js_1.assertUsage)(p.fileType !== '.page.client', `${p.filePath} (which is a \`.page.client.js\` file) has \`export { doNotPrerender }\` but it is only allowed in \`.page.server.js\` or \`.page.js\` files`); return true; }) .map((p) => concurrencyLimit(async () => { (0, utils_js_1.assert)(p.loadFile); await p.loadFile(); }))); globalContext._allPageIds.forEach((pageId) => { const pageFilesServerSide = (0, getPageFiles_js_1.getPageFilesServerSide)(globalContext._pageFilesAll, pageId); for (const p of pageFilesServerSide) { if (!p.exportNames?.includes('doNotPrerender')) continue; const { fileExports } = p; (0, utils_js_1.assert)(fileExports); (0, utils_js_1.assert)((0, utils_js_1.hasProp)(fileExports, 'doNotPrerender')); const { doNotPrerender } = fileExports; (0, utils_js_1.assertUsage)(doNotPrerender === true || doNotPrerender === false, `The \`export { doNotPrerender }\` value of ${p.filePath} should be \`true\` or \`false\``); if (!doNotPrerender) { // Do pre-render `pageId` return; } else { // Don't pre-render `pageId` doNotPrerenderList.push({ pageId }); } } }); } function assertExportNames(pageFile) { const { exportNames, fileType } = pageFile; (0, utils_js_1.assert)(exportNames || fileType === '.page.route' || fileType === '.css', pageFile.filePath); } async function callOnBeforePrerenderStartHooks(prerenderContext, globalContext, concurrencyLimit, doNotPrerenderList) { const onBeforePrerenderStartHooks = []; // V1 design await Promise.all(globalContext._pageConfigs.map((pageConfig) => concurrencyLimit(async () => { const hookName = 'onBeforePrerenderStart'; const pageConfigLoaded = await (0, loadConfigValues_js_1.loadConfigValues)(pageConfig, false); const hook = (0, getHook_js_1.getHookFromPageConfig)(pageConfigLoaded, hookName); if (!hook) return; const { hookFn, hookFilePath, hookTimeout } = hook; onBeforePrerenderStartHooks.push({ hookFn, hookName: 'onBeforePrerenderStart', hookFilePath, pageId: pageConfig.pageId, hookTimeout, }); }))); // 0.4 design await Promise.all(globalContext._pageFilesAll .filter((p) => { assertExportNames(p); if (!p.exportNames?.includes('prerender')) return false; (0, utils_js_1.assertUsage)(p.fileType === '.page.server', `${p.filePath} (which is a \`${p.fileType}.js\` file) has \`export { prerender }\` but it is only allowed in \`.page.server.js\` files`); return true; }) .map((p) => concurrencyLimit(async () => { await p.loadFile?.(); const hookFn = p.fileExports?.prerender; if (!hookFn) return; (0, utils_js_1.assertUsage)((0, utils_js_1.isCallable)(hookFn), `\`export { prerender }\` of ${p.filePath} should be a function.`); const hookFilePath = p.filePath; (0, utils_js_1.assert)(hookFilePath); onBeforePrerenderStartHooks.push({ hookFn, hookName: 'prerender', hookFilePath, pageId: p.pageId, hookTimeout: (0, getHook_js_1.getHookTimeoutDefault)('onBeforePrerenderStart'), }); }))); await Promise.all(onBeforePrerenderStartHooks.map(({ pageId, ...hook }) => concurrencyLimit(async () => { if (doNotPrerenderList.find((p) => p.pageId === pageId)) return; const { hookName, hookFilePath } = hook; const prerenderResult = await (0, execHook_js_1.execHookDirectWithoutPageContext)(() => hook.hookFn(), hook); const result = normalizeOnPrerenderHookResult(prerenderResult, hookFilePath, hookName); // Handle result await Promise.all(result.map(async ({ url, pageContext }) => { // Assert no duplication { const pageContextFound = prerenderContext.pageContexts.find((pageContext) => isSameUrl(pageContext.urlOriginal, url)); if (pageContextFound) { (0, utils_js_1.assert)(pageContextFound._providedByHook); const providedTwice = hookFilePath === pageContextFound._providedByHook.hookFilePath ? `twice by the ${hookName}() hook (${hookFilePath})` : `twice: by the ${hookName}() hook (${hookFilePath}) as well as by the hook ${pageContextFound._providedByHook.hookFilePath}() (${pageContextFound._providedByHook.hookName})`; (0, utils_js_1.assertUsage)(false, `URL ${picocolors_1.default.cyan(url)} provided ${providedTwice}. Make sure to provide the URL only once instead.`); } } // Add result const providedByHook = { hookFilePath, hookName }; const pageContextNew = await createPageContextPrerendering(url, prerenderContext, globalContext, false, undefined, providedByHook); prerenderContext.pageContexts.push(pageContextNew); if (pageContext) { (0, utils_js_1.objectAssign)(pageContextNew, { _pageContextAlreadyProvidedByOnPrerenderHook: true }); (0, utils_js_1.objectAssign)(pageContextNew, pageContext); } })); }))); } function getUrlListFromPagesWithStaticRoute(globalContext, doNotPrerenderList) { const urlList = []; globalContext._pageRoutes.map((pageRoute) => { const { pageId } = pageRoute; if (doNotPrerenderList.find((p) => p.pageId === pageId)) return; let urlOriginal; if (!('routeString' in pageRoute)) { // Abort since the page's route is a Route Function (0, utils_js_1.assert)(pageRoute.routeType === 'FUNCTION'); return; } else { const url = (0, resolveRouteString_js_1.getUrlFromRouteString)(pageRoute.routeString); if (!url) { // Abort since no URL can be deduced from a parameterized Route String return; } urlOriginal = url; } (0, utils_js_1.assert)(urlOriginal.startsWith('/')); urlList.push({ urlOriginal, pageId }); }); return urlList; } function getUrlList404(globalContext) { const urlList = []; const errorPageId = (0, error_page_js_1.getErrorPageId)(globalContext._pageFilesAll, globalContext._pageConfigs); if (errorPageId) { urlList.push({ // A URL is required for `viteDevServer.transformIndexHtml(url,html)` urlOriginal: '/404', pageId: errorPageId, }); } return urlList; } async function createPageContexts(urlList, prerenderContext, globalContext, concurrencyLimit, is404) { await Promise.all(urlList.map(({ urlOriginal, pageId }) => concurrencyLimit(async () => { // Already included in a onBeforePrerenderStart() hook if (prerenderContext.pageContexts.find((pageContext) => isSameUrl(pageContext.urlOriginal, urlOriginal))) { return; } const pageContext = await createPageContextPrerendering(urlOriginal, prerenderContext, globalContext, is404, pageId, null); prerenderContext.pageContexts.push(pageContext); }))); } async function createPageContextPrerendering(urlOriginal, prerenderContext, globalContext, is404, pageId, providedByHook) { const pageContextInit = { urlOriginal, ...prerenderContext._pageContextInit, }; const pageContext = await (0, createPageContextServerSide_js_1.createPageContextServerSide)(pageContextInit, globalContext, { isPrerendering: true, }); (0, utils_js_1.assert)(pageContext.isPrerendering === true); (0, utils_js_1.objectAssign)(pageContext, { _urlHandler: null, _httpRequestId: null, _urlRewrite: null, _noExtraDir: prerenderContext._noExtraDir, _prerenderContext: prerenderContext, _providedByHook: providedByHook, _urlOriginalModifiedByHook: null, is404, }); if (!is404) { const pageContextFromRoute = await (0, index_js_1.route)(pageContext); (0, utils_js_1.assert)((0, utils_js_1.hasProp)(pageContextFromRoute, 'pageId', 'null') || (0, utils_js_1.hasProp)(pageContextFromRoute, 'pageId', 'string')); // Help TS assertRouteMatch(pageContextFromRoute, pageContext); (0, utils_js_1.assert)(pageContextFromRoute.pageId); (0, utils_js_1.objectAssign)(pageContext, pageContextFromRoute); } else { (0, utils_js_1.assert)(pageId); (0, utils_js_1.objectAssign)(pageContext, { pageId, _debugRouteMatches: [], routeParams: {}, }); } (0, utils_js_2.augmentType)(pageContext, await (0, loadPageConfigsLazyServerSide_js_1.loadPageConfigsLazyServerSideAndExecHook)(pageContext)); let usesClientRouter; { const { pageId } = pageContext; (0, utils_js_1.assert)(pageId); (0, utils_js_1.assert)(globalContext._isPrerendering); if (globalContext._pageConfigs.length > 0) { const pageConfig = globalContext._pageConfigs.find((p) => p.pageId === pageId); (0, utils_js_1.assert)(pageConfig); usesClientRouter = (0, getConfigValueRuntime_js_1.getConfigValueRuntime)(pageConfig, 'clientRouting', 'boolean')?.value ?? false; } else { usesClientRouter = globalContext._usesClientRouter; } } (0, utils_js_1.objectAssign)(pageContext, { _usesClientRouter: usesClientRouter }); return pageContext; } function assertRouteMatch(pageContextFromRoute, pageContext) { if (pageContextFromRoute.pageId !== null) { (0, utils_js_1.assert)(pageContextFromRoute.pageId); return; } let hookName; let hookFilePath; if (pageContext._urlOriginalModifiedByHook) { hookName = pageContext._urlOriginalModifiedByHook.hookName; hookFilePath = pageContext._urlOriginalModifiedByHook.hookFilePath; } else if (pageContext._providedByHook) { hookName = pageContext._providedByHook.hookName; hookFilePath = pageContext._providedByHook.hookFilePath; } if (hookName) { (0, utils_js_1.assert)(hookFilePath); const { urlOriginal } = pageContext; (0, utils_js_1.assert)(urlOriginal); (0, utils_js_1.assertUsage)(false, `The ${hookName}() hook defined by ${hookFilePath} returns a URL ${picocolors_1.default.cyan(urlOriginal)} that ${noRouteMatch_js_1.noRouteMatch}. Make sure that the URLs returned by ${hookName}() always match the route of a page.`); } else { // `prerenderHookFile` is `null` when the URL was deduced by the Filesystem Routing of `.page.js` files. The `onBeforeRoute()` can override Filesystem Routing; it is therefore expected that the deduced URL may not match any page. (0, utils_js_1.assert)(pageContextFromRoute._routingProvidedByOnBeforeRouteHook); // Abort since the URL doesn't correspond to any page return; } } async function callOnPrerenderStartHook(prerenderContext, globalContext, concurrencyLimit) { let onPrerenderStartHook; // V1 design if (globalContext._pageConfigs.length > 0) { const hookName = 'onPrerenderStart'; const hook = (0, getHook_js_1.getHookFromPageConfigGlobal)(globalContext._pageConfigGlobal, hookName); if (hook) { (0, utils_js_1.assert)(hook.hookName === 'onPrerenderStart'); onPrerenderStartHook = { ...hook, // Make TypeScript happy hookName, }; } } // Old design // TODO/v1-release: remove if (globalContext._pageConfigs.length === 0) { const hookTimeout = (0, getHook_js_1.getHookTimeoutDefault)('onBeforePrerender'); const pageFilesWithOnBeforePrerenderHook = globalContext._pageFilesAll.filter((p) => { assertExportNames(p); if (!p.exportNames?.includes('onBeforePrerender')) return false; (0, utils_js_1.assertUsage)(p.fileType !== '.page.client', `${p.filePath} (which is a \`.page.client.js\` file) has \`export { onBeforePrerender }\` but it is only allowed in \`.page.server.js\` or \`.page.js\` files`); (0, utils_js_1.assertUsage)(p.isDefaultPageFile, `${p.filePath} has \`export { onBeforePrerender }\` but it is only allowed in \`_default.page.\` files`); return true; }); if (pageFilesWithOnBeforePrerenderHook.length === 0) { return; } (0, utils_js_1.assertUsage)(pageFilesWithOnBeforePrerenderHook.length === 1, 'There can be only one `onBeforePrerender()` hook. If you need to be able to define several, open a new GitHub issue.'); await Promise.all(pageFilesWithOnBeforePrerenderHook.map((p) => p.loadFile?.())); const hooks = pageFilesWithOnBeforePrerenderHook.map((p) => { (0, utils_js_1.assert)(p.fileExports); const { onBeforePrerender } = p.fileExports; (0, utils_js_1.assert)(onBeforePrerender); const hookFilePath = p.filePath; return { hookFilePath, onBeforePrerender }; }); (0, utils_js_1.assert)(hooks.length === 1); const hook = hooks[0]; onPrerenderStartHook = { hookFn: hook.onBeforePrerender, hookFilePath: hook.hookFilePath, hookName: 'onBeforePrerender', hookTimeout, }; } if (!onPrerenderStartHook) { return; } const msgPrefix = `The ${onPrerenderStartHook.hookName}() hook defined by ${onPrerenderStartHook.hookFilePath}`; const { hookFn, hookFilePath, hookName } = onPrerenderStartHook; (0, utils_js_1.assertUsage)((0, utils_js_1.isCallable)(hookFn), `${msgPrefix} should be a function.`); prerenderContext.pageContexts.forEach((pageContext) => { Object.defineProperty(pageContext, 'url', { // TODO/v1-release: remove warning get() { (0, utils_js_1.assertWarning)(false, msgPrefix + ' uses pageContext.url but it should use pageContext.urlOriginal instead, see https://vike.dev/migration/0.4.23', { showStackTrace: true, onlyOnce: true }); return pageContext.urlOriginal; }, enumerable: false, configurable: true, }); (0, utils_js_1.assert)((0, utils_js_1.isPropertyGetter)(pageContext, 'url')); (0, utils_js_1.assert)(pageContext.urlOriginal); pageContext._urlOriginalBeforeHook = pageContext.urlOriginal; }); prerenderContext.pageContexts.forEach((pageContext) => { // Preserve URL computed properties when the user is copying pageContext is his onPrerenderStart() hook, e.g. /examples/i18n/ // https://vike.dev/i18n#pre-rendering (0, utils_js_1.preservePropertyGetters)(pageContext); }); const prerenderContextPublic = preparePrerenderContextForPublicUsage(prerenderContext); let result = await (0, execHook_js_1.execHookDirectWithoutPageContext)(() => hookFn(prerenderContextPublic), onPrerenderStartHook); // Before applying result prerenderContext.pageContexts.forEach((pageContext) => { ; pageContext._restorePropertyGetters?.(); }); if (result === null || result === undefined) { return; } const errPrefix = `The ${hookName}() hook exported by ${hookFilePath}`; const rightUsage = `${errPrefix} should return ${picocolors_1.default.cyan('null')}, ${picocolors_1.default.cyan('undefined')}, or ${picocolors_1.default.cyan('{ prerenderContext: { pageContexts } }')}`; // TODO/v1-release: remove if ((0, utils_js_1.hasProp)(result, 'globalContext')) { (0, utils_js_1.assertUsage)((0, utils_js_1.isObjectWithKeys)(result, ['globalContext']) && (0, utils_js_1.hasProp)(result, 'globalContext', 'object') && (0, utils_js_1.hasProp)(result.globalContext, 'prerenderPageContexts', 'array'), rightUsage); (0, utils_js_1.assertWarning)(false, `${errPrefix} returns ${picocolors_1.default.cyan('{ globalContext: { prerenderPageContexts } }')} but the return value has been renamed to ${picocolors_1.default.cyan('{ prerenderContext: { pageContexts } }')}, see ${docLink}`, { onlyOnce: true }); result = { prerenderContext: { pageContexts: result.globalContext.prerenderPageContexts, }, }; } (0, utils_js_1.assertUsage)((0, utils_js_1.isObjectWithKeys)(result, ['prerenderContext']) && (0, utils_js_1.hasProp)(result, 'prerenderContext', 'object') && (0, utils_js_1.hasProp)(result.prerenderContext, 'pageContexts', 'array'), rightUsage); prerenderContext.pageContexts = result.prerenderContext.pageContexts; prerenderContext.pageContexts.forEach((pageContext) => { // TODO/v1-release: remove if (pageContext.url && !(0, utils_js_1.isPropertyGetter)(pageContext, 'url')) { (0, utils_js_1.assertWarning)(false, msgPrefix + ' provided pageContext.url but it should provide pageContext.urlOriginal instead, see https://vike.dev/migration/0.4.23', { onlyOnce: true }); pageContext.urlOriginal = pageContext.url; } delete pageContext.url; }); // After applying result prerenderContext.pageContexts.forEach((pageContext) => { ; pageContext._restorePropertyGetters?.(); }); // Assert URL modified by user await Promise.all(prerenderContext.pageContexts.map((pageContext) => concurrencyLimit(async () => { if (pageContext.urlOriginal !== pageContext._urlOriginalBeforeHook && !pageContext.is404) { pageContext._urlOriginalModifiedByHook = { hookFilePath, hookName, }; const pageContextFromRoute = await (0, index_js_1.route)(pageContext, // Avoid calling onBeforeRoute() twice, otherwise onBeforeRoute() will wrongfully believe URL doesn't have locale after onBeforeRoute() already removed the local from the URL when called the first time. true); assertRouteMatch(pageContextFromRoute, pageContext); } }))); } async function prerenderPages(prerenderContext, concurrencyLimit, onComplete) { await Promise.all(prerenderContext.pageContexts.map((pageContextBeforeRender) => concurrencyLimit(async () => { let res; try { res = await (0, renderPageAlreadyRouted_js_1.prerenderPage)(pageContextBeforeRender); } catch (err) { assertIsNotAbort(err, picocolors_1.default.cyan(pageContextBeforeRender.urlOriginal)); throw err; } const { documentHtml, pageContext } = res; const pageContextSerialized = pageContext.is404 ? null : res.pageContextSerialized; await onComplete({ pageContext, htmlString: documentHtml, pageContextSerialized, }); }))); } function warnContradictoryNoPrerenderList(prerenderedPageContexts, doNotPrerenderList) { Object.entries(prerenderedPageContexts).forEach(([pageId, pageContext]) => { const doNotPrerenderListEntry = doNotPrerenderList.find((p) => p.pageId === pageId); const { urlOriginal, _providedByHook: providedByHook } = pageContext; { const isContradictory = !!doNotPrerenderListEntry && providedByHook; if (!isContradictory) return; } (0, utils_js_1.assertWarning)(false, `The ${providedByHook.hookName}() hook defined by ${providedByHook.hookFilePath} returns the URL ${picocolors_1.default.cyan(urlOriginal)} matching the route of the page ${picocolors_1.default.cyan(pageId)} which isn't configured to be pre-rendered. This is contradictory: either enable pre-rendering for ${picocolors_1.default.cyan(pageId)} or remove the URL ${picocolors_1.default.cyan(urlOriginal)} from the list of URLs to be pre-rendered.`, { onlyOnce: true }); }); } async function warnMissingPages(prerenderedPageContexts, globalContext, doNotPrerenderList, partial) { const isV1 = globalContext._pageConfigs.length > 0; const hookName = isV1 ? 'onBeforePrerenderStart' : 'prerender'; /* TODO/after-v1-design-release: document setting `prerender: false` as an alternative to using prerender.partial (both in the warnings and the docs) const optOutName = isV1 ? 'prerender' : 'doNotPrerender' const msgAddendum = `Explicitly opt-out by setting the config ${optOutName} to ${isV1 ? 'false' : 'true'} or use the option prerender.partial` */ globalContext._allPageIds .filter((pageId) => !prerenderedPageContexts[pageId]) .filter((pageId) => !doNotPrerenderList.find((p) => p.pageId === pageId)) .filter((pageId) => !(0, error_page_js_1.isErrorPage)(pageId, globalContext._pageConfigs)) .forEach((pageId) => { const pageAt = isV1 ? pageId : `\`${pageId}.page.*\``; (0, utils_js_1.assertWarning)(partial, `Cannot pre-render page ${pageAt} because it has a non-static route, while there isn't any ${hookName}() hook returning an URL matching the page's route. You must use a ${hookName}() hook (https://vike.dev/${hookName}) for providing the list of URLs to be pre-rendered for that page. If you want to skip pre-rendering that page, you can remove this warning by setting +prerender to false at ${pageAt} (https://vike.dev/prerender#toggle) or by setting +prerender.partial to true (https://vike.dev/prerender#partial).`, { onlyOnce: true }); }); } async function writeFiles({ pageContext, htmlString, pageContextSerialized }, viteConfig, onPagePrerender, prerenderContext, logLevel) { const writeJobs = [write(pageContext, 'HTML', htmlString, viteConfig, onPagePrerender, prerenderContext, logLevel)]; if (pageContextSerialized !== null) { writeJobs.push(write(pageContext, 'JSON', pageContextSerialized, viteConfig, onPagePrerender, prerenderContext, logLevel)); } await Promise.all(writeJobs); } async function write(pageContext, fileType, fileContent, viteConfig, onPagePrerender, prerenderContext, logLevel) { const { urlOriginal } = pageContext; (0, utils_js_1.assert)(urlOriginal.startsWith('/')); const { outDirClient } = (0, getOutDirs_js_1.getOutDirs)(viteConfig); const { root } = viteConfig; let fileUrl; if (fileType === 'HTML') { const doNotCreateExtraDirectory = prerenderContext._noExtraDir ?? pageContext.is404; fileUrl = (0, utils_js_1.urlToFile)(urlOriginal, '.html', doNotCreateExtraDirectory); } else { (0, utils_js_1.assert)(fileType === 'JSON'); fileUrl = (0, getPageContextRequestUrl_js_1.getPageContextRequestUrl)(urlOriginal); } (0, utils_js_1.assertPosixPath)(fileUrl); (0, utils_js_1.assert)(fileUrl.startsWith('/')); const filePathRelative = fileUrl.slice(1); (0, utils_js_1.assert)(!filePathRelative.startsWith('/'), // https://github.com/vikejs/vike/issues/1929 { urlOriginal, fileUrl }); (0, utils_js_1.assertPosixPath)(outDirClient); (0, utils_js_1.assertPosixPath)(filePathRelative); const filePath = node_path_1.default.posix.join(outDirClient, filePathRelative); (0, utils_js_1.objectAssign)(pageContext, { _prerenderResult: { filePath, fileContent, }, }); prerenderContext.output.push({ filePath, fileType, fileContent, pageContext, }); if (onPagePrerender) { await onPagePrerender(pageContext); } else { const { promises } = await Promise.resolve().then(() => __importStar(require('node:fs'))); const { writeFile, mkdir } = promises; await mkdir(node_path_1.default.posix.dirname(filePath), { recursive: true }); await writeFile(filePath, fileContent); if (logLevel === 'info') { (0, utils_js_1.assertPosixPath)(root); (0, utils_js_1.assertPosixPath)(outDirClient); let outDirClientRelative = node_path_1.default.posix.relative(root, outDirClient); if (!outDirClientRelative.endsWith('/')) { outDirClientRelative = outDirClientRelative + '/'; } console.log(`${picocolors_1.default.dim(outDirClientRelative)}${picocolors_1.default.blue(filePathRelative)}`); } } } function normalizeOnPrerenderHookResult(prerenderResult, prerenderHookFile, hookName) { if ((0, utils_js_1.isArray)(prerenderResult)) { return prerenderResult.map(normalize); } else { return [normalize(prerenderResult)]; } function normalize(prerenderElement) { if (typeof prerenderElement === 'string') { prerenderElement = { url: prerenderElement, pageContext: null }; } const errMsg1 = `The ${hookName}() hook defined by ${prerenderHookFile} returned`; const errMsg2 = `${errMsg1} an invalid value`; const errHint = `Make sure your ${hookName}() hook returns an object ${picocolors_1.default.cyan('{ url, pageContext }')} or an array of such objects.`; (0, utils_js_1.assertUsage)((0, utils_js_1.isPlainObject)(prerenderElement), `${errMsg2}. ${errHint}`); (0, utils_js_1.assertUsage)((0, utils_js_1.hasProp)(prerenderElement, 'url'), `${errMsg2}: ${picocolors_1.default.cyan('url')} is missing. ${errHint}`); (0, utils_js_1.assertUsage)((0, utils_js_1.hasProp)(prerenderElement, 'url', 'string'), `${errMsg2}: ${picocolors_1.default.cyan('url')} should be a string (but ${picocolors_1.default.cyan(`typeof url === "${typeof prerenderElement.url}"`)}).`); (0, utils_js_1.assertUsage)(prerenderElement.url.startsWith('/'), `${errMsg1} a URL with an invalid value ${picocolors_1.default.cyan(prerenderElement.url)} which doesn't start with ${picocolors_1.default.cyan('/')}. Make sure each URL starts with ${picocolors_1.default.cyan('/')}.`); Object.keys(prerenderElement).forEach((key) => { (0, utils_js_1.assertUsage)(key === 'url' || key === 'pageContext', `${errMsg2}: unexpected object key ${picocolors_1.default.cyan(key)}. ${errHint}`); }); if (!(0, utils_js_1.hasProp)(prerenderElement, 'pageContext')) { prerenderElement.pageContext = null; } else if (!(0, utils_js_1.hasProp)(prerenderElement, 'pageContext', 'null')) { (0, utils_js_1.assertUsage)((0, utils_js_1.hasProp)(prerenderElement, 'pageContext', 'object'), `${errMsg1} an invalid ${picocolors_1.default.cyan('pageContext')} value: make sure ${picocolors_1.default.cyan('pageContext')} is an object.`); (0, utils_js_1.assertUsage)((0, utils_js_1.isPlainObject)(prerenderElement.pageContext), `${errMsg1} an invalid ${picocolors_1.default.cyan('pageContext')} object: make sure ${picocolors_1.default.cyan('pageContext')} is a plain JavaScript object.`); } (0, utils_js_1.assert)((0, utils_js_1.hasProp)(prerenderElement, 'pageContext', 'object') || (0, utils_js_1.hasProp)(prerenderElement, 'pageContext', 'null')); return prerenderElement; } } // TODO/v1-release: remove function checkOutdatedOptions(options) { (0, utils_js_1.assertUsage)(options.root === undefined, 'Option `prerender({ root })` deprecated: set `prerender({ viteConfig: { root }})` instead.', { showStackTrace: true }); (0, utils_js_1.assertUsage)(options.configFile === undefined, 'Option `prerender({ configFile })` deprecated: set `prerender({ viteConfig: { configFile }})` instead.', { showStackTrace: true }); ['noExtraDir', 'partial', 'parallel'].forEach((prop) => { (0, utils_js_1.assertUsage)(options[prop] === undefined, `[prerender()] Option ${picocolors_1.default.cyan(prop)} is deprecated. Define ${picocolors_1.default.cyan(prop)} in vite.config.js instead. See https://vike.dev/prerender`, { showStackTrace: true }); }); ['base', 'outDir'].forEach((prop) => { (0, utils_js_1.assertWarning)(options[prop] === undefined, `[prerender()] Option ${picocolors_1.default.cyan(prop)} is outdated and has no effect (vike now automatically determines ${picocolors_1.default.cyan(prop)})`, { showStackTrace: true, onlyOnce: true, }); }); } async function disableReactStreaming() { let mod; try { mod = await Promise.resolve().then(() => __importStar(require('react-streaming/server'))); } catch { return; } const { disable } = mod; disable(); } function isSameUrl(url1, url2) { return normalizeUrl(url1) === normalizeUrl(url2); } function normalizeUrl(url) { return '/' + url.split('/').filter(Boolean).join('/'); } function assertIsNotAbort(err, urlOriginal) { if (!(0, abort_js_1.isAbortError)(err)) return; const pageContextAbort = err._pageContextAbort; const hookLoc = (0, execHook_js_1.isUserHookError)(err); (0, utils_js_1.assert)(hookLoc); const thrownBy = ` by ${picocolors_1.default.cyan(`${hookLoc.hookName}()`)} hook defined at ${hookLoc.hookFilePath}`; const abortCaller = pageContextAbort._abortCaller; (0, utils_js_1.assert)(abortCaller); const abortCall = pageContextAbort._abortCall; (0, utils_js_1.assert)(abortCall); (0, utils_js_1.assertUsage)(false, `${picocolors_1.default.cyan(abortCall)} thrown${thrownBy} while pre-rendering ${urlOriginal} but ${picocolors_1.default.cyan(abortCaller)} isn't supported for pre-rendered pages`); } function preparePrerenderContextForPublicUsage(prerenderContext) { // TODO/v1-release: remove if (!('prerenderPageContexts' in prerenderContext)) { Object.defineProperty(prerenderContext, 'prerenderPageContexts', { get() { (0, utils_js_1.assertWarning)(false, `prerenderPageContexts has been renamed pageContexts, see ${picocolors_1.default.underline(docLink)}`, { showStackTrace: true, onlyOnce: true, }); return prerenderContext.pageContexts; }, }); } // Required because of https://vike.dev/i18n#pre-rendering // - Thus, we have to let users access the original pageContext object => we cannot use ES proxies and we cannot use preparePageContextForPublicUsage() prerenderContext.pageContexts.forEach((pageContext) => { (0, utils_js_1.changeEnumerable)(pageContext, '_isOriginalObject', true); }); const prerenderContextPublic = (0, getProxyForPublicUsage_js_1.getProxyForPublicUsage)(prerenderContext, 'prerenderContext'); return prerenderContextPublic; } async function prerenderRedirects(globalContext, onComplete, showWarningUponDynamicRedirects) { const redirects = globalContext.config.redirects ?? []; const redirectsStatic = (0, resolveRedirects_js_1.getStaticRedirectsForPrerender)(redirects, showWarningUponDynamicRedirects); for (const [urlSource, urlTarget] of Object.entries(redirectsStatic)) { const urlOriginal = urlSource; const htmlString = getRedirectHtml(urlTarget); await onComplete({ pageContext: { urlOriginal, pageId: null, is404: false, isRedirect: true }, htmlString, pageContextSerialized: null, }); } } function getRedirectHtml(urlTarget) { const urlTargetSafe = (0, utils_js_1.escapeHtml)(urlTarget); // To test it: /test/playground => http://localhost:3000/download const htmlString = `<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="refresh" content="0;url=${urlTargetSafe}"> <title>Redirect ${urlTargetSafe}</title> <style>body{opacity:0}</style> <noscript> <style>body{opacity:1}</style> </noscript> </head> <body style="min-height: 100vh; margin: 0; font-family: sans-serif; display: flex; justify-content: center; align-items: center; transition: opacity 0.3s;"> <script>setTimeout(()=>{document.body.style.opacity=1},2000)</script> <div> <h1>Redirect <a href="${urlTargetSafe}"><code style="background-color: #eaeaea; padding: 3px 5px; border-radius: 4px;">${urlTargetSafe}</code></a></h1> <p>If you aren't redirected, click the link above.</p> <!-- This HTML was generated by Vike. --> </div> </body> </html>`; return htmlString; }