Skip to content

Commit a2bff0c

Browse files
split stuff out, fix treeshaking
1 parent 0c4ce5a commit a2bff0c

File tree

10 files changed

+209
-194
lines changed

10 files changed

+209
-194
lines changed

packages/svelte/src/internal/client/reactivity/cache.js

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -68,8 +68,8 @@ function create_remover(key) {
6868
});
6969
}
7070

71-
export class CacheObserver extends BaseCacheObserver {
72-
constructor() {
73-
super(client_cache);
74-
}
75-
}
71+
// export class CacheObserver extends BaseCacheObserver {
72+
// constructor() {
73+
// super(client_cache);
74+
// }
75+
// }

packages/svelte/src/internal/server/context.js

Lines changed: 3 additions & 131 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
/** @import { Transport } from '#shared' */
44
import { DEV } from 'esm-env';
55
import * as e from './errors.js';
6+
import { save_render_context } from './render-context.js';
67

78
/** @type {SSRContext | null} */
89
export var ssr_context = null;
@@ -115,139 +116,10 @@ function get_parent_context(ssr_context) {
115116
*/
116117
export async function save(promise) {
117118
var previous_context = ssr_context;
118-
var previous_sync_store = sync_store;
119-
var value = await promise;
119+
const restore_render_context = await save_render_context(promise);
120120

121121
return () => {
122122
ssr_context = previous_context;
123-
sync_store = previous_sync_store;
124-
return value;
123+
return restore_render_context();
125124
};
126125
}
127-
128-
/** @type {string | null} */
129-
export let hydratable_key = null;
130-
131-
/** @param {string | null} key */
132-
export function set_hydratable_key(key) {
133-
hydratable_key = key;
134-
}
135-
136-
/**
137-
* @template T
138-
* @overload
139-
* @param {string} key
140-
* @param {() => Promise<T>} fn
141-
* @param {{ transport?: Transport<T> }} [options]
142-
* @returns {Promise<T>}
143-
*/
144-
/**
145-
* @template T
146-
* @overload
147-
* @param {() => Promise<T>} fn
148-
* @param {{ transport?: Transport<T> }} [options]
149-
* @returns {Promise<T>}
150-
*/
151-
/**
152-
* @template T
153-
* @param {string | (() => Promise<T>)} key_or_fn
154-
* @param {(() => Promise<T>) | { transport?: Transport<T> }} [fn_or_options]
155-
* @param {{ transport?: Transport<T> }} [maybe_options]
156-
* @returns {Promise<T>}
157-
*/
158-
export function hydratable(key_or_fn, fn_or_options = {}, maybe_options = {}) {
159-
// TODO DRY out with #shared
160-
/** @type {string} */
161-
let key;
162-
/** @type {() => Promise<T>} */
163-
let fn;
164-
/** @type {{ transport?: Transport<T> }} */
165-
let options;
166-
167-
if (typeof key_or_fn === 'string') {
168-
key = key_or_fn;
169-
fn = /** @type {() => Promise<T>} */ (fn_or_options);
170-
options = /** @type {{ transport?: Transport<T> }} */ (maybe_options);
171-
} else {
172-
if (hydratable_key === null) {
173-
throw new Error(
174-
'TODO error: `hydratable` must be called synchronously within `cache` in order to omit the key'
175-
);
176-
} else {
177-
key = hydratable_key;
178-
}
179-
fn = /** @type {() => Promise<T>} */ (key_or_fn);
180-
options = /** @type {{ transport?: Transport<T> }} */ (fn_or_options);
181-
}
182-
const store = get_render_store();
183-
184-
if (store.hydratables.has(key)) {
185-
// TODO error
186-
throw new Error("can't have two hydratables with the same key");
187-
}
188-
189-
const result = fn();
190-
store.hydratables.set(key, { value: result, transport: options.transport });
191-
return Promise.resolve(result);
192-
}
193-
194-
/** @type {RenderContext | null} */
195-
export let sync_store = null;
196-
197-
/** @param {RenderContext | null} store */
198-
export function set_sync_store(store) {
199-
sync_store = store;
200-
}
201-
202-
/** @type {AsyncLocalStorage<RenderContext | null> | null} */
203-
let als = null;
204-
205-
import('node:async_hooks')
206-
.then((hooks) => (als = new hooks.AsyncLocalStorage()))
207-
.catch(() => {
208-
// can't use ALS but can still use manual context preservation
209-
return null;
210-
});
211-
212-
/** @returns {RenderContext | null} */
213-
function try_get_render_store() {
214-
return sync_store ?? als?.getStore() ?? null;
215-
}
216-
217-
/** @returns {RenderContext} */
218-
export function get_render_store() {
219-
const store = try_get_render_store();
220-
221-
if (!store) {
222-
// TODO make this a proper e.error
223-
let message = 'Could not get rendering context.';
224-
225-
if (als) {
226-
message += ' This is an internal error.';
227-
} else {
228-
message +=
229-
' In environments without `AsyncLocalStorage`, `hydratable` must be accessed synchronously, not after an `await`.' +
230-
' If it was accessed synchronously then this is an internal error.';
231-
}
232-
233-
throw new Error(message);
234-
}
235-
236-
return store;
237-
}
238-
239-
/**
240-
* @template T
241-
* @param {RenderContext} store
242-
* @param {() => Promise<T>} fn
243-
* @returns {Promise<T>}
244-
*/
245-
export function with_render_store(store, fn) {
246-
try {
247-
sync_store = store;
248-
const storage = als;
249-
return storage ? storage.run(store, fn) : fn();
250-
} finally {
251-
sync_store = null;
252-
}
253-
}
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
/** @import { Transport } from '#shared' */
2+
3+
import { get_render_context } from './render-context';
4+
5+
/** @type {string | null} */
6+
export let hydratable_key = null;
7+
8+
/** @param {string | null} key */
9+
export function set_hydratable_key(key) {
10+
hydratable_key = key;
11+
}
12+
13+
/**
14+
* @template T
15+
* @overload
16+
* @param {string} key
17+
* @param {() => Promise<T>} fn
18+
* @param {{ transport?: Transport<T> }} [options]
19+
* @returns {Promise<T>}
20+
*/
21+
/**
22+
* @template T
23+
* @overload
24+
* @param {() => Promise<T>} fn
25+
* @param {{ transport?: Transport<T> }} [options]
26+
* @returns {Promise<T>}
27+
*/
28+
/**
29+
* @template T
30+
* @param {string | (() => Promise<T>)} key_or_fn
31+
* @param {(() => Promise<T>) | { transport?: Transport<T> }} [fn_or_options]
32+
* @param {{ transport?: Transport<T> }} [maybe_options]
33+
* @returns {Promise<T>}
34+
*/
35+
export async function hydratable(key_or_fn, fn_or_options = {}, maybe_options = {}) {
36+
// TODO DRY out with #shared
37+
/** @type {string} */
38+
let key;
39+
/** @type {() => Promise<T>} */
40+
let fn;
41+
/** @type {{ transport?: Transport<T> }} */
42+
let options;
43+
44+
if (typeof key_or_fn === 'string') {
45+
key = key_or_fn;
46+
fn = /** @type {() => Promise<T>} */ (fn_or_options);
47+
options = /** @type {{ transport?: Transport<T> }} */ (maybe_options);
48+
} else {
49+
if (hydratable_key === null) {
50+
throw new Error(
51+
'TODO error: `hydratable` must be called synchronously within `cache` in order to omit the key'
52+
);
53+
} else {
54+
key = hydratable_key;
55+
}
56+
fn = /** @type {() => Promise<T>} */ (key_or_fn);
57+
options = /** @type {{ transport?: Transport<T> }} */ (fn_or_options);
58+
}
59+
const store = await get_render_context();
60+
61+
if (store.hydratables.has(key)) {
62+
// TODO error
63+
throw new Error("can't have two hydratables with the same key");
64+
}
65+
66+
const result = fn();
67+
store.hydratables.set(key, { value: result, transport: options.transport });
68+
return result;
69+
}
Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import { BaseCacheObserver } from '../../shared/cache-observer';
2-
import { get_render_store, set_hydratable_key } from '../context';
1+
import { set_hydratable_key } from '../hydratable';
2+
import { get_render_context } from '../render-context';
33

44
/**
55
* @template {(...args: any[]) => any} TFn
@@ -8,7 +8,7 @@ import { get_render_store, set_hydratable_key } from '../context';
88
* @returns {ReturnType<TFn>}
99
*/
1010
export function cache(key, fn) {
11-
const cache = get_render_store().cache;
11+
const cache = get_render_context().cache;
1212
const entry = cache.get(key);
1313
if (entry) {
1414
return /** @type {ReturnType<TFn>} */ (entry);
@@ -20,8 +20,9 @@ export function cache(key, fn) {
2020
return new_entry;
2121
}
2222

23-
export class CacheObserver extends BaseCacheObserver {
24-
constructor() {
25-
super(get_render_store().cache);
26-
}
27-
}
23+
// TODO, has to be async
24+
// export class CacheObserver extends BaseCacheObserver {
25+
// constructor() {
26+
// super(get_render_store().cache);
27+
// }
28+
// }

packages/svelte/src/internal/server/reactivity/fetcher.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/** @import { GetRequestInit, Resource } from '#shared' */
22
import { fetch_json } from '../../shared/utils.js';
3-
import { hydratable } from '../context.js';
3+
import { hydratable } from '../hydratable.js';
44
import { cache } from './cache';
55
import { resource } from './resource.js';
66

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
/** @import { AsyncLocalStorage } from 'node:async_hooks' */
2+
/** @import { RenderContext } from '#server' */
3+
4+
import { deferred } from '../shared/utils';
5+
6+
/** @type {Promise<void> | null} */
7+
let current_render = null;
8+
9+
/** @type {RenderContext | null} */
10+
let sync_context = null;
11+
12+
/**
13+
* @template T
14+
* @param {Promise<T>} promise
15+
* @returns {Promise<() => T>}
16+
*/
17+
export async function save_render_context(promise) {
18+
var previous_context = sync_context;
19+
var value = await promise;
20+
21+
return () => {
22+
sync_context = previous_context;
23+
return value;
24+
};
25+
}
26+
27+
/** @returns {RenderContext | null} */
28+
export function try_get_render_context() {
29+
if (sync_context !== null) {
30+
return sync_context;
31+
}
32+
return als?.getStore() ?? null;
33+
}
34+
35+
/** @returns {RenderContext} */
36+
export function get_render_context() {
37+
const store = try_get_render_context();
38+
39+
if (!store) {
40+
// TODO make this a proper e.error
41+
let message = 'Could not get rendering context.';
42+
43+
if (als) {
44+
message += ' You may have called `hydratable` or `cache` outside of the render lifecycle.';
45+
} else {
46+
message +=
47+
' In environments without `AsyncLocalStorage`, `hydratable` must be accessed synchronously, not after an `await`.' +
48+
' If it was accessed synchronously then this is an internal error or you may have called `hydratable` or `cache` outside of the render lifecycle.';
49+
}
50+
51+
throw new Error(message);
52+
}
53+
54+
return store;
55+
}
56+
57+
/**
58+
* @template T
59+
* @param {() => Promise<T>} fn
60+
* @returns {Promise<T>}
61+
*/
62+
export async function with_render_context(fn) {
63+
try {
64+
sync_context = {
65+
hydratables: new Map(),
66+
cache: new Map()
67+
};
68+
if (in_webcontainer()) {
69+
const { promise, resolve } = deferred();
70+
const previous_render = current_render;
71+
current_render = promise;
72+
await previous_render;
73+
return fn().finally(resolve);
74+
}
75+
return als ? als.run(sync_context, fn) : fn();
76+
} finally {
77+
if (!in_webcontainer()) {
78+
sync_context = null;
79+
}
80+
}
81+
}
82+
83+
/** @type {AsyncLocalStorage<RenderContext | null> | null} */
84+
let als = null;
85+
86+
export async function init_render_context() {
87+
if (als !== null) return;
88+
try {
89+
const { AsyncLocalStorage } = await import('node:async_hooks');
90+
als = new AsyncLocalStorage();
91+
} catch {}
92+
}
93+
94+
function in_webcontainer() {
95+
// eslint-disable-next-line n/prefer-global/process
96+
return !!globalThis.process?.versions?.webcontainer;
97+
}

0 commit comments

Comments
 (0)