Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: sveltejs/kit
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: @sveltejs/kit@1.0.0-next.422
Choose a base ref
...
head repository: sveltejs/kit
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: @sveltejs/kit@1.0.0-next.423
Choose a head ref
  • 2 commits
  • 22 files changed
  • 3 contributors

Commits on Aug 18, 2022

  1. 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
    Rich-Harris authored Aug 18, 2022
    Copy the full SHA
    60a68a3 View commit details
  2. Version Packages (next) (#6055)

    Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
    github-actions[bot] and github-actions[bot] authored Aug 18, 2022

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    eecbbfc View commit details
Showing with 113 additions and 93 deletions.
  1. +5 −0 .changeset/heavy-phones-build.md
  2. +1 −0 .changeset/pre.json
  3. +12 −0 documentation/docs/03-routing.md
  4. +6 −0 packages/kit/CHANGELOG.md
  5. +1 −1 packages/kit/package.json
  6. +13 −8 packages/kit/src/core/sync/write_types.js
  7. +3 −3 packages/kit/src/runtime/client/client.js
  8. +22 −19 packages/kit/src/runtime/server/page/render.js
  9. +2 −0 packages/kit/test/apps/basics/src/routes/load/accumulated/+page.svelte
  10. +6 −0 packages/kit/test/apps/basics/src/routes/load/accumulated/with-page-data/+page.js
  11. +7 −0 packages/kit/test/apps/basics/src/routes/load/accumulated/with-page-data/+page.svelte
  12. +6 −0 packages/kit/test/apps/basics/src/routes/load/accumulated/without-page-data/+page.svelte
  13. +0 −7 packages/kit/test/apps/basics/src/routes/load/layout-props/+layout.js
  14. +0 −10 packages/kit/test/apps/basics/src/routes/load/layout-props/+layout.svelte
  15. +0 −2 packages/kit/test/apps/basics/src/routes/load/layout-props/a/+page.svelte
  16. +0 −2 packages/kit/test/apps/basics/src/routes/load/layout-props/b/+page.svelte
  17. +0 −10 packages/kit/test/apps/basics/src/routes/load/props/+layout.svelte
  18. +0 −7 packages/kit/test/apps/basics/src/routes/load/props/about/+page.svelte
  19. +1 −1 packages/kit/test/apps/basics/src/routes/shadowed/parent/+layout.svelte
  20. +1 −1 packages/kit/test/apps/basics/src/routes/shadowed/parent/+page.svelte
  21. +0 −7 packages/kit/test/apps/basics/test/client.test.js
  22. +27 −15 packages/kit/test/apps/basics/test/test.js
5 changes: 5 additions & 0 deletions .changeset/heavy-phones-build.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@sveltejs/kit': patch
---

Accumulate data from parent layouts into `export let data`
1 change: 1 addition & 0 deletions .changeset/pre.json
Original file line number Diff line number Diff line change
@@ -392,6 +392,7 @@
"heavy-lamps-explode",
"heavy-moons-admire",
"heavy-papayas-smile",
"heavy-phones-build",
"heavy-turkeys-report",
"heavy-ways-agree",
"hip-ears-reflect",
12 changes: 12 additions & 0 deletions documentation/docs/03-routing.md
Original file line number Diff line number Diff line change
@@ -279,6 +279,18 @@ export function load() {
Unlike `+page.js`, `+layout.js` cannot export `prerender`, `hydrate` and `router`, as these are page-level options.
Data returned from a layout's `load` function is also available to all its child pages:
```svelte
/// file: src/routes/settings/profile/+page.svelte
<script>
/** @type {import('./$types').PageData} */
export let data;

console.log(data.sections); // [{ slug: 'profile', title: 'Profile' }, ...]
</script>
```
> Often, layout data is unchanged when navigating between pages. SvelteKit will intelligently re-run [`load`](/docs/load) functions when necessary.
#### +layout.server.js
6 changes: 6 additions & 0 deletions packages/kit/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# @sveltejs/kit

## 1.0.0-next.423

### Patch Changes

- Accumulate data from parent layouts into `export let data` ([#6050](https://github.com/sveltejs/kit/pull/6050))

## 1.0.0-next.422

### Patch Changes
2 changes: 1 addition & 1 deletion packages/kit/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@sveltejs/kit",
"version": "1.0.0-next.422",
"version": "1.0.0-next.423",
"repository": {
"type": "git",
"url": "https://github.com/sveltejs/kit",
21 changes: 13 additions & 8 deletions packages/kit/src/core/sync/write_types.js
Original file line number Diff line number Diff line change
@@ -425,7 +425,7 @@ function process_node(ts, node, outdir, params, groups) {
written_proxies.push(write(`${outdir}/proxy${basename}`, proxy.code));
}

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

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

const parent_type = get_parent_type('LayoutData');

if (node.shared) {
const content = fs.readFileSync(node.shared, 'utf8');
const proxy = tweak_types(ts, content, shared_names);
if (proxy?.modified) {
written_proxies.push(write(`${outdir}/proxy${path.basename(node.shared)}`, proxy.code));
}

data = get_data_type(node.shared, 'load', server_data, proxy);
load = `Kit.Load<${params}, ${server_data}, ${get_parent_type('LayoutData')}, OutputData>`;
const type = get_data_type(node.shared, `${parent_type} & ${server_data}`, proxy);

data = `Omit<${parent_type}, keyof ${type}> & ${type}`;
load = `Kit.Load<${params}, ${server_data}, ${parent_type}, OutputData>`;
} else if (server_data === 'null') {
data = parent_type;
} else {
data = server_data;
data = `Omit<${parent_type}, keyof ${server_data}> & ${server_data}`;
}

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

/**
* @param {string} file_path
* @param {string} method
* @param {string} fallback
* @param {Proxy} proxy
*/
function get_data_type(file_path, method, fallback, proxy) {
function get_data_type(file_path, fallback, proxy) {
if (proxy) {
if (proxy.exports.includes(method)) {
if (proxy.exports.includes('load')) {
// If the file wasn't tweaked, we can use the return type of the original file.
// The advantage is that type updates are reflected without saving.
const from = proxy.modified
? `./proxy${replace_ext_with_js(path.basename(file_path))}`
: path_to_original(outdir, file_path);
return `Kit.AwaitedProperties<Awaited<ReturnType<typeof import('${from}').${method}>>>`;
return `Kit.AwaitedProperties<Awaited<ReturnType<typeof import('${from}').load>>>`;
} else {
return fallback;
}
6 changes: 3 additions & 3 deletions packages/kit/src/runtime/client/client.js
Original file line number Diff line number Diff line change
@@ -403,10 +403,10 @@ export function create_client({ target, base, trailing_slash }) {
let data = {};
let data_changed = false;
for (let i = 0; i < filtered.length; i += 1) {
Object.assign(data, filtered[i].data);
data = { ...data, ...filtered[i].data };
// Only set props if the node actually updated. This prevents needless rerenders.
if (!current.branch.some((node) => node === filtered[i])) {
result.props[`data_${i}`] = filtered[i].data;
if (data_changed || !current.branch.some((node) => node === filtered[i])) {
result.props[`data_${i}`] = data;
data_changed = true;
}
}
41 changes: 22 additions & 19 deletions packages/kit/src/runtime/server/page/render.js
Original file line number Diff line number Diff line change
@@ -83,18 +83,31 @@ export async function render_response({
navigating: writable(null),
updated
},
/** @type {import('types').Page} */
page: {
error,
params: /** @type {Record<string, any>} */ (event.params),
routeId: event.routeId,
status,
url: state.prerendering ? new PrerenderingURL(event.url) : event.url,
data: branch.reduce((acc, { data }) => (Object.assign(acc, data), acc), {})
},
components: await Promise.all(branch.map(({ node }) => node.component()))
};

let data = {};

// props_n (instead of props[n]) makes it easy to avoid
// unnecessary updates for layout components
for (let i = 0; i < branch.length; i += 1) {
data = { ...data, ...branch[i].data };
props[`data_${i}`] = data;
}

props.page = {
error,
params: /** @type {Record<string, any>} */ (event.params),
routeId: event.routeId,
status,
url: state.prerendering ? new PrerenderingURL(event.url) : event.url,
data
};

if (validation_errors) {
props.errors = validation_errors;
}

// TODO remove this for 1.0
/**
* @param {string} property
@@ -112,16 +125,6 @@ export async function render_response({
print_error('path', 'pathname');
print_error('query', 'searchParams');

// props_n (instead of props[n]) makes it easy to avoid
// unnecessary updates for layout components
for (let i = 0; i < branch.length; i += 1) {
props[`data_${i}`] = branch[i].data;
}

if (validation_errors) {
props.errors = validation_errors;
}

rendered = options.root.render(props);

for (const { node } of branch) {
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
<a href="/load/accumulated/with-page-data">with page data</a>
<a href="/load/accumulated/without-page-data">without page data</a>
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
/** @type {import('./$types').PageLoad} */
export function load() {
return {
pagedata: 'pagedata'
};
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<script>
/** @type {import('./$types').PageData} */
export let data;
</script>

<h1>foo.bar: {data.foo.bar}</h1>
<h2>pagedata: {data.pagedata}</h2>
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<script>
/** @type {import('./$types').PageData} */
export let data;
</script>

<h1>foo.bar: {data.foo.bar}</h1>

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

10 changes: 0 additions & 10 deletions packages/kit/test/apps/basics/src/routes/load/props/+layout.svelte

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<script>
export let data;
export let data;
</script>

<h2>Layout data: {JSON.stringify(data)}</h2>
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<script>
export let data;
export let data;
</script>

<p>Page data: {JSON.stringify(data)}</p>
7 changes: 0 additions & 7 deletions packages/kit/test/apps/basics/test/client.test.js
Original file line number Diff line number Diff line change
@@ -360,13 +360,6 @@ test.describe('Load', () => {
expect(await page.textContent('h1')).toBe("I didn't break!");
});

test("layout props don't cause rerender when unchanged", async ({ page, clicknav }) => {
await page.goto('/load/layout-props/a');
expect(await page.textContent('h1')).toBe('1');
await clicknav('[href="/load/layout-props/b"]');
expect(await page.textContent('h1')).toBe('1');
});

if (process.env.DEV) {
test('using window.fetch causes a warning', async ({ page }) => {
const port = 5173;
42 changes: 27 additions & 15 deletions packages/kit/test/apps/basics/test/test.js
Original file line number Diff line number Diff line change
@@ -260,21 +260,27 @@ test.describe('Shadowed pages', () => {

test('Parent data is present', async ({ page, clicknav }) => {
await page.goto('/shadowed/parent');
await expect(page.locator('h2')).toHaveText('Layout data: {"layout":"layout"}');
await expect(page.locator('h2')).toHaveText(
'Layout data: {"foo":{"bar":"Custom layout"},"layout":"layout"}'
);
await expect(page.locator('p')).toHaveText(
'Page data: {"page":"page","data":{"rootlayout":"rootlayout","layout":"layout"}}'
'Page data: {"foo":{"bar":"Custom layout"},"layout":"layout","page":"page","data":{"rootlayout":"rootlayout","layout":"layout"}}'
);

await clicknav('[href="/shadowed/parent?test"]');
await expect(page.locator('h2')).toHaveText('Layout data: {"layout":"layout"}');
await expect(page.locator('h2')).toHaveText(
'Layout data: {"foo":{"bar":"Custom layout"},"layout":"layout"}'
);
await expect(page.locator('p')).toHaveText(
'Page data: {"page":"page","data":{"rootlayout":"rootlayout","layout":"layout"}}'
'Page data: {"foo":{"bar":"Custom layout"},"layout":"layout","page":"page","data":{"rootlayout":"rootlayout","layout":"layout"}}'
);

await clicknav('[href="/shadowed/parent/sub"]');
await expect(page.locator('h2')).toHaveText('Layout data: {"layout":"layout"}');
await expect(page.locator('h2')).toHaveText(
'Layout data: {"foo":{"bar":"Custom layout"},"layout":"layout"}'
);
await expect(page.locator('p')).toHaveText(
'Page data: {"sub":"sub","data":{"rootlayout":"rootlayout","layout":"layout"}}'
'Page data: {"foo":{"bar":"Custom layout"},"layout":"layout","sub":"sub","data":{"rootlayout":"rootlayout","layout":"layout"}}'
);
});

@@ -842,15 +848,6 @@ test.describe('Load', () => {
expect(await page.innerHTML('.raw')).toBe('{ "oddly" : { "formatted" : "json" } }');
});

test('does not leak props to other pages', async ({ page, clicknav }) => {
await page.goto('/load/props/about');
expect(await page.textContent('p')).toBe('Data: null');
await clicknav('[href="/load/props/"]');
expect(await page.textContent('p')).toBe('Data: Hello from Index!');
await clicknav('[href="/load/props/about"]');
expect(await page.textContent('p')).toBe('Data: null');
});

test('server-side fetch respects set-cookie header', async ({ page, context }) => {
await context.clearCookies();

@@ -878,6 +875,21 @@ test.describe('Load', () => {
})
).toBe('rgb(255, 0, 0)');
});

test('page without load has access to layout data', async ({ page, clicknav }) => {
await page.goto('/load/accumulated');

await clicknav('[href="/load/accumulated/without-page-data"]');
expect(await page.textContent('h1')).toBe('foo.bar: Custom layout');
});

test('page with load has access to layout data', async ({ page, clicknav }) => {
await page.goto('/load/accumulated');

await clicknav('[href="/load/accumulated/with-page-data"]');
expect(await page.textContent('h1')).toBe('foo.bar: Custom layout');
expect(await page.textContent('h2')).toBe('pagedata: pagedata');
});
});

test.describe('Method overrides', () => {