UNPKG

hono

Version:

Web framework built on Web Standards

163 lines (162 loc) 5.04 kB
// src/jsx/context.ts import { raw } from "../helper/html/index.js"; import { JSXFragmentNode } from "./base.js"; import { DOM_RENDERER } from "./constants.js"; import { createContextProviderFunction } from "./dom/context.js"; var globalContexts = []; var alsProbed = false; var asyncLocalStorage; var fallbackStore; var fallbackRendersInFlight = 0; var warnedFallbackDefault = false; var loadAsyncLocalStorage = () => { if (alsProbed) { return asyncLocalStorage; } alsProbed = true; const global = globalThis; let AsyncLocalStorage; for (const probe of [ // Node.js >= 20.16, Deno, Bun, Cloudflare Workers (nodejs_compat). Property // access only, so bundlers don't statically resolve `node:async_hooks`. () => global.process?.getBuiltinModule?.("node:async_hooks")?.AsyncLocalStorage, // Node.js < 20.16 has no `process.getBuiltinModule`, but a CJS entrypoint // exposes the main module's `require` here. () => global.process?.mainModule?.require?.("node:async_hooks")?.AsyncLocalStorage ]) { try { AsyncLocalStorage = probe(); } catch { } if (AsyncLocalStorage) { break; } } if (AsyncLocalStorage) { asyncLocalStorage = new AsyncLocalStorage(); } return asyncLocalStorage; }; var getCurrentStore = () => { return loadAsyncLocalStorage()?.getStore() || fallbackStore; }; var warnIfStorelessAccess = () => { if (fallbackRendersInFlight > 0 && !warnedFallbackDefault) { warnedFallbackDefault = true; console.warn( "hono/jsx: AsyncLocalStorage is unavailable in this runtime, so useContext() after an await in an async component falls back to the context default value during server-side rendering. To get provided values across await boundaries, use a runtime with AsyncLocalStorage (Node.js >= 20.16, Deno, Bun, or Cloudflare Workers with the nodejs_compat flag)." ); } }; var getContextValuesIn = (store, context) => { if (!store) { warnIfStorelessAccess(); return context.values; } let values = store.get(context); if (!values) { values = [context.values[0]]; store.set(context, values); } return values; }; var readContextValueIn = (store, context) => { if (!store) { warnIfStorelessAccess(); return context.values.at(-1); } const values = store.get(context); return values?.length ? values.at(-1) : context.values[0]; }; var captureContextValues = (store) => (store ? globalContexts.filter((c) => store.has(c)) : globalContexts).map((c) => [ c, readContextValueIn(store, c) ]); var resumeWithContextValues = (callback, store, contexts) => runWithRenderContext(() => { const currentStore = getCurrentStore(); const valuesPerContext = contexts.map(([context, value]) => { const values = getContextValuesIn(currentStore, context); values.push(value); return values; }); const popContextValues = () => { valuesPerContext.forEach((values) => { values.pop(); }); }; try { const result = callback(); if (result instanceof Promise) { return result.finally(popContextValues); } popContextValues(); return result; } catch (e) { popContextValues(); throw e; } }, store); var runWithRenderContext = (callback, resumeStore) => { if (getCurrentStore()) { return callback(); } const store = resumeStore ?? /* @__PURE__ */ new WeakMap(); const storage = loadAsyncLocalStorage(); if (storage) { return storage.run(store, callback); } fallbackStore = store; let result; try { result = callback(); } finally { fallbackStore = void 0; } if (!warnedFallbackDefault && result instanceof Promise) { fallbackRendersInFlight++; result = result.finally(() => { fallbackRendersInFlight--; }); } return result; }; var captureRenderContext = () => { const store = getCurrentStore(); const contexts = captureContextValues(store); return (callback) => resumeWithContextValues(callback, store, contexts); }; var createContext = (defaultValue) => { const values = [defaultValue]; const context = ((props) => { const contextValues = getContextValuesIn(getCurrentStore(), context); contextValues.push(props.value); let string; try { string = props.children ? (Array.isArray(props.children) ? new JSXFragmentNode("", {}, props.children) : props.children).toString() : ""; } catch (e) { contextValues.pop(); throw e; } if (string instanceof Promise) { return string.finally(() => contextValues.pop()).then((resString) => raw(resString, resString.callbacks)); } else { contextValues.pop(); return raw(string); } }); context.values = values; context.Provider = context; context[DOM_RENDERER] = createContextProviderFunction(values); globalContexts.push(context); return context; }; var useContext = (context) => { return readContextValueIn(getCurrentStore(), context); }; export { captureRenderContext, createContext, globalContexts, runWithRenderContext, useContext };