Closed brillout closed 2 years ago
The user can choose between manually integrating tools, and/or using so-called plugs.
// server.js
// Manual integration
export { handler }
// The Telefunc middleware that exists today
import { telefunc } from 'telefunc'
async function handler(request) {
const { url, method } = request
const { pathname } = new URL(url)
if (pathname === '/_telefunc') {
const body = await request.text()
const resp = await telefunc({ url, method, body })
return new Response(resp.body, {
headers: { 'content-type': resp.contentType },
status: resp.statusCode,
})
}
// Same with Rakkas/VPS
// ...
}
This code is server agnostic: it can run in Cloudflare Workers, in an Express.js server, etc.
// server.js
// With plugs
export { handler }
import { createHandler } from 'vavite'
import { telefunc } from 'telefunc/vavite' // Telefunc would provide a vavite plug
import { rakkas } from 'rakkas/vavite' // Rakkas's vavite plug
// This returns a function with the same format than above: `Request => Promise<Response>`
const handler = createHandler([telefunc(), rakkas()])
Same here, this code is server agnostic
Or a combination:
// server.js
export const async function handler(request) {
if( someCondition ) {
// Do something custom
} else {
return createHandler([telefunc(), rakkas()])
}
}
// worker.js
// Cloudflare Workers
import { vavite } from 'vavite/cloudflare-workers' // Vavite's adapter for Cloudflare Workers
import { handler } from './server'
addEventListener('fetch', vavite(handler))
The user keeps full control over the worker
// express.js
// Express.js
// We use ESM with `package.json['type'] === 'module'`
import express from 'express'
import { vavite } from 'vavite/express' // Vavite's adapter for Express.js
import { handler } from './server'
const app = express()
app.use(vavite(handler))
app.listen(3000)
Here again the user keeps full control over the Express.js server
In the future, we can think of a zero-config thing, if we want. (I agree that Remix's approach of showing integration code makes sense; I still think there are use cases where zero-config makes sense though.)
// Zero-config mode, the user doesn't write any server code
{
"scripts": {
"dev": "vavite dev"
},
"dependencies": {
"rakkas": "*",
"telefunc": "*"
}
}
vavite
could automatically retrieve vavite plugs, e.g. with:
const packageJson = require('./path/to/user/package.json')
const plugs = Object.keys(packageJson.dependencies).map(dep => {
try {
const { __internal_self_installing_plug } = require(`${dep}/vavite`)
return __internal_self_installing_plug
} catch(_) {
return null
}
})
I purposely didn't include the Vite reloader: the entire thing is completely independent of Vite.
From an architectural point of view, all the vavite
code does is to take a server agnostic handler and adapts it into a middleware for Express.js / Cloudflare Workers / etc. That's it: there is no concept of transpiling going on here.
That said, there would be a plugin vavite/vite
, which would basically be the same than vavite/express
or vavite/cloudflare-workers
but for Vite's dev server:
// vite.config.js
import { vavite } from 'vavite/vite'
export default {
// This installs the `handler()` defined in `server.js` to Vite's dev server.
plugins: [ vavite('server.js') ]
}
For HMR we can use
vite.ssrLoadModule()
as usual; no need to use thehttpServer.on("request", app)
trick since we don't use Express.js at all.
I'm thinking maybe we should rename the project? I actually love the name vavite
but maybe it's a bit confusing if it's agnostic to Vite?
An interesting thing is that with vavite/cloudflare-worker
and vavite/vite
, we don't need Express.js anymore.
I was hoping that we would create an ecosystem of middlewares.
Yes. Any tool that works with the vavite server-agnostic format will automatically work with any deploy provider that has a vavite adapter.
For example, it's enough for NextAuth.js to be vavite-compatible to support all deploy environments. (FYI NextAuth.js currenlty doesn't support Cloudflare Workers.)
There is still a use case for Express.js for tools that don't support vavite yet. But vavite will eventually kill Express.js.
Epxress.js will finally die :D.
We basically don't need the RFC anymore...
All we need is the goold old ticket #5935. (Merging vite build --ssr
into $ vite build
and enabling custom build steps.)
The following would just work for Vite + Rakkas/VPS + Telefunc + Cloudflare Workers:
// vite.config.js
import { vavite } from 'vavite/vite'
import { cloudflareWorkers } from 'vite-plugin-cloudflare-workers'
export default {
plugins: [
// `server.js` is the file above (which integrates Rakkas + Telefunc)
// This installs `handler()` to Vite's dev server.
vavite('server.js'),
// `worker.js` is the file above (also integrates Rakkas + Telefunc)
// This adds a custom `esbuild` bundling step to `$ vite build`
cloudflareWorkers('worker.js')
]
}
Note that
vite-plugin-cloudflare-workers
is completely agnostic to vavite sinceworker.js
is a normal Cloudflare Worker.The following happens when the user does
$ vite build
:
$ vite build
(without--ssr
)$ vite build --ssr
esbuild
for bundlingworker.js
into a single file
That's it!
With zero-config mode, things would just work without the user having to write a single line of server code... We may want to work on zero-config just so that SvelteKit, Nuxt, etc. start using vavite (they are relentless about users not having to write a single line of integration code).
I think that nails it. Or am I missing something here?
Vavite is going to be big :-).
// server.js
// This is hatTip code, which is:
// 1. Completely agnostic to any server environment (Express.js, Cloudflare Workers, etc.)
// 2. Completely agnostic to any bundler (Vite, webpack, etc.)
import { createHandler } from 'hattip'
import { telefunc } from 'telefunc/hattip' // Telefunc provides an hatTip adapter.
// Such adatper is completely agnostic to Vite.
import { rakkas } from 'rakkas/hattip' // Rakkas's hatTip adapter
// Returns a function with the format `Request => Promise<Response>`
export const handler = createHandler([telefunc(), rakkas()])
// worker.js
// Cloudflare Workers
import { hattip } from 'hattip/cloudflare-workers' // hatTip's adapter for Cloudflare Workers
import { handler } from './server'
addEventListener('fetch', hattip(handler))
// vite.config.js
import { hattip } from 'hattip/vite'
import { cloudflareWorkers } from 'vite-plugin-cloudflare-workers'
export default {
plugins: [
// This basically installs `handler()` to Vite's dev server.
hattip('server.js'),
// The `vite-plugin-cloudflare-workers` is completely agnostic to hatTip
// - `worker.js` is just a normal Cloudflare Workers
cloudflareWorkers('worker.js')
]
}
The ideas are implemented by https://github.com/hattipjs/hattip.
Authors: @cyco130, @brillout. (Let us know if you want to join us in writting this RFC.)
Structure of this RFC:
Motivation
Vite's native SSR API has enabled deep collaboration between SSR frameworks.
This RFC enables the same for deploy: there are an increasing number of deploy targets (Deno Edge, Supabase Functions, Netlify Functions, Vercel, Cloudflare Workers, AWS Lamda, AWS EC2, ...) and, instead of having each SSR framework reinventing the wheel, this RFC enables a common ecosystem of deploy integrations.
Deploy integration is deeply intertwined with the topic of how Vite should handle the server.
That's why this RFC is called
Vite Server RFC
. But the most important consequence of this RFC is a shared ecosystem of deploy integrations.Vite is becoming the "Web Compiler Commons" that powers a flourishing ecosystem of tools and frameworks. This RFC spurs that.
High-Level Goal
The goal is to enable the following DX.
We show an example for Express.js and Cloudflare Workers.
Express.js Example
The
server.js
file above showcases Middleware Mode: the user manually sets up and controls the server. Middleware Mode is needed for advanced integrations such as Stripe. (This example makes Stripe integration seem easy, but a real world integration is more complex and warrants the need for Middleware Mode.)The goal of this RFC is to also support Server Mode: the user uses Vite's built-in dev server. This means that, with Server Mode, the user doesn't have to write the
server.js
file.Server Mode is the default but the user can opt-in Middleware Mode by setting
vite.config.js#server
.CLI integration:
The CLI command
$ vite
executes the server entry defined atvite.config.js#server
(or, if missing, Vite's built-in server). It would support server HMR.The CLI command
$ vite build
builds not only the browser-side JavaScript, but also the server entry defined atvite.config.js#server
. (The user can then, for example, use the server production build for a production server on AWS EC2.)Cloudflare Workers Example
CLI integration:
The CLI
vite build
takes care of everything, including bundlingworker.js
into a single file. (Plugins, such asvite-plugin-cloudflare-workers
, can add a custom build step.)Plugins, such as
vite-plugin-cloudflare-workers
, can provide a$ vite deploy
implementation.Plan
We progressively implement parts of this RFC.
For example, we can start by implementing:
$ vite deploy
.We progressively include implemented RFC parts into Vite releases. We don't foresee any breaking changes; we can hook into but not disrupt Vite's release lifecycle.