kit icon indicating copy to clipboard operation
kit copied to clipboard

Enable package-provided routes

Open Radiergummi opened this issue 2 years ago • 7 comments

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

Radiergummi avatar Feb 06 '23 09:02 Radiergummi

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

lettucebowler avatar Feb 10 '23 18:02 lettucebowler

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.

saabi avatar Mar 23 '23 06:03 saabi

This is something we're looking for too

Tam2 avatar Jul 31 '23 14:07 Tam2

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 => ...)
      }
    }
}); 

gu-stav avatar Sep 05 '23 18:09 gu-stav

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? 🙏

Radiergummi avatar Jun 11 '24 17:06 Radiergummi

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.

xmlking avatar Oct 06 '24 16:10 xmlking

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.

adamshand avatar Oct 14 '24 06:10 adamshand

In general, it would be nice to have a programmatic way to define routes. This would allow libraries to export functions that take a svelte kit context as parameter and add routes to it. The function could be called by an app or a plugin to inject the routes.

It would also allow app developers to quickly create routes within a single file, instead of having to create a folder and a file +server.ts, for each route.

t1u1 avatar Jan 03 '25 10:01 t1u1

For those that are interested, this setup is roughly how https://github.com/evidence-dev/evidence is architected.

The user brings a small number of files and the rest are provided by the library.

In terms of implementation, it works around the described issue by scaffolding a hidden svelte kit app in a .evidence directory, and makes liberal use of fs.copyFileSync to put the user files into this app.

When the users runs the app, they actually are running the app in the hidden directory. https://github.com/evidence-dev/evidence/blob/next/packages/evidence/cli.js

archiewood avatar Jan 08 '25 04:01 archiewood

Is there any plan to work on this feature?

B-Esmaili avatar Apr 12 '25 18:04 B-Esmaili

For those that are interested, this setup is roughly how https://github.com/evidence-dev/evidence is architected.

The user brings a small number of files and the rest are provided by the library.

In terms of implementation, it works around the described issue by scaffolding a hidden svelte kit app in a .evidence directory, and makes liberal use of fs.copyFileSync to put the user files into this app.

When the users runs the app, they actually are running the app in the hidden directory. https://github.com/evidence-dev/evidence/blob/next/packages/evidence/cli.js

Ah, that's a neat solution. I might steal it!

Radiergummi avatar Apr 12 '25 20:04 Radiergummi

Are there any plans for this?

ainsleyclark avatar Sep 13 '25 05:09 ainsleyclark

Will this be obsolete with remote functions maybe?

ptrxyz avatar Oct 17 '25 11:10 ptrxyz

Will this be obsolete with remote functions maybe?

No, because remote functions only offer to solvelibrary managed API endpoints but not page routes.

adamjkb avatar Oct 17 '25 12:10 adamjkb

I've been considering this recently, and Claude suggested an interesting approach: a Vite plugin could generate the routes at build-time. Based on this inspiration, here's a wild idea how to allow shipping routes from packages...

The package maintainer:

  1. Puts routes in lib/routes or some other standard location
  2. Somehow specifies configuration options
  3. These routes get get compiled into a Vite plugin which injects the routes at build-time

The package user:

  1. Imports the Vite plugin from the package
  2. Adds the plugin to their Vite config and optionally configures it
  3. Enjoys their new routes

Configuration options are passed to the routes are runtime.

I think the approach is promising, even if there are open questions:

  1. Do we need some ability to some hooks.server.ts that only applies to these routes? How would it interop with the user's hooks?
  2. Is there a need for some build-time config options? The main one I think about is a route prefix, which you almost certainly want to allow users to configure.
  3. How does this plugin interop with the SvelteKit plugin?

ixxie avatar Oct 19 '25 09:10 ixxie