163 lines
4.9 KiB
JavaScript
163 lines
4.9 KiB
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
|
|
};
|