RFC: Expose a server route manifest to allow routing requests to built environments #21212
Replies: 5 comments 11 replies
-
|
A question I have about my own proposal: if I'm authoring a provider plugin (let's say Maybe this is the one part where the solution to "need 1" intersects with this? By having, let's say, an explicit way to identify a Environment's server entry point (there is zero or one, remember), one can formulate the invariant that a server Environment with a server entry point must have at least one route in the manifest, and server Environments with no server entry points can be safely ignored by the provider plugin at build time. 🤔 |
Beta Was this translation helpful? Give feedback.
-
|
I find this all very confusing (I'm a maintainer on Astro). Vite has nothing to do with routing in production. Adding routing things just for dev creates dev/prod differences and those are never good. I'm probably missing something and would like to understand better. Is this for stuff that's only relevant in dev? |
Beta Was this translation helpful? Give feedback.
-
|
I talked to some people offline and understand just a little better. I still have some concerns / questions.
|
Beta Was this translation helpful? Give feedback.
-
|
Nice proposal @serhalp! My main concern is that this makes routing a purely build time concern. I feel that environments should be consistent across dev and build and so the routing info will also be needed in dev. |
Beta Was this translation helpful? Give feedback.
-
|
Hey everyone! 👋 I've been working on finding the most minimal API possible to address point 1 (server entry point location) and point 3 (routing metadata). The core question we're trying to solve is: How can deployment targets (Netlify, Cloudflare, Node, etc.) discover and work with the various server entries that frameworks define? ProposalThe approach I landed on is pretty straightforward: use a shared global // This package exports a global `store` object
import { store } from "@universal-deploy/store";
/* The store is minimal. It's essentially just this:
const store = globalThis[Symbol.for("ud:store")] ||= { entries: [] };
*/
// Registering entries is as simple as pushing to the store
store.entries.push(
{
id: "awesome-framework/standalone",
pattern: "/standalone",
},
{
id: "./src/server/api.ts",
pattern: "/api",
},
);
// Each entry follows this type:
export interface EntryMeta {
/**
* Module identifier for this entry. This can be a filesystem path or a virtual module.
*/
id: string;
/**
* HTTP method(s) to match. When omitted, matches all HTTP methods.
*/
method?: HttpMethod | HttpMethod[];
/**
* Route pattern(s) to match for this entry.
*
* Uses the {@link https://developer.mozilla.org/en-US/docs/Web/API/URLPattern | URLPattern API} syntax.
*/
pattern: URLPatternInput | URLPatternInput[];
/**
* The Vite environment for this entry.
*
* @default ssr
*/
environment?: string;
}Note This proposal isn't set in stone. The main goal here is to show that we can keep things really minimal. Proof of ConceptWe've built a working POC called
UsageFor framework authorsJust call import { store } from "@universal-deploy/store";
export function awesomeFramework(): Plugin[] {
return [
{
name: "awesome-framework",
config() {
store.entries.push(
{
id: "./src/server/api.ts",
pattern: "/api",
},
{
id: "awesome-framework/ssr",
pattern: "/**",
},
);
},
},
];
}For deployment plugin authorsIf the deployment provider needs a single server entry, the easiest approach is to set import { catchAllEntry } from "@universal-deploy/store";
export function awesomeProvider(): Plugin[] {
return [
{
name: "awesome-provider",
enforce: "post",
configEnvironment: {
// Give other plugins time to declare their entries
order: "post",
handler(name, env) {
if (env.consumer !== "server" && name !== "ssr") return;
return {
build: {
rollupOptions: {
input: {
// `virtual:ud:catch-all`
index: catchAllEntry,
},
},
},
};
},
},
},
];
}If the deployment provider supports multiple server entries, read import { store } from "@universal-deploy/store";
export function awesomeProvider(): Plugin[] {
return [
{
name: "awesome-provider",
enforce: "post",
configEnvironment: {
// Give other plugins time to declare their entries
order: "post",
handler(name, env) {
if (env.consumer !== "server" && name !== "ssr") return;
return {
build: {
rollupOptions: {
input: Object.fromEntries(
// Map entries to their destinations
store.entries.map((e) => [
entryDestination(e),
e.id,
]),
),
},
},
};
},
},
},
];
}Note Alternatively to Addressing Concerns"Pushing entries before
|
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
Motivation
One of the (very few!) obstacles to creating a full-featured, self-contained, framework-agnostic Vite plugin is the lack of a Vite API surface indicating which server entry point (if any) a given request should be routed to.
Below is a proposal to introduce such information. This will allow for use cases like separate SSR/RSC entry points, arbitrary server bundle splitting logic, granular per-route rendering modes, and avoiding invoking unnecessary compute to serve 404s for unknown paths.
Note
For a much more detailed deep dive into the motivation for this, see #20907. I'll keep it concise here.
This RFC is a concrete proposal to address "need 3" from the above, where there was broad ecosystem alignment on the need and the appropriateness of solving it in Vite core.
Proposal
Add a new method
addRoutesto theViteBuilderprototype (called by frameworks) and a new argumentrouteManifestto thebuildApphook (read by providers).Note
Alternative name ideas:
routes,routesByEnvironmentThis leverages the web platform URL Pattern standard to flexibly, agnostically specify route patterns.
Usage examples
In a metaframework:
In a provider plugin:
Discussion
Assumptions
The proposal above introduces the assumption that a Vite Environment has zero or one server entry point. As such, it encodes no direct relationship between requests and server entry points. Rather, a request is routed to one or more Environments (for example, a middleware environment and an SSR environment), each of which has an entry point.
This seems like a reasonable invariant to impose, because:
serverBundlesoption results in one Environment per server bundle.Alternatives considered
Leverage the
configResolvedhookThis is too early in the lifecycle for many metaframeworks to have resolved the necessary information, which may be a byproduct of a full app build.
Leverage
writeBundleand/orgenerateBundlehooksAs I understand it, Vite avoids extending Rollup types by design.
Attach route manifest to
Environmentinstancesthis.environmentis already available to plugin build hooks, making this appealing.But this felt smelly to me, as the app's server route manifest is not an inherent property of a given environment, but rather a property of the built app that happens to be data keyed by Environments.
Shout out to @jamesopstad for inspiring some of this here
Beta Was this translation helpful? Give feedback.
All reactions