Skip to content

[breaking] replace routeId with route.id - #7450

Merged
Rich-Harris merged 18 commits into
masterfrom
route-id
Nov 4, 2022
Merged

[breaking] replace routeId with route.id#7450
Rich-Harris merged 18 commits into
masterfrom
route-id

Conversation

@dummdidumm

Copy link
Copy Markdown
Member

Closes #6980

Draft, because the tracking part is missing, and I'm not sure yet if we want to replace routeId in all places (like $page.routeId)

Please don't delete this checklist! Before submitting the PR, please make sure you do the following:

  • It's really useful if your PR references an issue where it is discussed ahead of time. In many cases, features are absent for a reason. For large changes, please create an RFC: https://github.com/sveltejs/rfcs
  • This message body should clearly illustrate what problems it solves.
  • Ideally, include a test that fails without this PR but passes with it.

Tests

  • Run the tests with pnpm test and lint the project with pnpm lint and pnpm check

Changesets

  • If your PR makes a change that should be noted in one or more packages' changelogs, generate a changeset by running pnpm changeset and following the prompts. All changesets should be patch until SvelteKit 1.0

@changeset-bot

changeset-bot Bot commented Oct 31, 2022

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 85d15e1

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@sveltejs/kit Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@Conduitry

Copy link
Copy Markdown
Member

Is there anything else we foresee living in route? params could get moved in there maybe?

Whatever we do, for consistency I'd be in favor of having $page be the same shape to the extent possible.

@dummdidumm

Copy link
Copy Markdown
Member Author

params could be moved into it, but that would be too big of a breaking change at this point, and it also results in worse DX, because you'd have to destructure the input twice then.

@Rich-Harris

Copy link
Copy Markdown
Member

The site is failing to deploy because a code snippet in the docs references route.id instead of route?.id. It'd be easy to fix, but I wonder if instead the types should reflect the fact that route always exists in the context of load or a +server.js handler?

@Rich-Harris

Copy link
Copy Markdown
Member

I made that change for LoadEvent and ServerLoadEvent, trying to figure out the best approach for RequestHandler. If I make this change...

export interface RequestHandler<
	Params extends Partial<Record<string, string>> = Partial<Record<string, string>>
> {
-	(event: RequestEvent<Params>): MaybePromise<Response>;
+	(event: RequestEvent<Params> & { route: { id: string } }): MaybePromise<Response>;
}

...then it works, but this is what I get if I hover over route:

image

Not totally ideal. Do we need to come up with a new type that extends RequestEvent? Or should the route ID be a generic argument? (That way we could make it an actual value for the generated types, or a list of possible values for layouts, etc)

@dummdidumm

dummdidumm commented Nov 1, 2022

Copy link
Copy Markdown
Member Author

I thought this, too, but in some cases the root layout is loaded but without a route - for example when an infinite redirect loop is detected, or in case of a 404 - many ugh-vibes on this PR 😅 route.id is probably fine most of the time, but one could forget it and it would crash more likely than justing using routeId - an argument in favor of getRouteId()?

@Rich-Harris

Copy link
Copy Markdown
Member

Ah crap, you're right — will revert the last commit.

What if we went the generic route though, and made route always exist but with id defaulting to string | null?

// .svelte-kit/types/src/routes/blog/$types.d.ts
export type PageLoad<...> = Kit.Load<..., OutputData, 'blog'>;
export type LayoutLoad<...> = Kit.Load<..., OutputData, 'blog' | 'blog/[slug]'>;

Or is it misleading for the route object to exist when no route matched?

@dummdidumm

Copy link
Copy Markdown
Member Author

Funny how we go through the same thought process independently. I asked myself the same question, and came to the conclusion that it's weird. Then again, all of this is weird, so we need to choose something that is the least weird while being easy enough to use. After having implemented this, part of me thinks getRouteId() would be better after all - unless you imagine any other things ending up in that object later on.
The generic is interesting, although I'm wondering if that typing maintenance is worth it.
What are people using routeId for anyway? Surely it's only in layouts?

@Rich-Harris

Copy link
Copy Markdown
Member

And just as I'd come around to route.id! It's definitely more future-proof, but I can't imagine what it would need to be future-proof against (i.e. I can't think of any properties we'd need beyond id). Then again that's sort of the point of being future-proof — you can't anticipate everything.

I think parity with $page is a reasonably convincing argument. We could have event.getRouteId() and $page.routeId but it would definitely feel a bit more natural to have event.route.id and $page.route.id.

What are people using routeId for anyway? Surely it's only in layouts?

Yeah, I think the main use case is where you want some special case logic in a layout ('require the user to be logged in, unless the current routeId is /login', or whatever).

@dummdidumm

Copy link
Copy Markdown
Member Author

I like route.id more for future-proofness and for how it looks when reading it. I'm not sure how I feel about route: { id: string | null } vs route: { id: string } | null. The latter feels more correct, but it's easier to produce runtime nullpointers with it. So ... yeah, maybe go with route: { id: string | null }

Params extends Partial<Record<string, string>> = Partial<Record<string, string>>,
OutputData extends Record<string, any> | void = Record<string, any> | void
OutputData extends Record<string, any> | void = Record<string, any> | void,
RouteId extends string | null = string | null

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we need the overhead of that interface on the action interfaces? Actions can only be used on pages, so routeId can only be one thing, so it's useless to use it, so probably noone does that, so we probably can just type this as string on RequestEvent

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there any real drawback? It's contrived but you could imagine someone doing this sort of thing, where some_shared_logic is picky about the second argument:

export const actions = {
  blah: ({ request, route }) => {
    some_shared_logic(request, route.id);
  }
};

@Rich-Harris

Copy link
Copy Markdown
Member

This autocompletion is kinda neat:

Screen.Recording.2022-11-01.at.3.36.06.PM.mov

Also, this typechecking 😍

image

@Conduitry

Copy link
Copy Markdown
Member

Does that mean this also closes #6167?

@dummdidumm

Copy link
Copy Markdown
Member Author

no, because that relates to $page.routeId, but you are probably right that this can also be infered from that.

@Rich-Harris
Rich-Harris marked this pull request as ready for review November 1, 2022 21:48
@Rich-Harris

Copy link
Copy Markdown
Member

Ok that was a bit of a faff but it will now correctly identify the dependency on route.id if you have this...

/** @type {import('./$types').LayoutServerLoad} */
export function load({ route }) {
  return { route };
}

.,..and the same goes for url and params. Surprised we didn't get bitten by that bug sooner

@Rich-Harris
Rich-Harris merged commit a6a8690 into master Nov 4, 2022
@Rich-Harris
Rich-Harris deleted the route-id branch November 4, 2022 18:36
@github-actions github-actions Bot mentioned this pull request Nov 4, 2022
@brunnerh brunnerh mentioned this pull request Nov 5, 2022
5 tasks
Rich-Harris pushed a commit that referenced this pull request Jul 22, 2026
…error page (#16381)

After an error page renders, `current.route` is `null`
(`load_root_error_page` stores `route: null` in the navigation state).
The next navigation computes

```js
const route_changed = current.route ? route.id !== current.route.id : false;
```

so the route counts as unchanged no matter where the user goes next. A
root layout server load that tracked `route` is then skipped as fresh,
and its data from before the error page renders on the new page. The new
test's failing assertion on `version-3` shows the layout still claiming
the previous route:

```
Expected: "/b"
Received: "/a"
```

A missing `current.route` now counts as changed. On a first-ever
navigation the flag is irrelevant, the node has no previous data to
reuse anyway, so the only behavior change is the conservative refetch
after error pages.

The interaction was never designed. `route: null` came in #6552 to give
`beforeNavigate` a value meaning "no matched route", and the guard came
two months later in #7450 with route tracking itself, mirroring the
adjacent `url_changed` null-guard. Neither discussed the error-page
case.

The test lives in the no-ssr app. Its root layout gains a server load
returning `route.id`, the navigation's data request is intercepted with
a 500 to produce the error page, and the follow-up navigation must show
the new route id. The app's root `+layout.server.js` was an empty file,
shipped by #11354 so its 404-loop test has a server load node in dev;
giving it a real load keeps that trigger intact.

Found while reviewing #16380, which mirrors this computation for the
error page itself and carries the same one-line fix.

---

### Please don't delete this checklist! Before submitting the PR, please
make sure you do the following:
- [x] It's really useful if your PR references an issue where it is
discussed ahead of time. In many cases, features are absent for a
reason. For large changes, please create an RFC:
https://github.com/sveltejs/rfcs
- [x] This message body should clearly illustrate what problems it
solves.
- [x] Ideally, include a test that fails without this PR but passes with
it.

### Tests
- [x] Run the tests with `pnpm test` and lint the project with `pnpm
lint` and `pnpm check`

### Changesets
- [x] If your PR makes a change that should be noted in one or more
packages' changelogs, generate a changeset by running `pnpm changeset`
and following the prompts. Changesets that add features should be
`minor` and those that fix bugs should be `patch`. Please prefix
changeset messages with `feat:`, `fix:`, or `chore:`.

### Edits

- [x] Please ensure that 'Allow edits from maintainers' is checked. PRs
without this option may be closed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

routeId not tracked as dependency to layout load

3 participants