Skip to content

Enable package-provided routes #8896

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
Radiergummi opened this issue Feb 6, 2023 · 11 comments
Open

Enable package-provided routes #8896

Radiergummi opened this issue Feb 6, 2023 · 11 comments
Labels
feature / enhancement New feature or request
Milestone

Comments

@Radiergummi
Copy link

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

@Rich-Harris Rich-Harris added the feature / enhancement New feature or request label Feb 6, 2023
@Rich-Harris Rich-Harris added this to the later milestone Feb 6, 2023
@lettucebowler
Copy link
Contributor

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
Copy link

saabi commented Mar 23, 2023

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

This comment has been minimized.

@gu-stav
Copy link

gu-stav commented Sep 5, 2023

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: denoland/fresh#1602 (comment)

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

@Radiergummi
Copy link
Author

Radiergummi commented Jun 11, 2024

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
Copy link

xmlking commented Oct 6, 2024

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
Copy link
Contributor

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.

@t1u1
Copy link

t1u1 commented Jan 3, 2025

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.

@archiewood
Copy link

archiewood commented Jan 8, 2025

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

@B-Esmaili

This comment has been minimized.

@Radiergummi
Copy link
Author

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!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature / enhancement New feature or request
Projects
None yet
Development

No branches or pull requests