sveltejs / kit

web development, streamlined
https://svelte.dev/docs/kit
MIT License
18.73k stars 1.95k forks source link

Enable package-provided routes #8896

Open Radiergummi opened 1 year ago

Radiergummi commented 1 year ago

Describe the problem

Please bear with me for a minute, explaining the rationale requires a bit of context.
I'm working on a (FOSS) SvelteKit-based ebook library app. It is built to run on all platforms SvelteKit supports, with platform-specific integrations being used where possible. If you'd be running it on Cloudflare, for example, the Cloudflare R2 bucket integration will be used for storage; if running on Node, it will use the local filesystem. On a platform like Netlify, which has support for edge functions, book metadata parsing will run on the edge; whereas on other platforms, it'll use a web worker for that.
In addition to platform features, there are a bunch of other modules with configurable implementations (mailing, knowledge graph queries, image uploads, etc.).

Now, to make it easier for users to get started, as well as keeping runtime size small, I have an npm create package that will prompt the user for the services they use, and perform a custom install of all required packages: Say, @example/core, @example/mailing-mailgun, and @example/storage-cloudflare (and their respective dependencies).

All of this works; what I'm missing, however, is a good way of packaging up the actual SvelteKit app in a reusable way. Right now, I have to copy all project files from a template repository into the user's project directory, install dependencies, and generate a matching .env file.
On the plus side, this gives users something of an improved git clone, essentially a glorified configuration script. They can modify all routes and components to their liking. On the downside, this setup is incredibly hard to reliably update, and requires anything and everything to have a stable API.

Describe the proposed solution

What I envision is the ability to distribute my SvelteKit app as a dependency users would install into an empty project, along with any extension packages they need. The project would start with an empty src/routes directory; all routes would be loaded from the base application, unless a file with the same path exists in the routes directory. For example, my app would provide an implicit /profiles/[user] route, which users could eject/override by creating src/routes/profiles/[user]/+page.svelte. (Thanks to @theetrain on Discord for putting it so succinctly). Obviously this still bears the risk of ejected routes breaking after updates, but in that case it's immediately clear whether a bug is caused by userland or package code.

This would make for a very small installation footprint, while still being hackable.

From a technical POV, this would probably work best as a Vite plugin that overlays SvelteKit, as in plugins: [ kit(), app() ]. I have dabbled with Vite, but definitely not enough yet to do this on my own, however.

Alternatives considered

Having users git clone a starter repository, or copying a starter project using the create package both work, but come with the aforementioned downsides (namely the complexity of updates and support headaches).

Importance

would make my life easier

Additional Information

The project is still a work in progress, although the repository shows the structure I'm going for, if that helps: https://github.com/project-kiosk/kiosk/tree/next#readme

lettucebowler commented 1 year ago

Some way to expose routes from a package would be a nice enhancement for things like @auth/sveltekit to take advantage of. Currently it exposes all the api routes through a handler so they don't exist as routes in the route tree which isn't ideal when you want to split into multiple functions at route boundaries

saabi commented 1 year ago

This is something we've been missing as well for our Sveltekit app template and would love to see it implemented.

I would imagine that for something like this to work, the Sveltekit router would first have to be enhanced to also support non-file-based routing, which would consequently also help with localized routing.

Tam2 commented 1 year ago

This is something we're looking for too

gu-stav commented 1 year ago

I'd like to express my need for this too and believe this would enable a bunch of new use-cases by making svelte kit more composable.

In case there would be some interest by the maintainers to start conceptual work on this topic, I'd love to contribute. My impression from some explorations in svelte-kit to make this possible was, that it would not be very hard.

One way to open up the router e.g. could be to allow other vite-plugins accessing the internal manifest, extend it and offer a few convenience APIs, which could be equally composable as nuxt plugins/ modules.

Here a few interesting ways (imo) this is being handled by other frameworks at the moment:

Nuxt

Apps can be extended in different ways:

  1. app/router.options.ts: https://nuxt.com/docs/guide/going-further/custom-routing
import type { RouterConfig } from '@nuxt/schema'

export default <RouterConfig> {
  routes: (_routes) => [
    {
      name: 'home',
      path: '/',
      component: () => import('~/pages/home.vue').then(r => r.default || r)
    }
  ],
}
  1. Using the pages:extend hook: https://nuxt.com/docs/guide/going-further/custom-routing#using-the-pagesextend-hook

This might be very vue-oriented in terms of pattern (hooks), but I found the ability to hook into the setup process of nuxt very convincing and convenient. A plugin (what they call "module") registers itself as nuxt module which then calls defineNuxtConfig of each plugin. This makes it possible compose different plugins which add add different routes (thinking of a composable CMS e.g.).

The same pattern can be used to extend Nitro to add new API routes or route middlewares.

export default defineNuxtConfig({
  hooks: {
    'pages:extend' (pages) {
      // add a route
      pages.push({
        name: 'profile',
        path: '/profile',
        file: '~/extra-pages/profile.vue'
      })

      // remove routes
      function removePagesMatching (pattern: RegExp, pages: NuxtPage[] = []) {
        const pagesToRemove = []
        for (const page of pages) {
          if (pattern.test(page.file)) {
            pagesToRemove.push(page)
          } else {
            removePagesMatching(pattern, page.children)
          }
        }
        for (const page of pagesToRemove) {
          pages.splice(pages.indexOf(page), 1)
        }
      }
      removePagesMatching(/\.ts$/, pages)
    }
  }
})

Fresh

In fresh plugins can add new routes or middlewares too: https://deno.com/blog/fresh-1.3#adding-routes-andor-middlewares-from-plugins

function myPlugin() {
  return {
    name: "my-plugin",
    middlewares: [
      {
        middleware: { handler: () => new Response("Hello!") },
        path: "/hello",
      },
    ],
    routes: [
      {
        path: "/admin/hello",
        component: () => <p>Hello from /admin/hello</p>,
      },
    ],
  };
}

In a follow-up iteration they are planning to open this up even further: https://github.com/denoland/fresh/issues/1602#issuecomment-1671740190

const app = freshApp({
  plugins: [
    twindPlugin,
    {
      name: "my-inline-plugin",
      setup(app) {
        app.use(req => ...)
      }
    }
}); 
Radiergummi commented 5 months ago

I just found another use case for this. As part of an application I'm working on, I created a fully spec-compliant OAuth authorization server in SvelteKit. This is really gruesome validation work, and something I wouldn't recommend doing unless you really know your way around authorization.
So in short: This is ideally suited to be distributed as a package, to enable other SvelteKit applications to implement OAuth support without requiring domain knowledge. Authorization servers need to expose a few routes, some of them API-only, others with user-facing forms; they need quite a bit of configuration, and database-persisted entities. This means you can't really distribute it as a middleware-only layer—people would need all kinds of customisation, and doing those as native SvelteKit routes and page handlers is a lot more developer-friendly than passing a heap of options to a middleware.

If I had a way to provide this as a package to others, the ecosystem would benefit as a whole.

@Rich-Harris maybe you can take another look at this issue at some point? 🙏

xmlking commented 1 month ago

I am building reusable/publishable Smart UI components ( using Vercel AI SDK) that need associated server API (/api/ai/completions/+server.ts etc), I like to programmatically add those routes to host app.

adamshand commented 1 month ago

Yes please, this is currently my #1 frustration with SvelteKit.

I'm building a bunch of smaller sites for clients which share a lot of routes (RSS, blog, checkout, auth etc). It would be great to not have to manually keep routes in sync across sites as I make changes and improvements to individual routes.