vike
Version:
The Framework *You* Control - Next.js & Nuxt alternative for unprecedented flexibility and dependability.
779 lines (778 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.runPrerenderFromAPI = runPrerenderFromAPI;
exports.runPrerenderFromCLIPrerenderCommand = runPrerenderFromCLIPrerenderCommand;
exports.runPrerenderFromAutoRun = runPrerenderFromAutoRun;
exports.runPrerender_forceExit = runPrerender_forceExit;
const path_1 = __importDefault(require("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 os_1 = require("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 loadUserFilesServerSide_js_1 = require("../runtime/renderPage/loadUserFilesServerSide.js");
const getHook_js_1 = require("../../shared/hooks/getHook.js");
const noRouteMatch_js_1 = require("../../shared/route/noRouteMatch.js");
const getVikeConfig_js_1 = require("../plugin/plugins/importUserCode/v1-design/getVikeConfig.js");
const logErrorHint_js_1 = require("../runtime/renderPage/logErrorHint.js");
const executeHook_js_1 = require("../../shared/hooks/executeHook.js");
const prepareViteApiCall_js_1 = require("../api/prepareViteApiCall.js");
const context_js_1 = require("./context.js");
const resolvePrerenderConfig_js_1 = require("./resolvePrerenderConfig.js");
const getOutDirs_js_1 = require("../plugin/shared/getOutDirs.js");
const context_js_2 = require("../cli/context.js");
const isViteCliCall_js_1 = require("../plugin/shared/isViteCliCall.js");
const commonConfig_js_1 = require("../plugin/plugins/commonConfig.js");
const node_fs_1 = __importDefault(require("node:fs"));
async function runPrerenderFromAPI(options = {}) {
return await runPrerender(options, 'prerender()');
// - We purposely propagate the error to the user land, so that the error interrupts the user land. It's also, I guess, a nice-to-have that the user has control over the error.
// - We don't use logErrorHint() because we don't have control over what happens with the error. For example, if the user land purposely swallows the error then the hint shouldn't be logged. Also, it's best if the hint is shown to the user *after* the error, but we cannot do/guarentee that.
}
async function runPrerenderFromCLIPrerenderCommand() {
try {
const { viteConfigFromUserEnhanced } = await (0, prepareViteApiCall_js_1.prepareViteApiCall)({}, 'prerender');
await runPrerender({ viteConfig: viteConfigFromUserEnhanced }, '$ vike prerender');
}
catch (err) {
console.error(err);
// Error may come from user-land; we need to use logErrorHint()
(0, logErrorHint_js_1.logErrorHint)(err);
process.exit(1);
}
runPrerender_forceExit();
(0, utils_js_1.assert)(false);
}
async function runPrerenderFromAutoRun(viteConfig) {
try {
await runPrerender({ viteConfig });
}
catch (err) {
// Avoid Rollup prefixing the error with [vike:build:pluginAutoFullBuild], see for example https://github.com/vikejs/vike/issues/472#issuecomment-1276274203
console.error(err);
(0, logErrorHint_js_1.logErrorHint)(err);
process.exit(1);
}
const forceExit = (0, context_js_2.isVikeCli)() || (0, isViteCliCall_js_1.isViteCliCall)();
return { forceExit };
}
async function runPrerender(options = {}, standaloneTrigger) {
(0, context_js_1.setContextIsPrerendering)();
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, getVikeConfig_js_1.getVikeConfig)(viteConfig);
const vike = (0, commonConfig_js_1.getVikeConfigInternal)(viteConfig);
const { outDirClient, outDirServer } = (0, getOutDirs_js_1.getOutDirs)(viteConfig);
const { root } = viteConfig;
const prerenderConfigGlobal = (0, resolvePrerenderConfig_js_1.resolvePrerenderConfigGlobal)(vikeConfig);
const { partial, noExtraDir, parallel, defaultLocalValue, isPrerenderingEnabled } = prerenderConfigGlobal;
if (!isPrerenderingEnabled) {
(0, utils_js_1.assert)(standaloneTrigger);
/* TODO/soon: use this again a little while after https://github.com/magne4000/vite-plugin-vercel/pull/156 is merged.
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, os_1.cpus)().length : parallel);
await (0, globalContext_js_1.initGlobalContext_runPrerender)();
const { globalContext, globalContext_public } = 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, globalContext_public, concurrencyLimit, doNotPrerenderList);
// Create `pageContext` for each page with a static route
const urlList = getUrlListFromPagesWithStaticRoute(globalContext, doNotPrerenderList);
await createPageContexts(urlList, prerenderContext, globalContext, globalContext_public, concurrencyLimit, false);
// Create `pageContext` for 404 page
const urlList404 = getUrlList404(globalContext);
await createPageContexts(urlList404, prerenderContext, globalContext, globalContext_public, 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)(pageId);
prerenderContext._prerenderedPageContexts[pageId] = htmlFile.pageContext;
await writeFiles(htmlFile, root, outDirClient, options.onPagePrerender, prerenderContext.output, logLevel);
};
await prerenderPages(prerenderContext, concurrencyLimit, onComplete);
warnContradictoryNoPrerenderList(prerenderContext._prerenderedPageContexts, doNotPrerenderList);
if (logLevel === 'info') {
console.log(`${picocolors_1.default.green(`✓`)} ${prerenderedCount} HTML documents pre-rendered.`);
}
await warnMissingPages(prerenderContext._prerenderedPageContexts, globalContext, doNotPrerenderList, partial);
const prerenderContextPublic = makePublic(prerenderContext);
(0, utils_js_1.objectAssign)(vike.prerenderContext, prerenderContextPublic);
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, globalContext_public, 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(({ hookFn, hookName, hookFilePath, pageId, hookTimeout }) => concurrencyLimit(async () => {
if (doNotPrerenderList.find((p) => p.pageId === pageId))
return;
const prerenderResult = await (0, executeHook_js_1.executeHook)(() => hookFn(), { hookName, hookFilePath, hookTimeout }, null);
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, globalContext_public, 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, globalContext_public, 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, globalContext_public, is404, pageId, null);
prerenderContext.pageContexts.push(pageContext);
})));
}
async function createPageContextPrerendering(urlOriginal, prerenderContext, globalContext, globalContext_public, is404, pageId, providedByHook) {
const pageContextInit = {
urlOriginal,
...prerenderContext._pageContextInit
};
const pageContext = await (0, createPageContextServerSide_js_1.createPageContextServerSide)(pageContextInit, globalContext, globalContext_public, {
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_1.objectAssign)(pageContext, await (0, loadUserFilesServerSide_js_1.loadUserFilesServerSide)(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 Filesytem 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 \`_defaut.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;
});
const docLink = 'https://vike.dev/i18n#pre-rendering';
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);
});
let result = await (0, executeHook_js_1.executeHook)(() => {
const prerenderContextPublic = makePublic(prerenderContext);
// TODO/v1-release: remove warning
Object.defineProperty(prerenderContextPublic, 'prerenderPageContexts', {
get() {
(0, utils_js_1.assertWarning)(false, `prerenderPageContexts has been renamed pageContexts, see ${docLink}`, {
showStackTrace: true,
onlyOnce: true
});
return prerenderContext.pageContexts;
}
});
return hookFn(prerenderContextPublic);
}, onPrerenderStartHook, null);
// 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,
doNotCreateExtraDirectory: prerenderContext._noExtraDir ?? pageContext.is404
});
})));
}
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 no ${hookName}() hook returned any URL matching the page's route. You need to use a ${hookName}() hook (https://vike.dev/${hookName}) providing a list of URLs for ${pageAt} that should be pre-rendered. If you don't want to pre-render ${pageAt} then use the option prerender.partial (https://vike.dev/prerender#partial) to suppress this warning.`, { onlyOnce: true });
});
}
async function writeFiles({ pageContext, htmlString, pageContextSerialized, doNotCreateExtraDirectory }, root, outDirClient, onPagePrerender, output, logLevel) {
const { urlOriginal } = pageContext;
(0, utils_js_1.assert)(urlOriginal.startsWith('/'));
const writeJobs = [
write(urlOriginal, pageContext, 'HTML', htmlString, root, outDirClient, doNotCreateExtraDirectory, onPagePrerender, output, logLevel)
];
if (pageContextSerialized !== null) {
writeJobs.push(write(urlOriginal, pageContext, 'JSON', pageContextSerialized, root, outDirClient, doNotCreateExtraDirectory, onPagePrerender, output, logLevel));
}
await Promise.all(writeJobs);
}
async function write(urlOriginal, pageContext, fileType, fileContent, root, outDirClient, doNotCreateExtraDirectory, onPagePrerender, output, logLevel) {
let fileUrl;
if (fileType === 'HTML') {
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('/'),
// Let's remove this debug info after we add a assertUsage() avoiding https://github.com/vikejs/vike/issues/1929
{ urlOriginal, fileUrl });
(0, utils_js_1.assertPosixPath)(outDirClient);
(0, utils_js_1.assertPosixPath)(filePathRelative);
const filePath = path_1.default.posix.join(outDirClient, filePathRelative);
(0, utils_js_1.objectAssign)(pageContext, {
_prerenderResult: {
filePath,
fileContent
}
});
output.push({
filePath,
fileType,
fileContent,
pageContext
});
if (onPagePrerender) {
await onPagePrerender(pageContext);
}
else {
const { promises } = await Promise.resolve().then(() => __importStar(require('fs')));
const { writeFile, mkdir } = promises;
await mkdir(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 = 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 runPrerender_forceExit() {
// Force exit; known situations where pre-rendering is hanging:
// - https://github.com/vikejs/vike/discussions/774#discussioncomment-5584551
// - https://github.com/vikejs/vike/issues/807#issuecomment-1519010902
process.exit(0);
/* I guess there is no need to tell the user about it? Let's see if a user complains.
* I don't known whether there is a way to call process.exit(0) only if needed, thus I'm not sure if there is a way to conditionally show a assertInfo().
assertInfo(false, "Pre-rendering was forced exit. (Didn't gracefully exit because the event queue isn't empty. This is usally fine, see ...", { onlyOnce: false })
*/
}
function assertIsNotAbort(err, urlOriginal) {
if (!(0, abort_js_1.isAbortError)(err))
return;
const pageContextAbort = err._pageContextAbort;
const hookLoc = (0, executeHook_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 makePublic(prerenderContext) {
const prerenderContextPublic = (0, utils_js_1.getPublicProxy)(prerenderContext, 'prerenderContext', [
'output', // vite-plugin-vercel
'pageContexts' // https://vike.dev/i18n#pre-rendering
]);
return prerenderContextPublic;
}