XiNiHa / vilay

Vite SSR framework with support for React SSR streaming and Relay
https://vilay.xiniha.dev
MIT License
55 stars 4 forks source link

provide a custom server context handler to enable authn/authz and other server middlewares #27

Open acao opened 2 years ago

acao commented 2 years ago

We need to be able to allow users to asynchronously build the initial page context before renderPage in each of the dev server, node and cloudflare environments. Something similar to this:

https://github.com/iMrDJAi/vps-nextauth-example/blob/main/server/index.ts

So - I think we can do this with page modules!

export async function render(pageContext: PageContextBuiltIn & PageContext) {
  if (pageContext?.exports?.createServerPageContext) {
    pageContext = await pageContext.exports.createServerPageContext(
      pageContext
    )
  }

  const { initialCompletion, getStoreSource } = renderReact(pageContext)

then the user doesn't need to override _default.page.server.tsx

XiNiHa commented 2 years ago

Isn't it more convenient to have them in onBeforeRender placed inside _default.page.server.tsx? Can you explain a bit more why are you trying to make the change? Some sample code would be greatly appreciated.

acao commented 2 years ago

oh that will probably do I think? I was hoping to have access to the request object I forgot to mention, and I overlooked when I edited the original issue and changed the solution (much more context there)

I was hoping to do something like this, where we can handle jwt-style per-request authorization

https://vite-plugin-ssr.com/auth

I was thinking that users might want to be able to handle requests at the server level, attach middlewares, etc, looking something like this:

export const nodeExpressMiddleware = (req, res, pageContext) =>  {
    MyAuthMiddleware(req, res)
   // res so you can set headers/etc here as well
    return pageContext
 }

 export const cloudflareMiddleware = (req, res, pageContext) =>  {
   ...
 }

// fastify calls them plugins, but same concept
 export const nodeFastifyMiddleware = (req, res, pageContext) =>  {
   ....
 }

if you notice, the cited VPS vite examples each have a custom server that handles this at the middleware level i was just hoping to make it easy for users to extend the provided vilay servers.

XiNiHa commented 2 years ago

Oh right, a middleware API would be very nice to have. I'll look at what NextJS is doing and design the API based on it, with further expansion considered in mind (like isomorphic GraphQL resolver definition)

acao commented 2 years ago

nice ok! flarereact & remix have good cloudflare-centric approaches to this as well I think

acao commented 2 years ago

More than just being able to add middlewares, I also need req and pageContext and to be able to mutate the initial server page context. something like this (for cloudflare at least):

...
export async function render(req: Request, renderVps: typeof renderPage) {
  const cookieHeader = req.headers.get('Cookie')
  let pageContextInit = {
    url: req.url,
    cookies: cookieHeader != null ? cookie.parse(cookieHeader) : undefined,
    userAgent: req.headers.get('User-Agent'),
    fetch,
  };
  // supply this somehow with esbuild/vite config based on whether certain files are present relative to the cli `cwd()`?
  if((globalThis as any)?.onPageContextInit){
    // provide both req and res so that middlewares can be added before returning page context
    // another option - merge the result page context with the init above, a return pattern ala vps methods
    // so you don't accidentally break things, like forget to pass on the `url`. 
    // also if you don't return anything it still works, and you can mount middlewares despite that
    pageContextInit = await (globalThis as any).onPageContextInit(req, res, pageContextInit)
  }

  const pageContext = await renderVps<
    { redirectTo?: string },
    typeof pageContextInit
  >(pageContextInit)
  ...
 }
acao commented 2 years ago

in my original writeup for this issue i proposed a few options to define this config - simply define a vilay object in package.json, or even cosmic config for .js and .ts config files, or expect a specific path relative to .cwd(), but i think just specifying a specific file relative to the cwd() is fine, and the framework let esbuild/vite handle it with simple logic instead of hoping some weird tree shaking works out with user provided config and dynamic import paths.

letting esbuild/vite load the file as needed without user config outside page modules also lets it remain an internal framework detail, so you are free to introduce upstream optimizations and features to the server stack while still providing a powerful middleware layer. The framework will continue to take care of bundling the server source as well, making those build optimizations and runtime compatibility a built-in framework feature

this config could load from a few places:

  1. .vilay/server/cloudflare.ts or .vilay/server/node.ts?
  2. vilay-server/cloudflare.ts or vilay-server/node.ts?
  3. any other ideas?