UNPKG

vike

Version:

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

126 lines (125 loc) 6.65 kB
export { execHookOnRenderHtml }; import { isDocumentHtml, renderDocumentHtml, dangerouslySkipEscape, } from '../html/renderHtml.js'; import { getHookFromPageContext } from '../../../shared/hooks/getHook.js'; import { assert, assertUsage, assertWarning, isObject, objectAssign, isPromise, isCallable } from '../utils.js'; import { isStream } from '../html/stream.js'; import { assertPageContextProvidedByUser } from '../../../shared/assertPageContextProvidedByUser.js'; import { preparePageContextForPublicUsageServer, } from './preparePageContextForPublicUsageServer.js'; import { assertHookReturnedObject } from '../../../shared/assertHookReturnedObject.js'; import { logRuntimeError } from '../loggerRuntime.js'; import pc from '@brillout/picocolors'; import { execHookDirectSingleWithReturn } from '../../../shared/hooks/execHook.js'; async function execHookOnRenderHtml(pageContext) { const hook = getRenderHook(pageContext); objectAssign(pageContext, { _renderHook: hook }); const { hookReturn } = await execHookDirectSingleWithReturn(hook, pageContext, preparePageContextForPublicUsageServer); const { documentHtml, pageContextProvidedByRenderHook, pageContextPromise, injectFilter } = processHookReturnValue(hookReturn, hook); Object.assign(pageContext, pageContextProvidedByRenderHook); objectAssign(pageContext, { _pageContextPromise: pageContextPromise }); const onErrorWhileStreaming = (err) => { // Should the stream inject the following? // ``` // <script>console.error("An error occurred on the server side while streaming the page to HTML, see server logs.")</script> // ``` logRuntimeError(err, pageContext._httpRequestId); if (!pageContext.errorWhileRendering) { pageContext.errorWhileRendering = err; } }; const htmlRender = await renderDocumentHtml(documentHtml, pageContext, onErrorWhileStreaming, injectFilter); assert(typeof htmlRender === 'string' || isStream(htmlRender)); return { htmlRender, renderHook: hook }; } function getRenderHook(pageContext) { let hookFound; { let hook; let hookName = undefined; hook = getHookFromPageContext(pageContext, 'onRenderHtml'); if (hook) { hookName = 'onRenderHtml'; } else { hook = getHookFromPageContext(pageContext, 'render'); if (hook) { hookName = 'render'; } } if (hook) { assert(hookName); const { hookFilePath, hookFn, hookTimeout } = hook; hookFound = { hookFn, hookFilePath, hookName, hookTimeout }; } } if (!hookFound) { const hookName = pageContext._globalContext._pageConfigs.length > 0 ? 'onRenderHtml' : 'render'; assertUsage(false, [ `No ${hookName}() hook found, see https://vike.dev/${hookName}`, /* 'See https://vike.dev/render-modes for more information.', [ // 'Loaded config files (none of them define the onRenderHtml() hook):', 'Loaded server-side page files (none of them `export { render }`):', ...pageContext._pageFilePathsLoaded.map((f, i) => ` (${i + 1}): ${f}`) ].join('\n') */ ].join(' ')); } return hookFound; } function processHookReturnValue(hookReturnValue, renderHook) { let documentHtml; let pageContextPromise = null; let pageContextProvidedByRenderHook = null; let injectFilter = null; if (isDocumentHtml(hookReturnValue)) { documentHtml = hookReturnValue; return { documentHtml, pageContextProvidedByRenderHook, pageContextPromise, injectFilter }; } const errPrefix = `The ${renderHook.hookName}() hook defined at ${renderHook.hookFilePath}`; const errSuffix = `a string generated with ${pc.cyan('escapeInject`<html>...</html>`')} or the value returned by ${pc.cyan('dangerouslySkipEscape()')}, see https://vike.dev/escapeInject`; if (typeof hookReturnValue === 'string') { assertWarning(false, [ errPrefix, `returned a plain JavaScript string which is ${pc.red(pc.bold('dangerous'))}: it should instead return`, errSuffix, ].join(' '), { onlyOnce: true }); hookReturnValue = dangerouslySkipEscape(hookReturnValue); } const wrongReturnValue = `should return the value ${pc.cyan('documentHtml')} or an object ${pc.cyan('{ documentHtml }')} where ${pc.cyan('documentHtml')} is ${errSuffix}`; assertUsage(isObject(hookReturnValue), `${errPrefix} ${wrongReturnValue}`); assertHookReturnedObject(hookReturnValue, ['documentHtml', 'pageContext', 'injectFilter'], errPrefix); assertUsage(hookReturnValue.documentHtml, `${errPrefix} returned an object that is missing the ${pc.code('documentHtml')} property: it ${wrongReturnValue}`); if (hookReturnValue.injectFilter) { assertUsage(isCallable(hookReturnValue.injectFilter), 'injectFilter should be a function'); injectFilter = hookReturnValue.injectFilter; } { let val = hookReturnValue.documentHtml; const errBegin = `${errPrefix} returned ${pc.cyan('{ documentHtml }')}, but ${pc.cyan('documentHtml')}`; if (typeof val === 'string') { assertWarning(false, [ errBegin, `is a plain JavaScript string which is ${pc.bold(pc.red('dangerous'))}: ${pc.cyan('documentHtml')} should be`, errSuffix, ].join(' '), { onlyOnce: true }); val = dangerouslySkipEscape(val); } assertUsage(isDocumentHtml(val), [errBegin, 'should be', errSuffix].join(' ')); documentHtml = val; } if (hookReturnValue.pageContext) { const val = hookReturnValue.pageContext; const errBegin = `${errPrefix} returned ${pc.cyan('{ pageContext }')}, but ${pc.cyan('pageContext')}`; if (isPromise(val) || isCallable(val)) { assertWarning(!isPromise(val), `${errBegin} is a promise which is deprecated in favor of async functions, see https://vike.dev/streaming#initial-data-after-stream-end`, { onlyOnce: true }); pageContextPromise = val; } else { assertUsage(isObject(val), `${errBegin} should be an object or an async function, see https://vike.dev/streaming#initial-data-after-stream-end`); assertPageContextProvidedByUser(val, renderHook); pageContextProvidedByRenderHook = val; } } return { documentHtml, pageContextProvidedByRenderHook, pageContextPromise, injectFilter }; }