From cfd76707194e22cabe6a821d835db139184ee1ed Mon Sep 17 00:00:00 2001 From: Ben McCann <322311+benmccann@users.noreply.github.com> Date: Wed, 2 Sep 2020 21:20:24 -0700 Subject: [PATCH 1/4] Sapper's router as a standalone component --- text/0000-sapper-routing.md | 142 ++++++++++++++++++++++++++++++++++++ 1 file changed, 142 insertions(+) create mode 100644 text/0000-sapper-routing.md diff --git a/text/0000-sapper-routing.md b/text/0000-sapper-routing.md new file mode 100644 index 0000000..3bd740b --- /dev/null +++ b/text/0000-sapper-routing.md @@ -0,0 +1,142 @@ +- Start Date: 2020-09-16 +- RFC PR: (leave this empty) +- Svelte Issue: (leave this empty) + +# Refactoring out Sapper's Routing + +## Summary + +Split Sapper's routing functionality into a standalone library and make it possible to use Sapper's routing outside of Sapper. + +## Motivation + +Benefits include: + +* Making it possible to use Sapper's routing outside of Sapper. The routing component should be able to be used in a client-side only application +* Making Sapper's routing more configurable. E.g. the user may wish to set a custom header on a certain route. We could expose the router component in the application and allow the user to set configuration on a per-route basis along the lines of `router.header('/[list]/[page]', {'Cache-Control': max-age=600'})` + * Other requests users have for configuring the router include [routes aliases](https://github.com/sveltejs/sapper/issues/1450) and [configuring trailing slashes](https://github.com/sveltejs/sapper/issues/519), which we could expose APIs for as well +* Improved testability. Right now to test routing functionality you need a sample application and all tests are integration tests +* I believe it would become possible to make it such that Sapper's routing can be used with or without generating it from the file system though this is perhaps not a primary goal. +* Finally, Sapper's routing and Routify are quite similar. It may be possible to combine these two systems if everyone involved were open to such a possibility. I think it'd be good for the Svelte community to have a single solution because it means that solution would get more developer support than two individual solutions. Right now [there are many Svelte routing solutions](https://twitter.com/lihautan/status/1315482668440580096?s=19). + +## Detailed design + +### Implementation + +This could be split up into multiple PRs that are smaller and easier to review. + +#### Step 1 + +Remove need to specify CSS files in routing components. + +Update: this has been completed in [#1508](https://github.com/sveltejs/sapper/pull/1508) + + +#### Step 2 + +Put all routing information in a single routing component. Historically it has been split between `start` and `app`. + +Update: this has been largely completed in [#1434](https://github.com/sveltejs/sapper/pull/1434). We can probably still clean up the prefetching code, etc. + + +#### Step 3 + +Add an API to register routes and make the generated code call the router rather than the router calling the generated code. + +Right now, the generated `manifest-client.mjs` contains something like: + +``` +export const components = [ + { + js: () => import("../../../routes/index.svelte") + }, + { + js: () => import("../../../routes/[list]/[page].svelte") + } +]; + +export const routes = (d => [ + { + // index.svelte + pattern: /^\/$/, + parts: [ + { i: 0 } + ] + }, + { + // [list]/[page].svelte + pattern: /^\/([^/]+?)\/([^/]+?)\/?$/, + parts: [ + null, + { i: 1, params: match => ({ list: d(match[1]), page: d(match[2]) }) } + ] + } +])(decodeURIComponent); +``` + + +It would be nice to invert this such that the generated code calls the router instead of the router importing the generated code: + +``` +import router from 'router'; + +router.register({ + route: /^\/$/, + component: () => import("../../../routes/index.svelte") +}); +router.register({ + route: /^\/([^/]+?)\/([^/]+?)\/?$/, + params: match => ({ list: d(match[1]), page: d(match[2]) }), + component: () => import("../../../routes/[list]/[page].svelte") +}); +``` + +This API is a bit complex. However, it could be greatly simplified by making the route parsing be part of the router runtime instead of happening at compile time: + +``` +import router from 'router'; + +router.register('/', () => import("../../../routes/index.svelte")); +router.register('/[list]/[page]', () => import("../../../routes/[list]/[page].svelte")); +``` + +#### Step 4 + +Unify the client and server routing APIs. + +The routes today get generated into `manifest-client.mjs` and `manifest-server.mjs` in the `src/node_modules/@sapper/internal` directory. Seperate manifests are needed because Svelte generates different components for SSR and the DOM. Additionally, the server-side routing manifest contains a list of server routes, which the client manifest does not. + +The server manifest also differs in a number of unnecessary ways: +* it provide directly imported components while the client-side does dynamic imports. This could easily be changed so that both client and server use dynamic imports +* it contains a `name` field used only for webpack support. This field is a result of webpack unnecessarily using a different format in `build.json` +* it contains a `file` field to lookup `preload` header dependencies from `build.json`. We should be able key off the route instead of the file name in `build.json` + +We can remove the unnecessary differences. We will still need different client-side and server-side router instantiations registering their respective components, but they can utilize the same `router.register` API. On the client-side we can call `router.navigate`. On the server-side, we can just ask the router to return the registered component for a given route and let `get_page_handler` continue to handle it as it does today. + +#### Step 5 + +Refactor out routes generation into separate plugin. Publish components separately. + +Review the API and make sure we like it. There's probably some minor cleanup to do before exposing the routing API more broadly + +We could potentially have a library for the core routing functionality. And then a second for the file-system-based routes generator. + +## How we teach this + +Update the documentation, migration guide, and templates. + +## Drawbacks + +Some user migration may be necessary. E.g. if the router is a separate component, then it will need to be included in `package.json` as a dependency. + +The bundle size may increase slightly if the routes parsing is included in the bundle. This probably would result in about 50 lines of additional code in the router runtime. However, it would reduce the number of lines required to register a route so it may end up being a savings for larger applications. If we are concerned about this it could be made an optional pluggable component of the router. Most users will not register components at runtime and so using generated regexes will continue to be just fine for the existing use cases. Though where it is likely to be very helpful is if we want to allow programmatic access to configuring the routes (e.g. to add a header) + +## Alternatives + +Probably the main alternative would be the status quo. If people want to use Sapper's routing without the rest of Sapper then we point them to Routify, page.js, or some other solution. + +## Unresolved questions + +The last step seems the one that we would need to put the most time and thought into. E.g. should it live in a Rollup plugin, would we continue to support webpack, are there reason we don't want to make everything into plugins? + +When publishing new npm packages should we put them in a `@sapper` namespace? Can/should we publish multiple packages from the existing Saper repo or should we make a new repo for each package? From fd948d1805e34c63ddcb4761bfc21b2b53489c21 Mon Sep 17 00:00:00 2001 From: Ben McCann <322311+benmccann@users.noreply.github.com> Date: Sat, 23 Jan 2021 21:26:39 -0800 Subject: [PATCH 2/4] Update for current status / SvelteKit --- text/0000-routing.md | 135 ++++++++++++++++++++++++++++++++++ text/0000-sapper-routing.md | 142 ------------------------------------ 2 files changed, 135 insertions(+), 142 deletions(-) create mode 100644 text/0000-routing.md delete mode 100644 text/0000-sapper-routing.md diff --git a/text/0000-routing.md b/text/0000-routing.md new file mode 100644 index 0000000..81c9aab --- /dev/null +++ b/text/0000-routing.md @@ -0,0 +1,135 @@ +- Start Date: 2020-09-16 +- RFC PR: (leave this empty) +- Svelte Issue: (leave this empty) + +# Refactoring out SvelteKit's Routing + +## Summary + +Split SvelteKit's routing functionality into a standalone library and make it possible to use SvelteKit's routing outside of SvelteKit. + +## Motivation + +Benefits include: + +* Making it possible to use SvelteKit's routing outside of SvelteKit. The routing component should be able to be used in a client-side only application +* Making SvelteKit's routing more configurable + * User requests for configuring the router include [routes aliases](https://github.com/sveltejs/sapper/issues/1450) and [configuring trailing slashes](https://github.com/sveltejs/sapper/issues/519), which we could expose APIs for +* Improved testability. Right now to test routing functionality you need a sample application and all tests are integration tests +* I believe it would become possible to make it such that SvelteKit's routing can be used with or without generating it from the file system though this is perhaps not a primary goal. +* Finally, SvelteKit's routing and Routify are quite similar. It may be possible to combine these two systems if everyone involved were open to such a possibility. I think it'd be good for the Svelte community to have a single solution because it means that solution would get more developer support than two individual solutions. Right now [there are many Svelte routing solutions](https://twitter.com/lihautan/status/1315482668440580096?s=19). + +## Detailed design + +### Implementation + +This could be split up into multiple PRs that are smaller and easier to review. + +#### Step 1 + +Remove need to specify CSS files in routing components. + +Update: this was completed in [#1508](https://github.com/sveltejs/sapper/pull/1508). + + +#### Step 2 + +Put all routing information in a single routing component. Historically, in Sapper, it has been split between `start` and `app`. + +Update: this has basically been completed with the router now living `packages/kit/runtime/internal/router` + + +#### Step 3 + +Parse routes at runtime to simplify API. + +Right now, the generated `manifest.js` contains something like: + +``` +const components = [ + () => import("/_app/routes/index.svelte.js"), + () => import("/_app/routes/about.svelte.js"), + () => import("/_app/routes/item/[id].svelte.js"), + () => import("/_app/routes/user/[name].svelte.js") +]; + +const d = decodeURIComponent; +const empty = () => ({}); + +export const pages = [ + { + // index.svelte + pattern: /^\/$/, + params: empty, + parts: [components[0]] + }, + + { + // about.svelte + pattern: /^\/about\/?$/, + params: empty, + parts: [components[1]] + }, + + { + // item/[id].svelte + pattern: /^\/item\/([^/]+?)\/?$/, + params: (m) => ({ id: d(m[1])}), + parts: [components[2]] + }, + + { + // user/[name].svelte + pattern: /^\/user\/([^/]+?)\/?$/, + params: (m) => ({ name: d(m[1])}), + parts: [components[3]] + } +]; +``` + +This API is a bit complex. However, it could be greatly simplified by making the route parsing be part of the router runtime instead of happening at compile time: + +``` +const pages = { + '/': () => import("../../../routes/index.svelte" + '/[list]/[page]': () => import("../../../routes/[list]/[page].svelte" +} + +const router = new Router({ + base: paths.base, + host, + pages, + ignore +}); +``` + +The `pattern` is needed on the server-side too, so we will still have to include it in the manifest. + +#### Step 4 + +Refactor out routes generation into separate plugin. Publish components separately. + +Review the API and make sure we like it. There's probably some minor cleanup to do before exposing the routing API more broadly + +We could potentially have a library for the core routing functionality. And then a second for the file-system-based routes generator. + + +## How we teach this + +Update the documentation, migration guide, and templates. + +## Drawbacks + +Some user migration may be necessary. E.g. if the router is a separate component, then it will need to be included in `package.json` as a dependency. + +The bundle size may increase slightly if the routes parsing is included in the bundle. This probably would result in about 50 lines of additional code in the router runtime. However, it would reduce the number of lines required to register a route so it may end up being a savings for larger applications. If we are concerned about this it could be made an optional pluggable component of the router. Most users will not register components at runtime and so using generated regexes will continue to be just fine for the existing use cases. Though where it is likely to be very helpful is if we want to allow programmatic access to configuring the routes (e.g. to add a header) + +## Alternatives + +Probably the main alternative would be the status quo. If people want to use Sapper's routing without the rest of Sapper then we point them to Routify, page.js, or some other solution. + +## Unresolved questions + +The last step seems the one that we would need to put the most time and thought into. E.g. should it live in a Rollup plugin, would we continue to support webpack, are there reason we don't want to make everything into plugins? + +When publishing new npm packages should we put them in a `@sapper` namespace? Can/should we publish multiple packages from the existing Saper repo or should we make a new repo for each package? diff --git a/text/0000-sapper-routing.md b/text/0000-sapper-routing.md deleted file mode 100644 index 3bd740b..0000000 --- a/text/0000-sapper-routing.md +++ /dev/null @@ -1,142 +0,0 @@ -- Start Date: 2020-09-16 -- RFC PR: (leave this empty) -- Svelte Issue: (leave this empty) - -# Refactoring out Sapper's Routing - -## Summary - -Split Sapper's routing functionality into a standalone library and make it possible to use Sapper's routing outside of Sapper. - -## Motivation - -Benefits include: - -* Making it possible to use Sapper's routing outside of Sapper. The routing component should be able to be used in a client-side only application -* Making Sapper's routing more configurable. E.g. the user may wish to set a custom header on a certain route. We could expose the router component in the application and allow the user to set configuration on a per-route basis along the lines of `router.header('/[list]/[page]', {'Cache-Control': max-age=600'})` - * Other requests users have for configuring the router include [routes aliases](https://github.com/sveltejs/sapper/issues/1450) and [configuring trailing slashes](https://github.com/sveltejs/sapper/issues/519), which we could expose APIs for as well -* Improved testability. Right now to test routing functionality you need a sample application and all tests are integration tests -* I believe it would become possible to make it such that Sapper's routing can be used with or without generating it from the file system though this is perhaps not a primary goal. -* Finally, Sapper's routing and Routify are quite similar. It may be possible to combine these two systems if everyone involved were open to such a possibility. I think it'd be good for the Svelte community to have a single solution because it means that solution would get more developer support than two individual solutions. Right now [there are many Svelte routing solutions](https://twitter.com/lihautan/status/1315482668440580096?s=19). - -## Detailed design - -### Implementation - -This could be split up into multiple PRs that are smaller and easier to review. - -#### Step 1 - -Remove need to specify CSS files in routing components. - -Update: this has been completed in [#1508](https://github.com/sveltejs/sapper/pull/1508) - - -#### Step 2 - -Put all routing information in a single routing component. Historically it has been split between `start` and `app`. - -Update: this has been largely completed in [#1434](https://github.com/sveltejs/sapper/pull/1434). We can probably still clean up the prefetching code, etc. - - -#### Step 3 - -Add an API to register routes and make the generated code call the router rather than the router calling the generated code. - -Right now, the generated `manifest-client.mjs` contains something like: - -``` -export const components = [ - { - js: () => import("../../../routes/index.svelte") - }, - { - js: () => import("../../../routes/[list]/[page].svelte") - } -]; - -export const routes = (d => [ - { - // index.svelte - pattern: /^\/$/, - parts: [ - { i: 0 } - ] - }, - { - // [list]/[page].svelte - pattern: /^\/([^/]+?)\/([^/]+?)\/?$/, - parts: [ - null, - { i: 1, params: match => ({ list: d(match[1]), page: d(match[2]) }) } - ] - } -])(decodeURIComponent); -``` - - -It would be nice to invert this such that the generated code calls the router instead of the router importing the generated code: - -``` -import router from 'router'; - -router.register({ - route: /^\/$/, - component: () => import("../../../routes/index.svelte") -}); -router.register({ - route: /^\/([^/]+?)\/([^/]+?)\/?$/, - params: match => ({ list: d(match[1]), page: d(match[2]) }), - component: () => import("../../../routes/[list]/[page].svelte") -}); -``` - -This API is a bit complex. However, it could be greatly simplified by making the route parsing be part of the router runtime instead of happening at compile time: - -``` -import router from 'router'; - -router.register('/', () => import("../../../routes/index.svelte")); -router.register('/[list]/[page]', () => import("../../../routes/[list]/[page].svelte")); -``` - -#### Step 4 - -Unify the client and server routing APIs. - -The routes today get generated into `manifest-client.mjs` and `manifest-server.mjs` in the `src/node_modules/@sapper/internal` directory. Seperate manifests are needed because Svelte generates different components for SSR and the DOM. Additionally, the server-side routing manifest contains a list of server routes, which the client manifest does not. - -The server manifest also differs in a number of unnecessary ways: -* it provide directly imported components while the client-side does dynamic imports. This could easily be changed so that both client and server use dynamic imports -* it contains a `name` field used only for webpack support. This field is a result of webpack unnecessarily using a different format in `build.json` -* it contains a `file` field to lookup `preload` header dependencies from `build.json`. We should be able key off the route instead of the file name in `build.json` - -We can remove the unnecessary differences. We will still need different client-side and server-side router instantiations registering their respective components, but they can utilize the same `router.register` API. On the client-side we can call `router.navigate`. On the server-side, we can just ask the router to return the registered component for a given route and let `get_page_handler` continue to handle it as it does today. - -#### Step 5 - -Refactor out routes generation into separate plugin. Publish components separately. - -Review the API and make sure we like it. There's probably some minor cleanup to do before exposing the routing API more broadly - -We could potentially have a library for the core routing functionality. And then a second for the file-system-based routes generator. - -## How we teach this - -Update the documentation, migration guide, and templates. - -## Drawbacks - -Some user migration may be necessary. E.g. if the router is a separate component, then it will need to be included in `package.json` as a dependency. - -The bundle size may increase slightly if the routes parsing is included in the bundle. This probably would result in about 50 lines of additional code in the router runtime. However, it would reduce the number of lines required to register a route so it may end up being a savings for larger applications. If we are concerned about this it could be made an optional pluggable component of the router. Most users will not register components at runtime and so using generated regexes will continue to be just fine for the existing use cases. Though where it is likely to be very helpful is if we want to allow programmatic access to configuring the routes (e.g. to add a header) - -## Alternatives - -Probably the main alternative would be the status quo. If people want to use Sapper's routing without the rest of Sapper then we point them to Routify, page.js, or some other solution. - -## Unresolved questions - -The last step seems the one that we would need to put the most time and thought into. E.g. should it live in a Rollup plugin, would we continue to support webpack, are there reason we don't want to make everything into plugins? - -When publishing new npm packages should we put them in a `@sapper` namespace? Can/should we publish multiple packages from the existing Saper repo or should we make a new repo for each package? From a2290bdfd4a4f2734854964ec012c15cc288282c Mon Sep 17 00:00:00 2001 From: Ben McCann <322311+benmccann@users.noreply.github.com> Date: Wed, 2 Mar 2022 16:45:37 -0800 Subject: [PATCH 3/4] add missing parens --- text/0000-routing.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/text/0000-routing.md b/text/0000-routing.md index 81c9aab..ea3fa9d 100644 --- a/text/0000-routing.md +++ b/text/0000-routing.md @@ -91,8 +91,8 @@ This API is a bit complex. However, it could be greatly simplified by making the ``` const pages = { - '/': () => import("../../../routes/index.svelte" - '/[list]/[page]': () => import("../../../routes/[list]/[page].svelte" + '/': () => import("../../../routes/index.svelte"), + '/[list]/[page]': () => import("../../../routes/[list]/[page].svelte") } const router = new Router({ From e18f3b39f3d10568d2d775b09773fafda2418770 Mon Sep 17 00:00:00 2001 From: Ben McCann <322311+benmccann@users.noreply.github.com> Date: Wed, 20 Jul 2022 13:02:00 -0700 Subject: [PATCH 4/4] update with latest sveltekit changes --- text/0000-routing.md | 84 +++++++++++++++----------------------------- 1 file changed, 28 insertions(+), 56 deletions(-) diff --git a/text/0000-routing.md b/text/0000-routing.md index ea3fa9d..dfedb03 100644 --- a/text/0000-routing.md +++ b/text/0000-routing.md @@ -36,78 +36,50 @@ Update: this was completed in [#1508](https://github.com/sveltejs/sapper/pull/15 Put all routing information in a single routing component. Historically, in Sapper, it has been split between `start` and `app`. -Update: this has basically been completed with the router now living `packages/kit/runtime/internal/router` +Update: this has basically been completed with the router now living `packages/kit/src/runtime/client` #### Step 3 -Parse routes at runtime to simplify API. +Create a routing API. -Right now, the generated `manifest.js` contains something like: +Parse routes at runtime to simplify API. Update: this is now done and lives in `packages/kit/src/runtime/client/parse.js` + +Right now, the generated `./svelte-kit/generated/client-manifest.js` contains something like: ``` -const components = [ - () => import("/_app/routes/index.svelte.js"), - () => import("/_app/routes/about.svelte.js"), - () => import("/_app/routes/item/[id].svelte.js"), - () => import("/_app/routes/user/[name].svelte.js") +export { matchers } from './client-matchers.js'; + +export const components = [ + () => import("../../src/routes/__layout.svelte"), + () => import("../runtime/components/error.svelte"), + () => import("../../src/routes/[slug].svelte"), + () => import("../../src/routes/example/[param].svelte"), + () => import("../../src/routes/index.svelte") ]; -const d = decodeURIComponent; -const empty = () => ({}); - -export const pages = [ - { - // index.svelte - pattern: /^\/$/, - params: empty, - parts: [components[0]] - }, - - { - // about.svelte - pattern: /^\/about\/?$/, - params: empty, - parts: [components[1]] - }, - - { - // item/[id].svelte - pattern: /^\/item\/([^/]+?)\/?$/, - params: (m) => ({ id: d(m[1])}), - parts: [components[2]] - }, - - { - // user/[name].svelte - pattern: /^\/user\/([^/]+?)\/?$/, - params: (m) => ({ name: d(m[1])}), - parts: [components[3]] - } -]; +export const dictionary = { + "": [[0, 4], [1]], + "example/[param]": [[0, 3], [1]], + "[slug]": [[0, 2], [1]] +}; ``` -This API is a bit complex. However, it could be greatly simplified by making the route parsing be part of the router runtime instead of happening at compile time: - +This API is pretty compact, but not too human readable. It also requires the layouts and error components to be specified repeatedly. We could create a friendly API for this: ``` -const pages = { - '/': () => import("../../../routes/index.svelte"), - '/[list]/[page]': () => import("../../../routes/[list]/[page].svelte") -} - -const router = new Router({ - base: paths.base, - host, - pages, - ignore -}); +const routes = new Route('/') + .layout(() => import("../../src/routes/__layout.svelte")) + .error(() => import("../runtime/components/error.svelte")) + .routes({ + '': () => import('../../src/routes/index.svelte'), + '[slug]': () => import('../../src/routes/[slug].svelte'), + 'example/[param]': () => import(../../src/routes/example/[param].svelte); + }); ``` -The `pattern` is needed on the server-side too, so we will still have to include it in the manifest. - #### Step 4 -Refactor out routes generation into separate plugin. Publish components separately. +Refactor out routes generation into separate Vite plugin like `vite-plugin-svelte-kit:router`. Publish components separately. Review the API and make sure we like it. There's probably some minor cleanup to do before exposing the routing API more broadly