vike
Version:
The Framework *You* Control - Next.js & Nuxt alternative for unprecedented flexibility and dependability.
246 lines (245 loc) • 11.7 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.escapeInject = escapeInject;
exports.dangerouslySkipEscape = dangerouslySkipEscape;
exports.renderDocumentHtml = renderDocumentHtml;
exports.isDocumentHtml = isDocumentHtml;
exports.getHtmlString = getHtmlString;
const utils_js_1 = require("../utils.js");
const injectAssets_js_1 = require("./injectAssets.js");
const stream_js_1 = require("./stream.js");
const react_streaming_js_1 = require("./stream/react-streaming.js");
const picocolors_1 = __importDefault(require("@brillout/picocolors"));
function isDocumentHtml(something) {
if (isTemplateWrapped(something) || isEscapedString(something) || (0, stream_js_1.isStream)(something)) {
(0, utils_js_1.checkType)(something);
return true;
}
return false;
}
async function renderDocumentHtml(documentHtml, pageContext, onErrorWhileStreaming, injectFilter) {
if (isEscapedString(documentHtml)) {
(0, utils_js_1.objectAssign)(pageContext, { _isStream: false });
let htmlString = getEscapedString(documentHtml);
htmlString = await (0, injectAssets_js_1.injectHtmlTagsToString)([htmlString], pageContext, injectFilter);
return htmlString;
}
if ((0, stream_js_1.isStream)(documentHtml)) {
(0, utils_js_1.objectAssign)(pageContext, { _isStream: true });
const stream = documentHtml;
const streamWrapper = await renderHtmlStream(stream, null, pageContext, onErrorWhileStreaming, injectFilter);
return streamWrapper;
}
if (isTemplateWrapped(documentHtml)) {
const templateContent = documentHtml._template;
const render = renderTemplate(templateContent, pageContext);
if (!('htmlStream' in render)) {
(0, utils_js_1.objectAssign)(pageContext, { _isStream: false });
const { htmlPartsAll } = render;
const htmlString = await (0, injectAssets_js_1.injectHtmlTagsToString)(htmlPartsAll, pageContext, injectFilter);
return htmlString;
}
else {
(0, utils_js_1.objectAssign)(pageContext, { _isStream: true });
const { htmlStream } = render;
const streamWrapper = await renderHtmlStream(htmlStream, {
htmlPartsBegin: render.htmlPartsBegin,
htmlPartsEnd: render.htmlPartsEnd,
}, pageContext, onErrorWhileStreaming, injectFilter);
return streamWrapper;
}
}
(0, utils_js_1.checkType)(documentHtml);
(0, utils_js_1.assert)(false);
}
async function renderHtmlStream(streamOriginal, injectString, pageContext, onErrorWhileStreaming, injectFilter) {
const processStreamOptions = {
onErrorWhileStreaming,
enableEagerStreaming: pageContext.enableEagerStreaming,
};
if (injectString) {
let streamFromReactStreamingPackage = null;
if ((0, react_streaming_js_1.isStreamFromReactStreamingPackage)(streamOriginal) && !streamOriginal.disabled) {
streamFromReactStreamingPackage = streamOriginal;
}
const { injectAtStreamBegin, injectAtStreamAfterFirstChunk, injectAtStreamEnd } = (0, injectAssets_js_1.injectHtmlTagsToStream)(pageContext, streamFromReactStreamingPackage, injectFilter);
processStreamOptions.injectStringAtBegin = async () => {
return await injectAtStreamBegin(injectString.htmlPartsBegin);
};
processStreamOptions.injectStringAtEnd = async () => {
return await injectAtStreamEnd(injectString.htmlPartsEnd);
};
processStreamOptions.injectStringAfterFirstChunk = () => {
return injectAtStreamAfterFirstChunk();
};
}
let makeClosableAgain = () => { };
if ((0, react_streaming_js_1.isStreamFromReactStreamingPackage)(streamOriginal)) {
// Make sure Vike injects its HTML fragments, such as `<script id="vike_pageContext" type="application/json">`, before the stream is closed (if React/Vue finishes its stream before the promise below resolves).
makeClosableAgain = streamOriginal.doNotClose();
}
try {
const streamWrapper = await (0, stream_js_1.processStream)(streamOriginal, processStreamOptions);
return streamWrapper;
}
finally {
makeClosableAgain();
}
}
function isTemplateWrapped(something) {
return (0, utils_js_1.hasProp)(something, '_template');
}
function isEscapedString(something) {
const result = (0, utils_js_1.hasProp)(something, '_escaped');
if (result) {
(0, utils_js_1.assert)((0, utils_js_1.hasProp)(something, '_escaped', 'string'));
(0, utils_js_1.checkType)(something);
}
return result;
}
function getEscapedString(escapedString) {
let htmlString;
(0, utils_js_1.assert)((0, utils_js_1.hasProp)(escapedString, '_escaped'));
htmlString = escapedString._escaped;
(0, utils_js_1.assert)(typeof htmlString === 'string');
return htmlString;
}
function escapeInject(templateStrings, ...templateVariables) {
(0, utils_js_1.assertUsage)(templateStrings.length === templateVariables.length + 1 && templateStrings.every((str) => typeof str === 'string'), `You're using ${picocolors_1.default.cyan('escapeInject')} as a function, but ${picocolors_1.default.cyan('escapeInject')} is a string template tag, see https://vike.dev/escapeInject`, { showStackTrace: true });
return {
_template: {
templateStrings,
templateVariables: templateVariables,
},
};
}
function dangerouslySkipEscape(alreadyEscapedString) {
return _dangerouslySkipEscape(alreadyEscapedString);
}
function _dangerouslySkipEscape(arg) {
if ((0, utils_js_1.hasProp)(arg, '_escaped')) {
(0, utils_js_1.assert)(isEscapedString(arg));
return arg;
}
(0, utils_js_1.assertUsage)(!(0, utils_js_1.isPromise)(arg), `[dangerouslySkipEscape(${picocolors_1.default.cyan('str')})] Argument ${picocolors_1.default.cyan('str')} is a promise. It should be a string instead (or a stream). Make sure to ${picocolors_1.default.cyan('await str')}.`, { showStackTrace: true });
if (typeof arg === 'string') {
return { _escaped: arg };
}
(0, utils_js_1.assertWarning)(false, `[dangerouslySkipEscape(${picocolors_1.default.cyan('str')})] Argument ${picocolors_1.default.cyan('str')} should be a string but we got ${picocolors_1.default.cyan(`typeof str === "${typeof arg}"`)}.`, {
onlyOnce: false,
showStackTrace: true,
});
return { _escaped: String(arg) };
}
function renderTemplate(templateContent, pageContext) {
const htmlPartsBegin = [];
const htmlPartsEnd = [];
let htmlStream = null;
const addHtmlPart = (htmlPart) => {
if (htmlStream === null) {
htmlPartsBegin.push(htmlPart);
}
else {
htmlPartsEnd.push(htmlPart);
}
};
const setStream = (stream) => {
const { hookName, hookFilePath } = pageContext._renderHook;
(0, utils_js_1.assertUsage)(!htmlStream, `Injecting two streams in ${picocolors_1.default.cyan('escapeInject')} template tag of ${hookName}() hook defined by ${hookFilePath}. Inject only one stream instead.`);
htmlStream = stream;
};
const { templateStrings, templateVariables } = templateContent;
for (let i = 0; i < templateVariables.length; i++) {
addHtmlPart(templateStrings[i]);
let templateVar = templateVariables[i];
// Process `dangerouslySkipEscape()`
if (isEscapedString(templateVar)) {
const htmlString = getEscapedString(templateVar);
// User used `dangerouslySkipEscape()` so we assume the string to be safe
addHtmlPart(htmlString);
continue;
}
// Process `escapeInject` fragments
if (isTemplateWrapped(templateVar)) {
const templateContentInner = templateVar._template;
const result = renderTemplate(templateContentInner, pageContext);
if (!('htmlStream' in result)) {
result.htmlPartsAll.forEach(addHtmlPart);
}
else {
result.htmlPartsBegin.forEach(addHtmlPart);
setStream(result.htmlStream);
result.htmlPartsEnd.forEach(addHtmlPart);
}
continue;
}
if ((0, stream_js_1.isStream)(templateVar)) {
setStream(templateVar);
continue;
}
const getErrMsg = (msg) => {
const { hookName, hookFilePath } = pageContext._renderHook;
const nth = (i === 0 && '1st') || (i === 1 && '2nd') || (i === 2 && '3rd') || `${i}-th`;
return [
`The ${nth} HTML variable is ${msg}`,
`The HTML was provided by the ${hookName}() hook at ${hookFilePath}.`,
]
.filter(Boolean)
.join(' ');
};
(0, utils_js_1.assertUsage)(!(0, utils_js_1.isPromise)(templateVar), getErrMsg(`a promise, did you forget to ${picocolors_1.default.cyan('await')} the promise?`));
if (templateVar === undefined || templateVar === null) {
const msgVal = picocolors_1.default.cyan(String(templateVar));
const msgEmptyString = picocolors_1.default.cyan("''");
const msg = `${msgVal} which will be converted to an empty string. Pass the empty string ${msgEmptyString} instead of ${msgVal} to remove this warning.`;
(0, utils_js_1.assertWarning)(false, getErrMsg(msg), { onlyOnce: false });
templateVar = '';
}
{
const varType = typeof templateVar;
if (varType !== 'string') {
const msgType = picocolors_1.default.cyan(`typeof htmlVariable === "${varType}"`);
const msg = `${msgType} but a string or stream (https://vike.dev/streaming) is expected instead.`;
(0, utils_js_1.assertUsage)(false, getErrMsg(msg));
}
}
{
const { _isProduction: isProduction } = pageContext._globalContext;
if ((0, utils_js_1.isHtml)(templateVar) &&
// We don't show this warning in production because it's expected that some users may (un)willingly do some XSS injection: we avoid flooding the production logs.
!isProduction) {
const msgVal = picocolors_1.default.cyan(String(templateVar));
const msg = `${msgVal} which seems to be HTML code. Did you forget to wrap the value with dangerouslySkipEscape()?`;
(0, utils_js_1.assertWarning)(false, getErrMsg(msg), { onlyOnce: false });
}
}
// Escape untrusted template variable
addHtmlPart((0, utils_js_1.escapeHtml)(templateVar));
}
(0, utils_js_1.assert)(templateStrings.length === templateVariables.length + 1);
addHtmlPart(templateStrings[templateStrings.length - 1]);
if (htmlStream === null) {
(0, utils_js_1.assert)(htmlPartsEnd.length === 0);
return {
htmlPartsAll: htmlPartsBegin,
};
}
return {
htmlStream,
htmlPartsBegin,
htmlPartsEnd,
};
}
async function getHtmlString(htmlRender) {
if (typeof htmlRender === 'string') {
return htmlRender;
}
if ((0, stream_js_1.isStream)(htmlRender)) {
return (0, stream_js_1.streamToString)(htmlRender);
}
(0, utils_js_1.checkType)(htmlRender);
(0, utils_js_1.assert)(false);
}