vike
Version:
The Framework *You* Control - Next.js & Nuxt alternative for unprecedented flexibility and dependability.
783 lines (782 loc) • 42.8 kB
JavaScript
;
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;
}