vike
Version:
The Framework *You* Control - Next.js & Nuxt alternative for unprecedented flexibility and dependability.
216 lines (215 loc) • 9.35 kB
JavaScript
export { redirect };
export { render };
export { RenderErrorPage };
export { isAbortError };
export { isAbortPageContext };
export { logAbort };
export { getPageContextAddendumAbort };
export { addNewPageContextAborted };
export { AbortRender };
import { isUserHookError } from '../hooks/execHook.js';
import { assert, assertInfo, assertUsage, assertWarning } from '../../utils/assert.js';
import { assertUsageUrlPathAbsolute, assertUsageUrlRedirectTarget } from '../../utils/parseUrl.js';
import { checkType } from '../../utils/checkType.js';
import { hasProp } from '../../utils/hasProp.js';
import { isBrowser } from '../../utils/isBrowser.js';
import { joinEnglish } from '../../utils/joinEnglish.js';
import { objectAssign } from '../../utils/objectAssign.js';
import { truncateString } from '../../utils/truncateString.js';
import { unique } from '../../utils/unique.js';
import pc from '@brillout/picocolors';
/**
* Abort the rendering of the current page, and redirect the user to another URL instead.
*
* https://vike.dev/redirect
*
* @param url The URL to redirect to.
* @param statusCode By default the temporary redirection status code (`302`) is sent. For permanent redirections (`301`), use `+redirects` (https://vike.dev/redirects) or set this `statusCode` argument to `301`.
*/
function redirect(url, statusCode) {
const abortCaller = 'throw redirect()';
assertUsageUrlRedirectTarget(url, getErrPrefix(abortCaller));
const args = [JSON.stringify(url)];
if (!statusCode) {
statusCode = 302;
}
else {
if (
// Tree-shaking to save client-side KBs
!globalThis.__VIKE__IS_CLIENT ||
globalThis.__VIKE__IS_DEV ||
globalThis.__VIKE__IS_DEBUG) {
assertStatusCode(statusCode, [301, 302], 'redirect');
}
args.push(String(statusCode));
}
const pageContextAbort = {};
objectAssign(pageContextAbort, {
_abortCaller: abortCaller,
_abortCall: `redirect(${args.join(', ')})`,
_urlRedirect: {
url,
statusCode,
},
});
return AbortRender(pageContextAbort);
}
function render(urlOrStatusCode, abortReason) {
const args = [typeof urlOrStatusCode === 'number' ? String(urlOrStatusCode) : JSON.stringify(urlOrStatusCode)];
if (abortReason !== undefined)
args.push(truncateString(JSON.stringify(abortReason), 30));
const abortCaller = 'throw render()';
const abortCall = `render(${args.join(', ')})`;
return render_(urlOrStatusCode, abortReason, abortCall, abortCaller);
}
function render_(urlOrStatusCode, abortReason, abortCall, abortCaller, pageContextAddendum) {
const pageContextAbort = {
abortReason,
_abortCaller: abortCaller,
_abortCall: abortCall,
};
if (pageContextAddendum) {
assert(pageContextAddendum._isLegacyRenderErrorPage === true);
objectAssign(pageContextAbort, pageContextAddendum);
}
if (typeof urlOrStatusCode === 'string') {
const url = urlOrStatusCode;
assertUsageUrlPathAbsolute(url, getErrPrefix(abortCaller));
objectAssign(pageContextAbort, {
_urlRewrite: url,
});
return AbortRender(pageContextAbort);
}
else {
if (
// Tree-shaking to save client-side KBs
!globalThis.__VIKE__IS_CLIENT ||
globalThis.__VIKE__IS_DEV ||
globalThis.__VIKE__IS_DEBUG) {
assertStatusCode(urlOrStatusCode, [401, 403, 404, 410, 429, 500, 503], 'render');
}
const abortStatusCode = urlOrStatusCode;
objectAssign(pageContextAbort, {
abortStatusCode,
is404: abortStatusCode === 404,
});
return AbortRender(pageContextAbort);
}
}
function AbortRender(pageContextAbort) {
const err = new Error('AbortRender');
objectAssign(err, { _pageContextAbort: pageContextAbort, [stamp]: true });
checkType(err);
return err;
}
// TO-DO/next-major-release: remove
/**
* @deprecated Use `throw render()` or `throw redirect()` instead, see https://vike.dev/render'
*/
function RenderErrorPage({ pageContext = {} } = {}) {
assertWarning(false, `${pc.cyan('throw RenderErrorPage()')} is deprecated and will be removed in the next major release. Use ${pc.cyan('throw render()')} or ${pc.cyan('throw redirect()')} instead, see https://vike.dev/render`, { onlyOnce: false });
let abortStatusCode = 404;
let abortReason = 'Page Not Found';
if (pageContext.is404 === false || pageContext.pageProps?.is404 === false) {
abortStatusCode = 500;
abortReason = 'Something went wrong';
}
objectAssign(pageContext, { _isLegacyRenderErrorPage: true });
return render_(abortStatusCode, abortReason, 'RenderErrorPage()', 'throw RenderErrorPage()', pageContext);
}
const stamp = '_isAbortError';
function isAbortError(thing) {
return typeof thing === 'object' && thing !== null && stamp in thing;
}
function isAbortPageContext(pageContext) {
if (!(pageContext._urlRewrite || pageContext._urlRedirect || pageContext.abortStatusCode)) {
return false;
}
assert(hasProp(pageContext, '_abortCall', 'string'));
/* Isn't needed and is missing on the client-side
assert(hasProp(pageContext, '_abortCaller', 'string'))
*/
checkType(pageContext);
return true;
}
function logAbort(err, isProduction, pageContext) {
if (isProduction)
return;
const urlCurrent = pageContext._urlRewrite ?? pageContext.urlOriginal;
assert(urlCurrent);
const abortCall = err._pageContextAbort._abortCall;
assert(abortCall);
const hookLoc = isUserHookError(err);
let thrownBy = '';
if (hookLoc) {
thrownBy = ` by ${pc.cyan(`${hookLoc.hookName}()`)} hook defined at ${hookLoc.hookFilePath}`;
}
else {
// hookLoc is missing when serializing abort errors from server to client
}
assertInfo(false, `${pc.cyan(abortCall)} thrown${thrownBy} while rendering ${pc.cyan(urlCurrent)}`, {
onlyOnce: false,
});
}
function assertStatusCode(statusCode, expected, caller) {
assert(!globalThis.__VIKE__IS_CLIENT || globalThis.__VIKE__IS_DEV || globalThis.__VIKE__IS_DEBUG); // assert tree-shaking
// double check vike:pluginReplaceConstantsGlobalThis
if (globalThis.__VIKE__IS_CLIENT) {
assert(isBrowser());
assert(typeof globalThis.__VIKE__IS_DEV === 'boolean');
assert(typeof globalThis.__VIKE__IS_CLIENT === 'boolean');
assert(import.meta.env.SSR === false);
assert(import.meta.env.DEV === globalThis.__VIKE__IS_DEV);
}
else {
assert(!isBrowser());
if (import.meta.env) {
assert(typeof globalThis.__VIKE__IS_DEV === 'boolean');
assert(typeof globalThis.__VIKE__IS_CLIENT === 'boolean');
assert(import.meta.env.SSR === true);
assert(import.meta.env.DEV === globalThis.__VIKE__IS_DEV);
}
else {
// import.meta.env isn't defined when 'vike' is ssr.external
}
}
const expectedEnglish = joinEnglish(expected.map((s) => pc.bold(String(s))), 'or');
const statusCodeWithColor = pc.bold(String(statusCode));
if (statusCode === 400) {
assert(!expected.includes(statusCode));
assertWarning(false, `We recommend against using the status code ${statusCodeWithColor} passed to ${caller}() — we recommend using ${pc.bold('404')} instead, see https://github.com/vikejs/vike/issues/1008#issuecomment-3270894445`, { onlyOnce: true, showStackTrace: true });
}
else {
assertWarning(expected.includes(statusCode), `Unexpected status code ${statusCodeWithColor} passed to ${caller}() — we recommend using ${expectedEnglish} instead. (Or reach out at https://github.com/vikejs/vike/issues/1008 if you believe ${statusCodeWithColor} should be added.)`, { onlyOnce: true, showStackTrace: true });
}
}
function getPageContextAddendumAbort(pageContextsAborted) {
const pageContextAbortedLast = pageContextsAborted.at(-1);
if (!pageContextAbortedLast)
return null;
const pageContextAbort = pageContextAbortedLast._pageContextAbort;
assert(pageContextAbort);
// Sets pageContext._urlRewrite from pageContextAbort._urlRewrite
return pageContextAbort;
}
function addNewPageContextAborted(pageContextsAborted, pageContext, pageContextAbort) {
objectAssign(pageContext, { _pageContextAbort: pageContextAbort });
pageContextsAborted.push(pageContext);
assertNoInfiniteAbortLoop(pageContextsAborted);
}
// There doesn't seem to be a way to count the number of HTTP redirects (Vike doesn't have access to the HTTP request headers/cookies)
// https://stackoverflow.com/questions/9683007/detect-infinite-http-redirect-loop-on-server-side
function assertNoInfiniteAbortLoop(pageContextsAborted) {
if (pageContextsAborted.length < 10)
return;
const loop = pageContextsAborted.map((pageContext) => {
return pageContext._pageContextAbort._abortCall;
});
// Unique array => no redundant call => no infinite loop
if (unique(loop).length === loop.length)
return;
assertUsage(false, `Infinite loop: ${loop.join(' => ')}`);
}
function getErrPrefix(abortCaller) {
return `URL passed to ${pc.code(abortCaller)}`;
}