@sveltejs/kit 1.7.2 → 1.8.0

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.
@@ -1,5 +1,6 @@
1
1
  import { disable_search, make_trackable } from '../../../utils/url.js';
2
2
  import { unwrap_promises } from '../../../utils/promises.js';
3
+ import { DEV } from 'esm-env';
3
4
 
4
5
  /**
5
6
  * Calls the user's server `load` function.
@@ -14,6 +15,8 @@ import { unwrap_promises } from '../../../utils/promises.js';
14
15
  export async function load_server_data({ event, state, node, parent }) {
15
16
  if (!node?.server) return null;
16
17
 
18
+ let done = false;
19
+
17
20
  const uses = {
18
21
  dependencies: new Set(),
19
22
  params: new Set(),
@@ -23,6 +26,12 @@ export async function load_server_data({ event, state, node, parent }) {
23
26
  };
24
27
 
25
28
  const url = make_trackable(event.url, () => {
29
+ if (DEV && done && !uses.url) {
30
+ console.warn(
31
+ `${node.server_id}: Accessing URL properties in a promise handler after \`load(...)\` has returned will not cause the function to re-run when the URL changes`
32
+ );
33
+ }
34
+
26
35
  uses.url = true;
27
36
  });
28
37
 
@@ -34,6 +43,13 @@ export async function load_server_data({ event, state, node, parent }) {
34
43
  ...event,
35
44
  fetch: (info, init) => {
36
45
  const url = new URL(info instanceof Request ? info.url : info, event.url);
46
+
47
+ if (DEV && done && !uses.dependencies.has(url.href)) {
48
+ console.warn(
49
+ `${node.server_id}: Calling \`event.fetch(...)\` in a promise handler after \`load(...)\` has returned will not cause the function to re-run when the dependency is invalidated`
50
+ );
51
+ }
52
+
37
53
  uses.dependencies.add(url.href);
38
54
 
39
55
  return event.fetch(info, init);
@@ -42,25 +58,54 @@ export async function load_server_data({ event, state, node, parent }) {
42
58
  depends: (...deps) => {
43
59
  for (const dep of deps) {
44
60
  const { href } = new URL(dep, event.url);
61
+
62
+ if (DEV && done && !uses.dependencies.has(href)) {
63
+ console.warn(
64
+ `${node.server_id}: Calling \`depends(...)\` in a promise handler after \`load(...)\` has returned will not cause the function to re-run when the dependency is invalidated`
65
+ );
66
+ }
67
+
45
68
  uses.dependencies.add(href);
46
69
  }
47
70
  },
48
71
  params: new Proxy(event.params, {
49
72
  get: (target, key) => {
73
+ if (DEV && done && typeof key === 'string' && !uses.params.has(key)) {
74
+ console.warn(
75
+ `${node.server_id}: Accessing \`params.${String(
76
+ key
77
+ )}\` in a promise handler after \`load(...)\` has returned will not cause the function to re-run when the param changes`
78
+ );
79
+ }
80
+
50
81
  uses.params.add(key);
51
82
  return target[/** @type {string} */ (key)];
52
83
  }
53
84
  }),
54
85
  parent: async () => {
86
+ if (DEV && done && !uses.parent) {
87
+ console.warn(
88
+ `${node.server_id}: Calling \`parent(...)\` in a promise handler after \`load(...)\` has returned will not cause the function to re-run when parent data changes`
89
+ );
90
+ }
91
+
55
92
  uses.parent = true;
56
93
  return parent();
57
94
  },
58
- route: {
59
- get id() {
95
+ route: new Proxy(event.route, {
96
+ get: (target, key) => {
97
+ if (DEV && done && typeof key === 'string' && !uses.route) {
98
+ console.warn(
99
+ `${node.server_id}: Accessing \`route.${String(
100
+ key
101
+ )}\` in a promise handler after \`load(...)\` has returned will not cause the function to re-run when the route changes`
102
+ );
103
+ }
104
+
60
105
  uses.route = true;
61
- return event.route.id;
106
+ return target[/** @type {'id'} */ (key)];
62
107
  }
63
- },
108
+ }),
64
109
  url
65
110
  });
66
111
 
@@ -69,6 +114,8 @@ export async function load_server_data({ event, state, node, parent }) {
69
114
  validate_load_response(data, /** @type {string} */ (event.route.id));
70
115
  }
71
116
 
117
+ done = true;
118
+
72
119
  return {
73
120
  type: 'data',
74
121
  data,
@@ -89,7 +136,7 @@ export async function load_server_data({ event, state, node, parent }) {
89
136
  * state: import('types').SSRState;
90
137
  * csr: boolean;
91
138
  * }} opts
92
- * @returns {Promise<Record<string, any> | null>}
139
+ * @returns {Promise<Record<string, any | Promise<any>> | null>}
93
140
  */
94
141
  export async function load_data({
95
142
  event,
@@ -119,7 +166,10 @@ export async function load_data({
119
166
  });
120
167
 
121
168
  const data = result ? await unwrap_promises(result) : null;
122
- validate_load_response(data, /** @type {string} */ (event.route.id));
169
+ if (__SVELTEKIT_DEV__) {
170
+ validate_load_response(data, /** @type {string} */ (event.route.id));
171
+ }
172
+
123
173
  return data;
124
174
  }
125
175
 
@@ -7,9 +7,10 @@ import { serialize_data } from './serialize_data.js';
7
7
  import { s } from '../../../utils/misc.js';
8
8
  import { Csp } from './csp.js';
9
9
  import { uneval_action_response } from './actions.js';
10
- import { clarify_devalue_error } from '../utils.js';
11
- import { version, public_env } from '../../shared.js';
10
+ import { clarify_devalue_error, stringify_uses, handle_error_and_jsonify } from '../utils.js';
11
+ import { public_env } from '../../shared-server.js';
12
12
  import { text } from '../../../exports/index.js';
13
+ import { create_async_iterator } from '../../../utils/streaming.js';
13
14
 
14
15
  // TODO rename this function/module
15
16
 
@@ -57,11 +58,11 @@ export async function render_response({
57
58
  }
58
59
  }
59
60
 
60
- const { entry } = manifest._;
61
+ const { client } = manifest._;
61
62
 
62
- const stylesheets = new Set(entry.stylesheets);
63
- const modulepreloads = new Set(entry.imports);
64
- const fonts = new Set(manifest._.entry.fonts);
63
+ const modulepreloads = new Set([...client.start.imports, ...client.app.imports]);
64
+ const stylesheets = new Set(client.app.stylesheets);
65
+ const fonts = new Set(client.app.fonts);
65
66
 
66
67
  /** @type {Set<string>} */
67
68
  const link_header_preloads = new Set();
@@ -141,17 +142,9 @@ export async function render_response({
141
142
  }
142
143
 
143
144
  for (const { node } of branch) {
144
- if (node.imports) {
145
- node.imports.forEach((url) => modulepreloads.add(url));
146
- }
147
-
148
- if (node.stylesheets) {
149
- node.stylesheets.forEach((url) => stylesheets.add(url));
150
- }
151
-
152
- if (node.fonts) {
153
- node.fonts.forEach((url) => fonts.add(url));
154
- }
145
+ for (const url of node.imports) modulepreloads.add(url);
146
+ for (const url of node.stylesheets) stylesheets.add(url);
147
+ for (const url of node.fonts) fonts.add(url);
155
148
 
156
149
  if (node.inline_styles) {
157
150
  Object.entries(await node.inline_styles()).forEach(([k, v]) => inline_styles.set(k, v));
@@ -161,34 +154,42 @@ export async function render_response({
161
154
  rendered = { head: '', html: '', css: { code: '', map: null } };
162
155
  }
163
156
 
164
- let head = '';
165
- let body = rendered.html;
166
-
167
- const csp = new Csp(options.csp, {
168
- prerender: !!state.prerendering
169
- });
170
-
171
- const target = hash(body);
172
-
173
157
  /**
174
158
  * The prefix to use for static assets. Replaces `%sveltekit.assets%` in the template
175
159
  * @type {string}
176
160
  */
177
161
  let resolved_assets;
178
162
 
163
+ /**
164
+ * An expression that will evaluate in the client to determine the resolved asset path
165
+ */
166
+ let asset_expression;
167
+
179
168
  if (assets) {
180
169
  // if an asset path is specified, use it
181
170
  resolved_assets = assets;
171
+ asset_expression = s(assets);
182
172
  } else if (state.prerendering?.fallback) {
183
173
  // if we're creating a fallback page, asset paths need to be root-relative
184
174
  resolved_assets = base;
175
+ asset_expression = s(base);
185
176
  } else {
186
177
  // otherwise we want asset paths to be relative to the page, so that they
187
178
  // will work in odd contexts like IPFS, the internet archive, and so on
188
179
  const segments = event.url.pathname.slice(base.length).split('/').slice(2);
189
180
  resolved_assets = segments.length > 0 ? segments.map(() => '..').join('/') : '.';
181
+ asset_expression = `new URL(${s(
182
+ resolved_assets
183
+ )}, location.href).pathname.replace(/^\\\/$/, '')`;
190
184
  }
191
185
 
186
+ let head = '';
187
+ let body = rendered.html;
188
+
189
+ const csp = new Csp(options.csp, {
190
+ prerender: !!state.prerendering
191
+ });
192
+
192
193
  /** @param {string} path */
193
194
  const prefixed = (path) => {
194
195
  if (path.startsWith('/')) {
@@ -200,48 +201,6 @@ export async function render_response({
200
201
  return `${resolved_assets}/${path}`;
201
202
  };
202
203
 
203
- const serialized = { data: '', form: 'null', error: 'null' };
204
-
205
- try {
206
- serialized.data = `[${branch
207
- .map(({ server_data }) => {
208
- if (server_data?.type === 'data') {
209
- const data = devalue.uneval(server_data.data);
210
-
211
- const uses = [];
212
- if (server_data.uses.dependencies.size > 0) {
213
- uses.push(`dependencies:${s(Array.from(server_data.uses.dependencies))}`);
214
- }
215
-
216
- if (server_data.uses.params.size > 0) {
217
- uses.push(`params:${s(Array.from(server_data.uses.params))}`);
218
- }
219
-
220
- if (server_data.uses.parent) uses.push(`parent:1`);
221
- if (server_data.uses.route) uses.push(`route:1`);
222
- if (server_data.uses.url) uses.push(`url:1`);
223
-
224
- return `{type:"data",data:${data},uses:{${uses.join(',')}}${
225
- server_data.slash ? `,slash:${s(server_data.slash)}` : ''
226
- }}`;
227
- }
228
-
229
- return s(server_data);
230
- })
231
- .join(',')}]`;
232
- } catch (e) {
233
- const error = /** @type {any} */ (e);
234
- throw new Error(clarify_devalue_error(event, error));
235
- }
236
-
237
- if (form_value) {
238
- serialized.form = uneval_action_response(form_value, /** @type {string} */ (event.route.id));
239
- }
240
-
241
- if (error) {
242
- serialized.error = devalue.uneval(error);
243
- }
244
-
245
204
  if (inline_styles.size > 0) {
246
205
  const content = Array.from(inline_styles.values()).join('\n');
247
206
 
@@ -289,18 +248,86 @@ export async function render_response({
289
248
  }
290
249
  }
291
250
 
251
+ const global = `__sveltekit_${options.version_hash}`;
252
+
253
+ const { data, chunks } = get_data(
254
+ event,
255
+ options,
256
+ branch.map((b) => b.server_data),
257
+ global
258
+ );
259
+
260
+ if (page_config.ssr && page_config.csr) {
261
+ body += `\n\t\t\t${fetched
262
+ .map((item) =>
263
+ serialize_data(item, resolve_opts.filterSerializedResponseHeaders, !!state.prerendering)
264
+ )
265
+ .join('\n\t\t\t')}`;
266
+ }
267
+
292
268
  if (page_config.csr) {
293
- const opts = [
294
- `assets: ${s(assets)}`,
269
+ const included_modulepreloads = Array.from(modulepreloads, (dep) => prefixed(dep)).filter(
270
+ (path) => resolve_opts.preload({ type: 'js', path })
271
+ );
272
+
273
+ for (const path of included_modulepreloads) {
274
+ // we use modulepreload with the Link header for Chrome, along with
275
+ // <link rel="preload"> for Safari. This results in the fastest loading in
276
+ // the most used browsers, with no double-loading. Note that we need to use
277
+ // .mjs extensions for `preload` to behave like `modulepreload` in Chrome
278
+ link_header_preloads.add(`<${encodeURI(path)}>; rel="modulepreload"; nopush`);
279
+ head += `\n\t\t<link rel="preload" as="script" crossorigin="anonymous" href="${path}">`;
280
+ }
281
+
282
+ const blocks = [];
283
+
284
+ const properties = [
295
285
  `env: ${s(public_env)}`,
296
- `target: document.querySelector('[data-sveltekit-hydrate="${target}"]').parentNode`,
297
- `version: ${s(version)}`
286
+ `assets: ${asset_expression}`,
287
+ `element: document.currentScript.parentElement`
298
288
  ];
299
289
 
290
+ if (chunks) {
291
+ blocks.push(`const deferred = new Map();`);
292
+
293
+ properties.push(`defer: (id) => new Promise((fulfil, reject) => {
294
+ deferred.set(id, { fulfil, reject });
295
+ })`);
296
+
297
+ properties.push(`resolve: ({ id, data, error }) => {
298
+ const { fulfil, reject } = deferred.get(id);
299
+ deferred.delete(id);
300
+
301
+ if (error) reject(error);
302
+ else fulfil(data);
303
+ }`);
304
+ }
305
+
306
+ blocks.push(`${global} = {
307
+ ${properties.join(',\n\t\t\t\t\t\t')}
308
+ };`);
309
+
310
+ const args = [`app`, `${global}.element`];
311
+
300
312
  if (page_config.ssr) {
313
+ const serialized = { form: 'null', error: 'null' };
314
+
315
+ blocks.push(`const data = ${data};`);
316
+
317
+ if (form_value) {
318
+ serialized.form = uneval_action_response(
319
+ form_value,
320
+ /** @type {string} */ (event.route.id)
321
+ );
322
+ }
323
+
324
+ if (error) {
325
+ serialized.error = devalue.uneval(error);
326
+ }
327
+
301
328
  const hydrate = [
302
329
  `node_ids: [${branch.map(({ node }) => node.index).join(', ')}]`,
303
- `data: ${serialized.data}`,
330
+ `data`,
304
331
  `form: ${serialized.form}`,
305
332
  `error: ${serialized.error}`
306
333
  ];
@@ -313,67 +340,44 @@ export async function render_response({
313
340
  hydrate.push(`params: ${devalue.uneval(event.params)}`, `route: ${s(event.route)}`);
314
341
  }
315
342
 
316
- opts.push(`hydrate: {\n\t\t\t\t\t${hydrate.join(',\n\t\t\t\t\t')}\n\t\t\t\t}`);
343
+ args.push(`{\n\t\t\t\t\t\t\t${hydrate.join(',\n\t\t\t\t\t\t\t')}\n\t\t\t\t\t\t}`);
317
344
  }
318
345
 
319
- // prettier-ignore
320
- const init_app = `
321
- import { start } from ${s(prefixed(entry.file))};
322
-
323
- start({
324
- ${opts.join(',\n\t\t\t\t')}
325
- });
326
- `;
327
-
328
- for (const dep of modulepreloads) {
329
- const path = prefixed(dep);
330
-
331
- if (resolve_opts.preload({ type: 'js', path })) {
332
- link_header_preloads.add(`<${encodeURI(path)}>; rel="modulepreload"; nopush`);
333
- if (state.prerendering) {
334
- head += `\n\t\t<link rel="modulepreload" href="${path}">`;
335
- }
336
- }
346
+ blocks.push(`Promise.all([
347
+ import(${s(prefixed(client.start.file))}),
348
+ import(${s(prefixed(client.app.file))})
349
+ ]).then(([kit, app]) => {
350
+ kit.start(${args.join(', ')});
351
+ });`);
352
+
353
+ if (options.service_worker) {
354
+ const opts = __SVELTEKIT_DEV__ ? `, { type: 'module' }` : '';
355
+
356
+ // we use an anonymous function instead of an arrow function to support
357
+ // older browsers (https://github.com/sveltejs/kit/pull/5417)
358
+ blocks.push(`if ('serviceWorker' in navigator) {
359
+ addEventListener('load', function () {
360
+ navigator.serviceWorker.register('${prefixed('service-worker.js')}'${opts});
361
+ });
362
+ }`);
337
363
  }
338
364
 
339
- const attributes = ['type="module"', `data-sveltekit-hydrate="${target}"`];
340
-
365
+ const init_app = `
366
+ {
367
+ ${blocks.join('\n\n\t\t\t\t\t')}
368
+ }
369
+ `;
341
370
  csp.add_script(init_app);
342
371
 
343
- if (csp.script_needs_nonce) {
344
- attributes.push(`nonce="${csp.nonce}"`);
345
- }
346
-
347
- body += `\n\t\t<script ${attributes.join(' ')}>${init_app}</script>`;
372
+ body += `\n\t\t\t<script${
373
+ csp.script_needs_nonce ? ` nonce="${csp.nonce}"` : ''
374
+ }>${init_app}</script>\n\t\t`;
348
375
  }
349
376
 
350
- if (page_config.ssr && page_config.csr) {
351
- body += `\n\t${fetched
352
- .map((item) =>
353
- serialize_data(item, resolve_opts.filterSerializedResponseHeaders, !!state.prerendering)
354
- )
355
- .join('\n\t')}`;
356
- }
357
-
358
- if (options.service_worker) {
359
- const opts = __SVELTEKIT_DEV__ ? `, { type: 'module' }` : '';
360
-
361
- // we use an anonymous function instead of an arrow function to support
362
- // older browsers (https://github.com/sveltejs/kit/pull/5417)
363
- const init_service_worker = `
364
- if ('serviceWorker' in navigator) {
365
- addEventListener('load', function () {
366
- navigator.serviceWorker.register('${prefixed('service-worker.js')}'${opts});
367
- });
368
- }
369
- `;
370
-
371
- // always include service worker unless it's turned off explicitly
372
- csp.add_script(init_service_worker);
373
-
374
- head += `
375
- <script${csp.script_needs_nonce ? ` nonce="${csp.nonce}"` : ''}>${init_service_worker}</script>`;
376
- }
377
+ const headers = new Headers({
378
+ 'x-sveltekit-page': 'true',
379
+ 'content-type': 'text/html'
380
+ });
377
381
 
378
382
  if (state.prerendering) {
379
383
  // TODO read headers set with setHeaders and convert into http-equiv where possible
@@ -391,6 +395,19 @@ export async function render_response({
391
395
  if (http_equiv.length > 0) {
392
396
  head = http_equiv.join('\n') + head;
393
397
  }
398
+ } else {
399
+ const csp_header = csp.csp_provider.get_header();
400
+ if (csp_header) {
401
+ headers.set('content-security-policy', csp_header);
402
+ }
403
+ const report_only_header = csp.report_only_provider.get_header();
404
+ if (report_only_header) {
405
+ headers.set('content-security-policy-report-only', report_only_header);
406
+ }
407
+
408
+ if (link_header_preloads.size) {
409
+ headers.set('link', Array.from(link_header_preloads).join(', '));
410
+ }
394
411
  }
395
412
 
396
413
  // add the content after the script/css links so the link elements are parsed first
@@ -411,39 +428,122 @@ export async function render_response({
411
428
  done: true
412
429
  })) || '';
413
430
 
414
- if (DEV && page_config.csr) {
415
- if (transformed.split('<!--').length < html.split('<!--').length) {
416
- // the \u001B stuff is ANSI codes, so that we don't need to add a library to the runtime
417
- // https://svelte.dev/repl/1b3f49696f0c44c881c34587f2537aa2
418
- console.warn(
419
- "\u001B[1m\u001B[31mRemoving comments in transformPageChunk can break Svelte's hydration\u001B[39m\u001B[22m"
420
- );
431
+ if (!chunks) {
432
+ headers.set('etag', `"${hash(transformed)}"`);
433
+ }
434
+
435
+ if (DEV) {
436
+ if (page_config.csr) {
437
+ if (transformed.split('<!--').length < html.split('<!--').length) {
438
+ // the \u001B stuff is ANSI codes, so that we don't need to add a library to the runtime
439
+ // https://svelte.dev/repl/1b3f49696f0c44c881c34587f2537aa2
440
+ console.warn(
441
+ "\u001B[1m\u001B[31mRemoving comments in transformPageChunk can break Svelte's hydration\u001B[39m\u001B[22m"
442
+ );
443
+ }
444
+ } else {
445
+ if (chunks) {
446
+ console.warn(
447
+ '\u001B[1m\u001B[31mReturning promises from server `load` functions will only work if `csr === true`\u001B[39m\u001B[22m'
448
+ );
449
+ }
421
450
  }
422
451
  }
423
452
 
424
- const headers = new Headers({
425
- 'x-sveltekit-page': 'true',
426
- 'content-type': 'text/html',
427
- etag: `"${hash(transformed)}"`
428
- });
453
+ return !chunks
454
+ ? text(transformed, {
455
+ status,
456
+ headers
457
+ })
458
+ : new Response(
459
+ new ReadableStream({
460
+ async start(controller) {
461
+ controller.enqueue(transformed);
462
+ for await (const chunk of chunks) {
463
+ controller.enqueue(chunk);
464
+ }
465
+ controller.close();
466
+ }
467
+ }),
468
+ {
469
+ headers: {
470
+ 'content-type': 'text/html'
471
+ }
472
+ }
473
+ );
474
+ }
429
475
 
430
- if (!state.prerendering) {
431
- const csp_header = csp.csp_provider.get_header();
432
- if (csp_header) {
433
- headers.set('content-security-policy', csp_header);
434
- }
435
- const report_only_header = csp.report_only_provider.get_header();
436
- if (report_only_header) {
437
- headers.set('content-security-policy-report-only', report_only_header);
438
- }
476
+ /**
477
+ * If the serialized data contains promises, `chunks` will be an
478
+ * async iterable containing their resolutions
479
+ * @param {import('types').RequestEvent} event
480
+ * @param {import('types').SSROptions} options
481
+ * @param {Array<import('types').ServerDataNode | null>} nodes
482
+ * @param {string} global
483
+ * @returns {{ data: string, chunks: AsyncIterable<string> | null }}
484
+ */
485
+ function get_data(event, options, nodes, global) {
486
+ let promise_id = 1;
487
+ let count = 0;
488
+
489
+ const { iterator, push, done } = create_async_iterator();
490
+
491
+ /** @param {any} thing */
492
+ function replacer(thing) {
493
+ if (typeof thing?.then === 'function') {
494
+ const id = promise_id++;
495
+ count += 1;
496
+
497
+ thing
498
+ .then(/** @param {any} data */ (data) => ({ data }))
499
+ .catch(
500
+ /** @param {any} error */ async (error) => ({
501
+ error: await handle_error_and_jsonify(event, options, error)
502
+ })
503
+ )
504
+ .then(
505
+ /**
506
+ * @param {{data: any; error: any}} result
507
+ */
508
+ async ({ data, error }) => {
509
+ count -= 1;
510
+
511
+ let str;
512
+ try {
513
+ str = devalue.uneval({ id, data, error }, replacer);
514
+ } catch (e) {
515
+ error = await handle_error_and_jsonify(
516
+ event,
517
+ options,
518
+ new Error(`Failed to serialize promise while rendering ${event.route.id}`)
519
+ );
520
+ data = undefined;
521
+ str = devalue.uneval({ id, data, error }, replacer);
522
+ }
523
+
524
+ push(`\n<script>${global}.resolve(${str})</script>`);
525
+ if (count === 0) done();
526
+ }
527
+ );
439
528
 
440
- if (link_header_preloads.size) {
441
- headers.set('link', Array.from(link_header_preloads).join(', '));
529
+ return `${global}.defer(${id})`;
442
530
  }
443
531
  }
444
532
 
445
- return text(transformed, {
446
- status,
447
- headers
448
- });
533
+ try {
534
+ const strings = nodes.map((node) => {
535
+ if (!node) return 'null';
536
+
537
+ return `{"type":"data","data":${devalue.uneval(node.data, replacer)},${stringify_uses(node)}${
538
+ node.slash ? `,"slash":${JSON.stringify(node.slash)}` : ''
539
+ }}`;
540
+ });
541
+
542
+ return {
543
+ data: `[${strings.join(',')}]`,
544
+ chunks: count > 0 ? iterator : null
545
+ };
546
+ } catch (e) {
547
+ throw new Error(clarify_devalue_error(event, /** @type {any} */ (e)));
548
+ }
449
549
  }
@@ -1,5 +1,5 @@
1
1
  import { CookieSerializeOptions } from 'cookie';
2
- import { SSRNode, CspDirectives } from 'types';
2
+ import { SSRNode, CspDirectives, ServerDataNode } from 'types';
3
3
 
4
4
  export interface Fetched {
5
5
  url: string;
@@ -13,7 +13,7 @@ export interface Fetched {
13
13
  export type Loaded = {
14
14
  node: SSRNode;
15
15
  data: Record<string, any> | null;
16
- server_data: any;
16
+ server_data: ServerDataNode | null;
17
17
  };
18
18
 
19
19
  type CspMode = 'hash' | 'nonce' | 'auto';