UNPKG

@modern-js/server-core

Version:

A Progressive React Framework for modern web development.

217 lines (216 loc) • 6.66 kB
"use strict"; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); var ssrCache_exports = {}; __export(ssrCache_exports, { getCacheResult: () => getCacheResult, matchCacheControl: () => matchCacheControl, shouldUseCache: () => shouldUseCache }); module.exports = __toCommonJS(ssrCache_exports); var import_storer = require("@modern-js/runtime-utils/storer"); var import_constants = require("../../constants"); var import_utils = require("../../utils"); const removeTailSlash = (s) => s.replace(/\/+$/, ""); const ZERO_RENDER_LEVEL = /"renderLevel":0/; const NO_SSR_CACHE = /<meta\s+[^>]*name=["']no-ssr-cache["'][^>]*>/i; async function processCache({ request, key, requestHandler, requestHandlerOptions, ttl, container, cacheStatus }) { const response = await requestHandler(request, requestHandlerOptions); const { onError } = requestHandlerOptions; const nonCacheableStatusCodes = [ 204, 305, 404, 405, 500, 501, 502, 503, 504 ]; if (nonCacheableStatusCodes.includes(response.status)) { return response; } const decoder = new TextDecoder(); if (response.body) { const stream = (0, import_utils.createTransformStream)(); const reader = response.body.getReader(); const writer = stream.writable.getWriter(); let html = ""; const push = () => reader.read().then(({ done, value }) => { if (done) { const match = ZERO_RENDER_LEVEL.test(html) || NO_SSR_CACHE.test(html); if (match) { writer.close(); return; } const current = Date.now(); const cache = { val: html, cursor: current }; container.set(key, JSON.stringify(cache), { ttl }).catch(() => { if (onError) { onError(`[render-cache] set cache failed, key: ${key}, value: ${JSON.stringify(cache)}`); } else { console.error(`[render-cache] set cache failed, key: ${key}, value: ${JSON.stringify(cache)}`); } }); writer.close(); return; } const content = decoder.decode(value); html += content; writer.write(value); push(); }); push(); cacheStatus && response.headers.set(import_constants.X_RENDER_CACHE, cacheStatus); return new Response(stream.readable, { status: response.status, headers: response.headers }); } return response; } const CACHE_NAMESPACE = "__ssr__cache"; const storage = (0, import_storer.createMemoryStorage)(CACHE_NAMESPACE); function computedKey(req, cacheControl) { const pathname = (0, import_utils.getPathname)(req); const { customKey } = cacheControl; const defaultKey = pathname === "/" ? pathname : removeTailSlash(pathname); if (customKey) { if (typeof customKey === "string") { return customKey; } else { return customKey(defaultKey); } } else { return defaultKey; } } function shouldUseCache(request) { const url = new URL(request.url); const hasRSCAction = request.headers.has("x-rsc-action"); const hasRSCTree = request.headers.has("x-rsc-tree"); const hasLoaderQuery = url.searchParams.has("__loader"); return !(hasRSCAction || hasRSCTree || hasLoaderQuery); } function matchCacheControl(cacheOption, req) { if (!cacheOption || !req) { return void 0; } else if (isCacheControl(cacheOption)) { return cacheOption; } else if (isCacheOptionProvider(cacheOption)) { return cacheOption(req); } else { const url = req.url; const options = Object.entries(cacheOption); for (const [key, option] of options) { if (key === "*" || new RegExp(key).test(url)) { if (typeof option === "function") { return option(req); } else { return option; } } } return void 0; } function isCacheOptionProvider(option) { return typeof option === "function"; } function isCacheControl(option) { return typeof option === "object" && option !== null && "maxAge" in option; } } async function getCacheResult(request, options) { const { cacheControl, container = storage, requestHandler, requestHandlerOptions } = options; const { onError } = requestHandlerOptions; const key = computedKey(request, cacheControl); let value; try { value = await container.get(key); } catch (_) { if (onError) { onError(`[render-cache] get cache failed, key: ${key}`); } else { console.error(`[render-cache] get cache failed, key: ${key}`); } value = void 0; } const { maxAge, staleWhileRevalidate } = cacheControl; const ttl = maxAge + staleWhileRevalidate; if (value) { const cache = JSON.parse(value); const interval = Date.now() - cache.cursor; if (interval <= maxAge) { const cacheStatus = "hit"; return new Response(cache.val, { headers: { [import_constants.X_RENDER_CACHE]: cacheStatus } }); } else if (interval <= staleWhileRevalidate + maxAge) { processCache({ key, request, requestHandler, requestHandlerOptions, ttl, container }).then(async (response) => { await response.text(); }); const cacheStatus = "stale"; return new Response(cache.val, { headers: { [import_constants.X_RENDER_CACHE]: cacheStatus } }); } else { return processCache({ key, request, requestHandler, requestHandlerOptions, ttl, container, cacheStatus: "expired" }); } } else { return processCache({ key, request, requestHandler, requestHandlerOptions, ttl, container, cacheStatus: "miss" }); } } // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { getCacheResult, matchCacheControl, shouldUseCache });