hono
Version:
Web framework built on Web Standards
163 lines (162 loc) • 5.04 kB
JavaScript
// 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
};