UNPKG

vike

Version:

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

174 lines (173 loc) 7.29 kB
export { redirect }; export { render }; export { RenderErrorPage }; export { isAbortError }; export { isAbortPageContext }; export { logAbortErrorHandled }; export { getPageContextFromAllRewrites }; export { AbortRender }; export { assertNoInfiniteAbortLoop }; import { isUserHookError } from '../hooks/execHook.js'; import { assert, assertInfo, assertUsage, assertUsageUrlPathnameAbsolute, assertUsageUrlRedirectTarget, assertWarning, checkType, hasProp, joinEnglish, objectAssign, truncateString, } from './utils.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 a temporary redirection (`302`) is performed. For permanent redirections (`301`), use `config.redirects` https://vike.dev/redirects instead or, alternatively, set the `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 { 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; assertUsageUrlPathnameAbsolute(url, getErrPrefix(abortCaller)); objectAssign(pageContextAbort, { _urlRewrite: url, }); return AbortRender(pageContextAbort); } else { const abortStatusCode = urlOrStatusCode; assertStatusCode(urlOrStatusCode, [401, 403, 404, 410, 429, 500, 503], 'render'); 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; } // TODO/v1-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 logAbortErrorHandled(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) { const expectedEnglish = joinEnglish(expected.map((s) => s.toString()), 'or'); assertWarning(expected.includes(statusCode), `Unepexected status code ${statusCode} passed to ${caller}(), we recommend ${expectedEnglish} instead. (Or reach out at https://github.com/vikejs/vike/issues/1008 if you believe ${statusCode} should be added.)`, { onlyOnce: true }); } function getPageContextFromAllRewrites(pageContextsFromRewrite) { assertNoInfiniteLoop(pageContextsFromRewrite); const pageContextFromAllRewrites = { _urlRewrite: null }; pageContextsFromRewrite.forEach((pageContextFromRewrite) => { Object.assign(pageContextFromAllRewrites, pageContextFromRewrite); }); return pageContextFromAllRewrites; } function assertNoInfiniteLoop(pageContextsFromRewrite) { const urlRewrites = []; pageContextsFromRewrite.forEach((pageContext) => { const urlRewrite = pageContext._urlRewrite; { const idx = urlRewrites.indexOf(urlRewrite); if (idx !== -1) { const loop = [...urlRewrites.slice(idx), urlRewrite].map((url) => `render('${url}')`).join(' => '); assertUsage(false, `Infinite loop of render() calls: ${loop}`); } } urlRewrites.push(urlRewrite); }); } function assertNoInfiniteAbortLoop(rewriteCount, redirectCount) { const abortCalls = [ // prettier-ignore // biome-ignore format: rewriteCount > 0 && pc.cyan("throw render('/some-url')"), redirectCount > 0 && pc.cyan("throw redirect('/some-url')"), ] .filter(Boolean) .join(' and '); assertUsage(rewriteCount + redirectCount <= 7, `Maximum chain length of 7 ${abortCalls} exceeded. Did you define an infinite loop of ${abortCalls}?`); } function getErrPrefix(abortCaller) { return `URL passed to ${pc.code(abortCaller)}`; }