Skip to content

Commit 60a68a3

Browse files
authored
Accumulate data (#6050)
* accumulate data * update write_types * simplify * changeset * update tests * add Omit logic * update all nodes that are downstream of changes * remove outdated test
1 parent 4ef2f05 commit 60a68a3

File tree

19 files changed

+105
-92
lines changed

19 files changed

+105
-92
lines changed

.changeset/heavy-phones-build.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@sveltejs/kit': patch
3+
---
4+
5+
Accumulate data from parent layouts into `export let data`

documentation/docs/03-routing.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -279,6 +279,18 @@ export function load() {
279279
280280
Unlike `+page.js`, `+layout.js` cannot export `prerender`, `hydrate` and `router`, as these are page-level options.
281281
282+
Data returned from a layout's `load` function is also available to all its child pages:
283+
284+
```svelte
285+
/// file: src/routes/settings/profile/+page.svelte
286+
<script>
287+
/** @type {import('./$types').PageData} */
288+
export let data;
289+
290+
console.log(data.sections); // [{ slug: 'profile', title: 'Profile' }, ...]
291+
</script>
292+
```
293+
282294
> Often, layout data is unchanged when navigating between pages. SvelteKit will intelligently re-run [`load`](/docs/load) functions when necessary.
283295
284296
#### +layout.server.js

packages/kit/src/core/sync/write_types.js

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -425,7 +425,7 @@ function process_node(ts, node, outdir, params, groups) {
425425
written_proxies.push(write(`${outdir}/proxy${basename}`, proxy.code));
426426
}
427427

428-
server_data = get_data_type(node.server, 'load', 'null', proxy);
428+
server_data = get_data_type(node.server, 'null', proxy);
429429
server_load = `Kit.ServerLoad<${params}, ${get_parent_type('LayoutServerData')}, OutputData>`;
430430

431431
if (proxy) {
@@ -449,36 +449,41 @@ function process_node(ts, node, outdir, params, groups) {
449449
server_data = 'null';
450450
}
451451

452+
const parent_type = get_parent_type('LayoutData');
453+
452454
if (node.shared) {
453455
const content = fs.readFileSync(node.shared, 'utf8');
454456
const proxy = tweak_types(ts, content, shared_names);
455457
if (proxy?.modified) {
456458
written_proxies.push(write(`${outdir}/proxy${path.basename(node.shared)}`, proxy.code));
457459
}
458460

459-
data = get_data_type(node.shared, 'load', server_data, proxy);
460-
load = `Kit.Load<${params}, ${server_data}, ${get_parent_type('LayoutData')}, OutputData>`;
461+
const type = get_data_type(node.shared, `${parent_type} & ${server_data}`, proxy);
462+
463+
data = `Omit<${parent_type}, keyof ${type}> & ${type}`;
464+
load = `Kit.Load<${params}, ${server_data}, ${parent_type}, OutputData>`;
465+
} else if (server_data === 'null') {
466+
data = parent_type;
461467
} else {
462-
data = server_data;
468+
data = `Omit<${parent_type}, keyof ${server_data}> & ${server_data}`;
463469
}
464470

465471
return { data, server_data, load, server_load, errors, written_proxies };
466472

467473
/**
468474
* @param {string} file_path
469-
* @param {string} method
470475
* @param {string} fallback
471476
* @param {Proxy} proxy
472477
*/
473-
function get_data_type(file_path, method, fallback, proxy) {
478+
function get_data_type(file_path, fallback, proxy) {
474479
if (proxy) {
475-
if (proxy.exports.includes(method)) {
480+
if (proxy.exports.includes('load')) {
476481
// If the file wasn't tweaked, we can use the return type of the original file.
477482
// The advantage is that type updates are reflected without saving.
478483
const from = proxy.modified
479484
? `./proxy${replace_ext_with_js(path.basename(file_path))}`
480485
: path_to_original(outdir, file_path);
481-
return `Kit.AwaitedProperties<Awaited<ReturnType<typeof import('${from}').${method}>>>`;
486+
return `Kit.AwaitedProperties<Awaited<ReturnType<typeof import('${from}').load>>>`;
482487
} else {
483488
return fallback;
484489
}

packages/kit/src/runtime/client/client.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -403,10 +403,10 @@ export function create_client({ target, base, trailing_slash }) {
403403
let data = {};
404404
let data_changed = false;
405405
for (let i = 0; i < filtered.length; i += 1) {
406-
Object.assign(data, filtered[i].data);
406+
data = { ...data, ...filtered[i].data };
407407
// Only set props if the node actually updated. This prevents needless rerenders.
408-
if (!current.branch.some((node) => node === filtered[i])) {
409-
result.props[`data_${i}`] = filtered[i].data;
408+
if (data_changed || !current.branch.some((node) => node === filtered[i])) {
409+
result.props[`data_${i}`] = data;
410410
data_changed = true;
411411
}
412412
}

packages/kit/src/runtime/server/page/render.js

Lines changed: 22 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -83,18 +83,31 @@ export async function render_response({
8383
navigating: writable(null),
8484
updated
8585
},
86-
/** @type {import('types').Page} */
87-
page: {
88-
error,
89-
params: /** @type {Record<string, any>} */ (event.params),
90-
routeId: event.routeId,
91-
status,
92-
url: state.prerendering ? new PrerenderingURL(event.url) : event.url,
93-
data: branch.reduce((acc, { data }) => (Object.assign(acc, data), acc), {})
94-
},
9586
components: await Promise.all(branch.map(({ node }) => node.component()))
9687
};
9788

89+
let data = {};
90+
91+
// props_n (instead of props[n]) makes it easy to avoid
92+
// unnecessary updates for layout components
93+
for (let i = 0; i < branch.length; i += 1) {
94+
data = { ...data, ...branch[i].data };
95+
props[`data_${i}`] = data;
96+
}
97+
98+
props.page = {
99+
error,
100+
params: /** @type {Record<string, any>} */ (event.params),
101+
routeId: event.routeId,
102+
status,
103+
url: state.prerendering ? new PrerenderingURL(event.url) : event.url,
104+
data
105+
};
106+
107+
if (validation_errors) {
108+
props.errors = validation_errors;
109+
}
110+
98111
// TODO remove this for 1.0
99112
/**
100113
* @param {string} property
@@ -112,16 +125,6 @@ export async function render_response({
112125
print_error('path', 'pathname');
113126
print_error('query', 'searchParams');
114127

115-
// props_n (instead of props[n]) makes it easy to avoid
116-
// unnecessary updates for layout components
117-
for (let i = 0; i < branch.length; i += 1) {
118-
props[`data_${i}`] = branch[i].data;
119-
}
120-
121-
if (validation_errors) {
122-
props.errors = validation_errors;
123-
}
124-
125128
rendered = options.root.render(props);
126129

127130
for (const { node } of branch) {
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
<a href="/load/accumulated/with-page-data">with page data</a>
2+
<a href="/load/accumulated/without-page-data">without page data</a>
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
/** @type {import('./$types').PageLoad} */
2+
export function load() {
3+
return {
4+
pagedata: 'pagedata'
5+
};
6+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<script>
2+
/** @type {import('./$types').PageData} */
3+
export let data;
4+
</script>
5+
6+
<h1>foo.bar: {data.foo.bar}</h1>
7+
<h2>pagedata: {data.pagedata}</h2>
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
<script>
2+
/** @type {import('./$types').PageData} */
3+
export let data;
4+
</script>
5+
6+
<h1>foo.bar: {data.foo.bar}</h1>

packages/kit/test/apps/basics/src/routes/load/layout-props/+layout.js

Lines changed: 0 additions & 7 deletions
This file was deleted.

0 commit comments

Comments
 (0)