hadars 0.1.22 → 0.1.24

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -7,8 +7,45 @@ import {
7
7
  createElement,
8
8
  jsx
9
9
  } from "../chunk-OS3V4CPN.js";
10
+ import {
11
+ __require
12
+ } from "../chunk-EZUCZHGV.js";
10
13
 
11
14
  // src/slim-react/renderContext.ts
15
+ var CONTEXT_STORE_KEY = "__slimReactContextStore";
16
+ var _g = globalThis;
17
+ if (!_g[CONTEXT_STORE_KEY]) {
18
+ try {
19
+ const { AsyncLocalStorage } = __require("node:async_hooks");
20
+ _g[CONTEXT_STORE_KEY] = new AsyncLocalStorage();
21
+ } catch {
22
+ _g[CONTEXT_STORE_KEY] = null;
23
+ }
24
+ }
25
+ var _contextStore = _g[CONTEXT_STORE_KEY];
26
+ function runWithContextStore(fn) {
27
+ return _contextStore ? _contextStore.run(/* @__PURE__ */ new Map(), fn) : fn();
28
+ }
29
+ function getContextValue(context) {
30
+ const store = _contextStore?.getStore();
31
+ if (store && store.has(context))
32
+ return store.get(context);
33
+ const c = context;
34
+ return "_defaultValue" in c ? c._defaultValue : c._currentValue;
35
+ }
36
+ function pushContextValue(context, value) {
37
+ const store = _contextStore?.getStore();
38
+ const c = context;
39
+ const prev = store && store.has(context) ? store.get(context) : "_defaultValue" in c ? c._defaultValue : c._currentValue;
40
+ if (store)
41
+ store.set(context, value);
42
+ return prev;
43
+ }
44
+ function popContextValue(context, prev) {
45
+ const store = _contextStore?.getStore();
46
+ if (store)
47
+ store.set(context, prev);
48
+ }
12
49
  var GLOBAL_KEY = "__slimReactRenderState";
13
50
  var EMPTY = { id: 0, overflow: "", bits: 0 };
14
51
  function s() {
@@ -134,8 +171,8 @@ function useActionState(_action, initialState, _permalink) {
134
171
  }, false];
135
172
  }
136
173
  function use(usable) {
137
- if (typeof usable === "object" && usable !== null && "_currentValue" in usable) {
138
- return usable._currentValue;
174
+ if (typeof usable === "object" && usable !== null && ("_currentValue" in usable || "_defaultValue" in usable)) {
175
+ return getContextValue(usable);
139
176
  }
140
177
  const promise = usable;
141
178
  if (promise.status === "fulfilled")
@@ -151,6 +188,7 @@ function startTransition(callback) {
151
188
  // src/slim-react/context.ts
152
189
  function createContext(defaultValue) {
153
190
  const context = {
191
+ _defaultValue: defaultValue,
154
192
  _currentValue: defaultValue,
155
193
  Provider: null,
156
194
  Consumer: null
@@ -526,7 +564,7 @@ function renderComponent(type, props, writer, isSvg) {
526
564
  }
527
565
  if (typeOf === REACT_CONSUMER) {
528
566
  const ctx2 = type._context;
529
- const value = ctx2?._currentValue;
567
+ const value = ctx2 ? getContextValue(ctx2) : void 0;
530
568
  const result2 = typeof props.children === "function" ? props.children(value) : null;
531
569
  const savedScope2 = pushComponentScope();
532
570
  const finish2 = () => popComponentScope(savedScope2);
@@ -542,18 +580,20 @@ function renderComponent(type, props, writer, isSvg) {
542
580
  let ctx;
543
581
  if (isProvider) {
544
582
  ctx = type._context ?? type;
545
- prevCtxValue = ctx._currentValue;
546
- ctx._currentValue = props.value;
583
+ prevCtxValue = pushContextValue(ctx, props.value);
547
584
  }
548
585
  const savedScope = pushComponentScope();
549
586
  if (isProvider && typeof type !== "function") {
550
587
  const finish2 = () => {
551
588
  popComponentScope(savedScope);
552
- ctx._currentValue = prevCtxValue;
589
+ popContextValue(ctx, prevCtxValue);
553
590
  };
554
591
  const r2 = renderChildren(props.children, writer, isSvg);
555
592
  if (r2 && typeof r2.then === "function") {
556
- return r2.then(finish2);
593
+ return r2.then(finish2, (e) => {
594
+ finish2();
595
+ throw e;
596
+ });
557
597
  }
558
598
  finish2();
559
599
  return;
@@ -574,26 +614,35 @@ function renderComponent(type, props, writer, isSvg) {
574
614
  } catch (e) {
575
615
  popComponentScope(savedScope);
576
616
  if (isProvider)
577
- ctx._currentValue = prevCtxValue;
617
+ popContextValue(ctx, prevCtxValue);
578
618
  throw e;
579
619
  }
580
620
  const finish = () => {
581
621
  popComponentScope(savedScope);
582
622
  if (isProvider)
583
- ctx._currentValue = prevCtxValue;
623
+ popContextValue(ctx, prevCtxValue);
584
624
  };
585
625
  if (result instanceof Promise) {
586
626
  return result.then((resolved) => {
587
627
  const r2 = renderNode(resolved, writer, isSvg);
588
628
  if (r2 && typeof r2.then === "function") {
589
- return r2.then(finish);
629
+ return r2.then(finish, (e) => {
630
+ finish();
631
+ throw e;
632
+ });
590
633
  }
591
634
  finish();
635
+ }, (e) => {
636
+ finish();
637
+ throw e;
592
638
  });
593
639
  }
594
640
  const r = renderNode(result, writer, isSvg);
595
641
  if (r && typeof r.then === "function") {
596
- return r.then(finish);
642
+ return r.then(finish, (e) => {
643
+ finish();
644
+ throw e;
645
+ });
597
646
  }
598
647
  finish();
599
648
  }
@@ -679,7 +728,7 @@ async function renderSuspense(props, writer, isSvg = false) {
679
728
  }
680
729
  function renderToStream(element) {
681
730
  const encoder = new TextEncoder();
682
- return new ReadableStream({
731
+ return runWithContextStore(() => new ReadableStream({
683
732
  async start(controller) {
684
733
  resetRenderState();
685
734
  const writer = {
@@ -702,42 +751,44 @@ function renderToStream(element) {
702
751
  controller.error(error);
703
752
  }
704
753
  }
705
- });
754
+ }));
706
755
  }
707
- async function renderToString(element) {
708
- for (let attempt = 0; attempt < MAX_SUSPENSE_RETRIES; attempt++) {
709
- resetRenderState();
710
- const chunks = [];
711
- const writer = {
712
- lastWasText: false,
713
- write(c) {
714
- chunks.push(c);
715
- this.lastWasText = false;
716
- },
717
- text(s2) {
718
- chunks.push(s2);
719
- this.lastWasText = true;
720
- }
721
- };
722
- try {
723
- const r = renderNode(element, writer);
724
- if (r && typeof r.then === "function")
725
- await r;
726
- return chunks.join("");
727
- } catch (error) {
728
- if (error && typeof error.then === "function") {
729
- await error;
730
- continue;
756
+ function renderToString(element) {
757
+ return runWithContextStore(async () => {
758
+ for (let attempt = 0; attempt < MAX_SUSPENSE_RETRIES; attempt++) {
759
+ resetRenderState();
760
+ const chunks = [];
761
+ const writer = {
762
+ lastWasText: false,
763
+ write(c) {
764
+ chunks.push(c);
765
+ this.lastWasText = false;
766
+ },
767
+ text(s2) {
768
+ chunks.push(s2);
769
+ this.lastWasText = true;
770
+ }
771
+ };
772
+ try {
773
+ const r = renderNode(element, writer);
774
+ if (r && typeof r.then === "function")
775
+ await r;
776
+ return chunks.join("");
777
+ } catch (error) {
778
+ if (error && typeof error.then === "function") {
779
+ await error;
780
+ continue;
781
+ }
782
+ throw error;
731
783
  }
732
- throw error;
733
784
  }
734
- }
735
- throw new Error("[slim-react] renderToString exceeded maximum retries");
785
+ throw new Error("[slim-react] renderToString exceeded maximum retries");
786
+ });
736
787
  }
737
788
 
738
789
  // src/slim-react/index.ts
739
790
  function useContext(context) {
740
- return context._currentValue;
791
+ return getContextValue(context);
741
792
  }
742
793
  var Suspense = SUSPENSE_TYPE;
743
794
  function isValidElement(obj) {
@@ -2,6 +2,7 @@ import {
2
2
  Fragment,
3
3
  jsx
4
4
  } from "../chunk-OS3V4CPN.js";
5
+ import "../chunk-EZUCZHGV.js";
5
6
  export {
6
7
  Fragment,
7
8
  jsx,
@@ -1,3 +1,11 @@
1
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
2
+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
3
+ }) : x)(function(x) {
4
+ if (typeof require !== "undefined")
5
+ return require.apply(this, arguments);
6
+ throw Error('Dynamic require of "' + x + '" is not supported');
7
+ });
8
+
1
9
  // src/ssr-render-worker.ts
2
10
  import { workerData, parentPort } from "node:worker_threads";
3
11
  import { pathToFileURL } from "node:url";
@@ -67,6 +75,40 @@ function createElement(type, props, ...children) {
67
75
  }
68
76
 
69
77
  // src/slim-react/renderContext.ts
78
+ var CONTEXT_STORE_KEY = "__slimReactContextStore";
79
+ var _g = globalThis;
80
+ if (!_g[CONTEXT_STORE_KEY]) {
81
+ try {
82
+ const { AsyncLocalStorage } = __require("node:async_hooks");
83
+ _g[CONTEXT_STORE_KEY] = new AsyncLocalStorage();
84
+ } catch {
85
+ _g[CONTEXT_STORE_KEY] = null;
86
+ }
87
+ }
88
+ var _contextStore = _g[CONTEXT_STORE_KEY];
89
+ function runWithContextStore(fn) {
90
+ return _contextStore ? _contextStore.run(/* @__PURE__ */ new Map(), fn) : fn();
91
+ }
92
+ function getContextValue(context) {
93
+ const store = _contextStore?.getStore();
94
+ if (store && store.has(context))
95
+ return store.get(context);
96
+ const c = context;
97
+ return "_defaultValue" in c ? c._defaultValue : c._currentValue;
98
+ }
99
+ function pushContextValue(context, value) {
100
+ const store = _contextStore?.getStore();
101
+ const c = context;
102
+ const prev = store && store.has(context) ? store.get(context) : "_defaultValue" in c ? c._defaultValue : c._currentValue;
103
+ if (store)
104
+ store.set(context, value);
105
+ return prev;
106
+ }
107
+ function popContextValue(context, prev) {
108
+ const store = _contextStore?.getStore();
109
+ if (store)
110
+ store.set(context, prev);
111
+ }
70
112
  var GLOBAL_KEY = "__slimReactRenderState";
71
113
  var EMPTY = { id: 0, overflow: "", bits: 0 };
72
114
  function s() {
@@ -479,7 +521,7 @@ function renderComponent(type, props, writer, isSvg) {
479
521
  }
480
522
  if (typeOf === REACT_CONSUMER) {
481
523
  const ctx2 = type._context;
482
- const value = ctx2?._currentValue;
524
+ const value = ctx2 ? getContextValue(ctx2) : void 0;
483
525
  const result2 = typeof props.children === "function" ? props.children(value) : null;
484
526
  const savedScope2 = pushComponentScope();
485
527
  const finish2 = () => popComponentScope(savedScope2);
@@ -495,18 +537,20 @@ function renderComponent(type, props, writer, isSvg) {
495
537
  let ctx;
496
538
  if (isProvider) {
497
539
  ctx = type._context ?? type;
498
- prevCtxValue = ctx._currentValue;
499
- ctx._currentValue = props.value;
540
+ prevCtxValue = pushContextValue(ctx, props.value);
500
541
  }
501
542
  const savedScope = pushComponentScope();
502
543
  if (isProvider && typeof type !== "function") {
503
544
  const finish2 = () => {
504
545
  popComponentScope(savedScope);
505
- ctx._currentValue = prevCtxValue;
546
+ popContextValue(ctx, prevCtxValue);
506
547
  };
507
548
  const r2 = renderChildren(props.children, writer, isSvg);
508
549
  if (r2 && typeof r2.then === "function") {
509
- return r2.then(finish2);
550
+ return r2.then(finish2, (e) => {
551
+ finish2();
552
+ throw e;
553
+ });
510
554
  }
511
555
  finish2();
512
556
  return;
@@ -527,26 +571,35 @@ function renderComponent(type, props, writer, isSvg) {
527
571
  } catch (e) {
528
572
  popComponentScope(savedScope);
529
573
  if (isProvider)
530
- ctx._currentValue = prevCtxValue;
574
+ popContextValue(ctx, prevCtxValue);
531
575
  throw e;
532
576
  }
533
577
  const finish = () => {
534
578
  popComponentScope(savedScope);
535
579
  if (isProvider)
536
- ctx._currentValue = prevCtxValue;
580
+ popContextValue(ctx, prevCtxValue);
537
581
  };
538
582
  if (result instanceof Promise) {
539
583
  return result.then((resolved) => {
540
584
  const r2 = renderNode(resolved, writer, isSvg);
541
585
  if (r2 && typeof r2.then === "function") {
542
- return r2.then(finish);
586
+ return r2.then(finish, (e) => {
587
+ finish();
588
+ throw e;
589
+ });
543
590
  }
544
591
  finish();
592
+ }, (e) => {
593
+ finish();
594
+ throw e;
545
595
  });
546
596
  }
547
597
  const r = renderNode(result, writer, isSvg);
548
598
  if (r && typeof r.then === "function") {
549
- return r.then(finish);
599
+ return r.then(finish, (e) => {
600
+ finish();
601
+ throw e;
602
+ });
550
603
  }
551
604
  finish();
552
605
  }
@@ -630,35 +683,37 @@ async function renderSuspense(props, writer, isSvg = false) {
630
683
  }
631
684
  writer.write("<!--/$-->");
632
685
  }
633
- async function renderToString(element) {
634
- for (let attempt = 0; attempt < MAX_SUSPENSE_RETRIES; attempt++) {
635
- resetRenderState();
636
- const chunks = [];
637
- const writer = {
638
- lastWasText: false,
639
- write(c) {
640
- chunks.push(c);
641
- this.lastWasText = false;
642
- },
643
- text(s2) {
644
- chunks.push(s2);
645
- this.lastWasText = true;
646
- }
647
- };
648
- try {
649
- const r = renderNode(element, writer);
650
- if (r && typeof r.then === "function")
651
- await r;
652
- return chunks.join("");
653
- } catch (error) {
654
- if (error && typeof error.then === "function") {
655
- await error;
656
- continue;
686
+ function renderToString(element) {
687
+ return runWithContextStore(async () => {
688
+ for (let attempt = 0; attempt < MAX_SUSPENSE_RETRIES; attempt++) {
689
+ resetRenderState();
690
+ const chunks = [];
691
+ const writer = {
692
+ lastWasText: false,
693
+ write(c) {
694
+ chunks.push(c);
695
+ this.lastWasText = false;
696
+ },
697
+ text(s2) {
698
+ chunks.push(s2);
699
+ this.lastWasText = true;
700
+ }
701
+ };
702
+ try {
703
+ const r = renderNode(element, writer);
704
+ if (r && typeof r.then === "function")
705
+ await r;
706
+ return chunks.join("");
707
+ } catch (error) {
708
+ if (error && typeof error.then === "function") {
709
+ await error;
710
+ continue;
711
+ }
712
+ throw error;
657
713
  }
658
- throw error;
659
714
  }
660
- }
661
- throw new Error("[slim-react] renderToString exceeded maximum retries");
715
+ throw new Error("[slim-react] renderToString exceeded maximum retries");
716
+ });
662
717
  }
663
718
 
664
719
  // src/ssr-render-worker.ts
package/dist/ssr-watch.js CHANGED
@@ -12,7 +12,7 @@ var __dirname = process.cwd();
12
12
  var packageDir = pathMod.dirname(fileURLToPath(import.meta.url));
13
13
  var clientScriptPath = pathMod.resolve(packageDir, "template.html");
14
14
  var loaderPath = existsSync(pathMod.resolve(packageDir, "loader.cjs")) ? pathMod.resolve(packageDir, "loader.cjs") : pathMod.resolve(packageDir, "loader.ts");
15
- var getConfigBase = (mode) => {
15
+ var getConfigBase = (mode, isServerBuild = false) => {
16
16
  const isDev = mode === "development";
17
17
  return {
18
18
  experiments: {
@@ -111,9 +111,12 @@ var getConfigBase = (mode) => {
111
111
  };
112
112
  };
113
113
  var buildCompilerConfig = (entry2, opts, includeHotPlugin) => {
114
- const Config = getConfigBase(opts.mode);
115
114
  const { base: base2 } = opts;
116
115
  const isDev = opts.mode === "development";
116
+ const isServerBuild = Boolean(
117
+ opts.output && typeof opts.output === "object" && (opts.output.library || String(opts.output.filename || "").includes("ssr"))
118
+ );
119
+ const Config = getConfigBase(opts.mode, isServerBuild);
117
120
  const localConfig = {
118
121
  ...Config,
119
122
  module: {
@@ -162,12 +165,9 @@ var buildCompilerConfig = (entry2, opts, includeHotPlugin) => {
162
165
  if (opts.moduleRules && opts.moduleRules.length > 0) {
163
166
  localConfig.module.rules.push(...opts.moduleRules);
164
167
  }
165
- const isServerBuild2 = Boolean(
166
- opts.output && typeof opts.output === "object" && (opts.output.library || String(opts.output.filename || "").includes("ssr"))
167
- );
168
168
  const slimReactIndex = pathMod.resolve(packageDir, "slim-react", "index.js");
169
169
  const slimReactJsx = pathMod.resolve(packageDir, "slim-react", "jsx-runtime.js");
170
- const resolveAliases = isServerBuild2 ? {
170
+ const resolveAliases = isServerBuild ? {
171
171
  // Route all React imports to slim-react for SSR.
172
172
  react: slimReactIndex,
173
173
  "react/jsx-runtime": slimReactJsx,
@@ -178,7 +178,17 @@ var buildCompilerConfig = (entry2, opts, includeHotPlugin) => {
178
178
  "@emotion/cache": path.resolve(process.cwd(), "node_modules", "@emotion", "cache"),
179
179
  "@emotion/styled": path.resolve(process.cwd(), "node_modules", "@emotion", "styled")
180
180
  } : void 0;
181
- const externals = isServerBuild2 ? [
181
+ const externals = isServerBuild ? [
182
+ // Node.js built-ins — must not be bundled; resolved by the runtime.
183
+ // Both the bare name and the node: prefix are listed because rspack
184
+ // may encounter either form depending on how the import is written.
185
+ "node:async_hooks",
186
+ "async_hooks",
187
+ "node:fs",
188
+ "node:path",
189
+ "node:os",
190
+ "node:stream",
191
+ "node:util",
182
192
  // react / react-dom are replaced by slim-react via alias above — not external.
183
193
  // emotion should be external on server builds to avoid client/browser code
184
194
  "@emotion/react",
@@ -199,9 +209,9 @@ var buildCompilerConfig = (entry2, opts, includeHotPlugin) => {
199
209
  extensions: [".tsx", ".ts", ".js", ".jsx"],
200
210
  alias: resolveAliases,
201
211
  // for server builds prefer the package "main"/"module" fields and avoid "browser" so we don't pick browser-specific entrypoints
202
- mainFields: isServerBuild2 ? ["main", "module"] : ["browser", "module", "main"]
212
+ mainFields: isServerBuild ? ["main", "module"] : ["browser", "module", "main"]
203
213
  };
204
- const optimization = !isServerBuild2 && !isDev ? {
214
+ const optimization = !isServerBuild && !isDev ? {
205
215
  moduleIds: "deterministic",
206
216
  splitChunks: {
207
217
  chunks: "all",
@@ -229,7 +239,7 @@ var buildCompilerConfig = (entry2, opts, includeHotPlugin) => {
229
239
  externals,
230
240
  ...optimization !== void 0 ? { optimization } : {},
231
241
  plugins: [
232
- new rspack.HtmlRspackPlugin({
242
+ !isServerBuild && new rspack.HtmlRspackPlugin({
233
243
  publicPath: base2 || "/",
234
244
  template: opts.htmlTemplate ? pathMod.resolve(process.cwd(), opts.htmlTemplate) : clientScriptPath,
235
245
  scriptLoading: "module",
@@ -237,12 +247,7 @@ var buildCompilerConfig = (entry2, opts, includeHotPlugin) => {
237
247
  inject: "head",
238
248
  minify: opts.mode === "production"
239
249
  }),
240
- // Add `async` to the emitted module script so DOMContentLoaded fires
241
- // as soon as HTML is parsed — without waiting for the bundle to execute.
242
- // `<script type="module" async>` is valid: it downloads in parallel and
243
- // executes without blocking DOMContentLoaded, while retaining module
244
- // semantics (strict mode, ES imports, etc.).
245
- {
250
+ !isServerBuild && {
246
251
  apply(compiler) {
247
252
  compiler.hooks.emit.tapAsync("HadarsAsyncModuleScript", (compilation, cb) => {
248
253
  const asset = compilation.assets["out.html"];
@@ -261,8 +266,8 @@ var buildCompilerConfig = (entry2, opts, includeHotPlugin) => {
261
266
  });
262
267
  }
263
268
  },
264
- isDev && !isServerBuild2 && new ReactRefreshPlugin(),
265
- includeHotPlugin && isDev && new rspack.HotModuleReplacementPlugin(),
269
+ isDev && !isServerBuild && new ReactRefreshPlugin(),
270
+ includeHotPlugin && isDev && !isServerBuild && new rspack.HotModuleReplacementPlugin(),
266
271
  ...extraPlugins
267
272
  ],
268
273
  ...localConfig,
@@ -277,7 +282,7 @@ var buildCompilerConfig = (entry2, opts, includeHotPlugin) => {
277
282
  // for client builds. SSR builds still need it for dynamic import() of exports.
278
283
  experiments: {
279
284
  ...localConfig.experiments || {},
280
- outputModule: isServerBuild2
285
+ outputModule: isServerBuild
281
286
  },
282
287
  // Prevent rspack from watching its own build output — without this the
283
288
  // SSR watcher writing .hadars/index.ssr.js triggers the client compiler
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hadars",
3
- "version": "0.1.22",
3
+ "version": "0.1.24",
4
4
  "description": "Minimal SSR framework for React — rspack, HMR, TypeScript, Bun/Node/Deno",
5
5
  "module": "./dist/index.js",
6
6
  "type": "module",
@@ -9,7 +9,8 @@ import type { SlimNode } from "./types";
9
9
  */
10
10
 
11
11
  export interface Context<T> {
12
- _currentValue: T;
12
+ _defaultValue: T;
13
+ _currentValue: T; // kept for external compat (real React contexts passed to useContext)
13
14
  Provider: ContextProvider<T>;
14
15
  Consumer: (props: { children: (value: T) => SlimNode }) => SlimNode;
15
16
  }
@@ -23,6 +24,7 @@ export type ContextProvider<T> = ((props: {
23
24
 
24
25
  export function createContext<T>(defaultValue: T): Context<T> {
25
26
  const context: Context<T> = {
27
+ _defaultValue: defaultValue,
26
28
  _currentValue: defaultValue,
27
29
  Provider: null!,
28
30
  Consumer: null!,
@@ -6,7 +6,7 @@
6
6
  * compatible libraries to work during server-side rendering.
7
7
  */
8
8
 
9
- import { makeId } from "./renderContext";
9
+ import { makeId, getContextValue } from "./renderContext";
10
10
 
11
11
  // ---- useState ----
12
12
  export function useState<T>(
@@ -115,9 +115,9 @@ export function use<T>(
115
115
  if (
116
116
  typeof usable === "object" &&
117
117
  usable !== null &&
118
- "_currentValue" in usable
118
+ ("_currentValue" in usable || "_defaultValue" in usable)
119
119
  ) {
120
- return (usable as { _currentValue: T })._currentValue;
120
+ return getContextValue<T>(usable as object);
121
121
  }
122
122
 
123
123
  // Promise – Suspense protocol
@@ -67,12 +67,10 @@ export {
67
67
  import { createContext } from "./context";
68
68
  export { createContext, type Context } from "./context";
69
69
 
70
- // Re-export useContext from hooks.ts? No – we have it in context.ts
71
- // Actually useContext reads from the context object, let's export a
72
- // single implementation that lives close to createContext:
70
+ import { getContextValue } from "./renderContext";
73
71
  import type { Context } from "./context";
74
72
  export function useContext<T>(context: Context<T>): T {
75
- return context._currentValue;
73
+ return getContextValue<T>(context);
76
74
  }
77
75
 
78
76
  // ---- Rendering ----