Single File Page. (Being able to define everything in a single file .page.js.)

Instead of having several page files (.page.js, .page.route.js, .page.server.js, .page.client.js), we would define everything in .page.js (while vite-plugin-ssr automatically statically extracts the relevant parts like in https://next-code-elimination.vercel.app.)
Many people have expressed a longing for this, but I'm on the fence. Simply because I highly value clarity and simplicity: it's obvious and simple what the .page.js, .page.route.js, .page.server.js, and .page.client.js files are about, whereas if we merge everything in .page.js it becomes less clear what is run in what environment.

When I stumbled across this project my immediate thought was "I could make a clone of Remix with this" https://remix.run/
I really like the clean API they have https://www.youtube.com/watch?v=4dOAFJUOi-s&t=1281s

@deckchairlabs what is it you like abour their API? Glazed over the first minutes of the video; didn't see anything special.

Yeah, being able to define everything in *.page.ts files would be so great.

I'm on the fence. Simply because I highly value clarity and simplicity: it's obvious and simple what the .page.js, .page.route.js, .page.server.js, and .page.client.js files are about, whereas if we merge everything in .page.js it becomes less clear what is run in what environment.

@chrisvariety

having it all in one file and shaking out the server-side dependencies (see https://next-code-elimination.vercel.app ) could be a nice enhancement.

I'm on the fence. Simply because I highly value clarity and simplicity: it's obvious and simple what the .page.js, .page.route.js, .page.server.js, and .page.client.js files are about, whereas if we merge everything in .page.js it becomes less clear what is run in what environment.

One thing we could do is to remove the .page.route.js file (instead, the user export { route } in .page.js). We then have:

.page.js => Node.js & Browser
.page.server.js => Node.js
.page.client.js => Browser

The user quickly and naturally understands that the page types .page.js VS .page.server.js VS .page.client.js are only about where code runs. (As things currently are, the .page.route.js adds semantics to what .page.* types are about and can lead to confusion.)
Isn't it annoying to have to create all these files?
Almost always the .page.client.js is written only once as _default.page.client.js. So the user ends up having to write a max of 2 files (.page.js and .page.server.js) per page, instead of only one file per page. Yes, it's slightly annoying to have to create 2 files instead of one, but it would, IMO, be worth it in terms of clarity.

@brillout

@deckchairlabs what is it you like abour their API? Glazed over the first minutes of the video; didn't see anything special.

I suppose what I like is the fact they separate the "Loading" of data (addContextProps) and handling of an incoming "Action" (form POST for instance). Although in saying that, I do like that with this plugin, there isn't as much "magic" as appears to be happening with Remix.
In my little playground I'm handling POST requests within a *.page.server.ts within addContextProps.
https://github.com/deckchairlabs/vite-remix-clone/blob/main/pages/posts.page.server.ts#L11

I do like that with this plugin, there isn't as much "magic" as appears to be happening with Remix.

In my little playground I'm handling POST requests within a *.page.server.ts within addContextProps.

It's actually a common practice to do all kinds of things in addContextProps(). An alternative is to do it before renderPage() is called.

https://github.com/deckchairlabs/vite-remix-clone 😍 Are you doing it for yourself or do you want to make it an open source proejct?

At the moment just playing around, but eventually!

This plugin is great! And I think the default behavior is very clear and easy to reason about, especially with the well written docs.
However, I would love the ability to hook into the plugin's get page functionality, so that if someone wanted to have more control (and maybe implement a single file page), then they have the possibility of doing so at their own risk.
This could be a dumb idea, but I would love to know what you think?

@doeixd We can start by removing the .page.route.js file and see how it goes. I'm not sure that we'll achieve to remove .page.server.js and .page.client.js in a 1. DX friendly way, and 2. without too much burden on the vite-plugin-ssr source code; things should stay simple. But I'm happy to explore this area. To get started you can make yourself familiar with Vite/rollup's architecture, e.g. https://vitejs.dev/guide/api-plugin.html#transforming-custom-file-types.

I also definitely prefer the clarity of having separate server and client files. This has been so much nicer to work with than other frameworks that blur the line between server and client.

Yes, that's something I highly value. This actually has been my main reason for pushing back against the many people who expressed a longing for having everything defined in a single file. But I can see that there is a solution here. For example, if the default is to have separate files, the docs always use sperate files, and having everything defined in a single file would merely be a convenience for "pro" users that know what they are doing.

I think it's actually clearer having the route in a separate file. There's no confusion as to whether it's isomorphic avoiding questions like "does the route information go in .server or .client?", "can it go in both?"

That's a good point. Although strong docs can go a long way in clearing this kind of confusion out: simple and strong contracts between user and vite-plugin-ssr, API docs that are clear and complete, and guides that reflect the path of least resistance for 90% of use cases, while those 10% complex use cases are expected to (and usually inherently) require brain power from the user.

But I can see that there is a solution here. For example, if the default is to have separate files, the docs always use sperate files, and having everything defined in a single file would merely be a convenience for "pro" users that know what they are doing.

Supporting both sounds like a good call. It of course increases the complexity of the codebase a bit, but if it's serving the preferences of a significant body of users, I suppose it is a relatively harmless addition.
One other point about the route file: I was going to suggest at some point that it should support named exports, in order to accommodate additional route configuration (for example, route name - this is the big one that would be convenient for Vue Router users). It would still be possible to do this in a single page file, but it would require more specific naming e.g. instead of
name, routeName.

I also definitely prefer the clarity of having separate server and client files. This has been so much nicer to work with than other frameworks that blur the line between server and client.

This is a good point! Although I think it is possible to make a single file page, where it is clear where client/server functionality takes place. Just look at the success of projects like Next.js, Remix.js, and SvelteKit. They all handle this by exporting functions that clearly describe where the functionality takes place. Personally, I think something like this is just as clear as having separate files.

Supporting both sounds like a good call. It of course increases the complexity of the codebase a bit, but if it's serving the preferences of a significant body of users, I suppose it is a relatively harmless addition.

This is a great point! This functionality would require significant additional effort which may be out of scope for this project. And not adding it makes a lot of sense.

Agreed.

Personally, I think something like this is just as clear

I agree.

increases the complexity of the codebase a bit

It may not increase the complexity much if it ends up only being a preprocessing step that is independent of the rest of the code.

I also definitely prefer the clarity of having separate server and client files. This has been so much nicer to work with than other frameworks that blur the line between server and client.

This is a good point! Although I think it is possible to make a single file page, where it is clear where client/server functionality takes place. Just look at the success of projects like Next.js, Remix.js, and SvelteKit. They all handle this by exporting functions that clearly describe where the functionality takes place. Personally, I think something like this is just as clear as having separate files. All the functionality of the page is easily accessible and doesn't require 4 separate files for a simple page.

Supporting both sounds like a good call. It of course increases the complexity of the codebase a bit, but if it's serving the preferences of a significant body of users, I suppose it is a relatively harmless addition.

This is a great point! This functionality would require significant additional effort which may be out of scope for this project. And not adding it makes a lot of sense.

It gets less clear when you have dependencies. How can I be guaranteed that node-fetch isn't making it into my client bundle? Magic?

@gryphonmyers pretty much yep, magic! see e.g. https://next-code-elimination.vercel.app ✨

I personally hate magic 😂. Anyway, my opinions aside, if @brillout isn't worried about supporting this as an additional supported project structure then I guess we can all have our preferred project structures!

I guess we can agree that:

Docs would still be based on separate files to keep things clear.
SFP (Single File Page) would be optional.

I will not implement SFP myself but I will happily accept a PR.
If anyone wants to work on SFP talk to me (on Discord or here), I'm happy to give guidance. @doeixd let me know if you are still interested on working on this.

Re-opening because I see situations (especially with vps frameworks) where this would be quite nice.
E.g. being able to seamlessly opt-in between SPA and SSR pages.
@AaronBeaudoin WDYT of following design?
// /pages/spa.page.js

// We mark `doNotPrerender` as server-side (vps generates a virtual module
// `spa.page.server.js` and moves `doNotPrerender` over there).
// @server
export const doNotPrerender = true

// We mark `Page` as client-side (vps generates a virtual module
// `spa.page.client.js` and moves `Page` over there).
// @client
export { Page }

// We don't mark this export => it stays in this `spa.page.js` module.
export const title = "An SPA page"

This means that Page and doNotPrerender are removed from spa.page.js.
The whole thing is fairly easy to achieve. (Vite is designed to easily implement things like this, e.g. Vue's SFCs.)
The only thing that is non-trivial is code pruning:
// /pages/spa.page.js

// This import also needs to be pruned
import something from 'some-dependency'

// `Page` is moved to a virtual module `spa.page.client.js`. In other words: it's
// pruned from `spa.page.js`.
// @client
export function Page() {
  // `something()` is only used by `Page`
  something()
}

But it's definitely doable and has been done before (e.g. Next.js or @cyco130's Rakkas).
Vite uses acorn, so ideally we would use acorn as well for this (reducing the total number of dependencies).
@cyco130 I guess you used Babel because you were more familiar with it? Nothing speaks against Acorn, right?

@cyco130 I guess you used Babel because you were more familiar with it? Nothing speaks against Acorn, right?

I would think so. But Vite's React plugin already uses Babel, that's why I started there.
Here's my implementation and its tests. It strips headers, prerender, and action functions from the client bundle.  I just pass this plugin to vite-plugin-react's babelPlugins option (more or less, an options.ssr test is needed which has been merged recently).

Ok that makes sense. (Although Aaron is using Vue so he probably won't want Babel to clutter his node_modules.)
Thanks for the pointers.

Overall, this seems like a very sensible approach IMO. Definitely in the right direction. However I would modify it as follows.

Maybe this is just because of my use case, but I honestly feel like most of the time the user expects their exports to be available anywhere. So I think what you suggested is amazing, but could be modified like this:
// Behavior for the `Page` export remains the same as currently,
// so if you're in `.page.xyz` then `Page` is exported on both
// the server and the client, in `.page.server.xyz` only the
// server, and in `.page.client.xyz` only the client.
export { Page };

// All other exports are made available on both the server and the
// client by default. So, whether I'm in `.page.xyz`, `.page.server.xyz`
// or `.page.client.xyz`, the below export will be available.

// - If we are in `.page.xyz` behavior is the same as currently.
// - If we are in `.page.server.xyz`, it adds the export to the client too.
// - If we are in `.page.client.xyz`, it adds the export to the server too.
export const title = "Some Title";

// ————————————————————————————————————————————————————————————

// If you want an export to be available on only one side, that's where
// the @somewhere comments come in. Rather than "moving" an export,
// semantically now what we are doing is "scoping" an export.

// The below export is available only on the server. This means that:
// - If we are in `.page.xyz` it removes the export from the client.
// - If we are in `.page.server.xyz`, it does nothing.
// - If we are in `.page.client.xyz`, it moves the export to the server.

// @server-only
export const onlyOnServer = "Hello";

// The below export is available only on the client. This means that:
// - If we are in `.page.xyz` it removes the export from the server.
// - If we are in `.page.server.xyz`, it moves the export to the client.
// - If we are in `.page.client.xyz`, it does nothing.

// @client-only
export const onlyOnClient = "World";

// ————————————————————————————————————————————————————————————

// Special VPS exports have implicit defaults.
// Here are some examples:

// This export implicitly has `// @server-only`.
export const doNotPrerender = true;

// This export implicitly has `// @server-only`.
export async function prerender() { ... }

// This export implicitly has `// @server-only`, of course.
export const passToClient = [ ... ];

// This export implicitly follows the filename.
// If in `.page.xyz`, a comment can be added to "scope" it.
// Perhaps optional `renderServer` and `renderClient`
// exports could also be added "scope" this anywhere?
// (Because you can have two exports with the same name.)
export async function render(pageContext) { ... }

// This export implicitly has `// @server-only`,
// but `// @server-and-client` can be added to change behavior.
export async function onBeforeRender(pageContext) { ... }

// This export implicitly has `// @client-only`, of course.
export const clientRouting = true;

In my opinion, this would be super intuitive. Technically it would mean you could even have a single _default.page.[js|ts] file, which is kind of cool, I guess.
It also has another major effect for Vue users. For Vue single-file components, the whole export { Page } is abstracted away by Vue, so I have no idea where would you put a // @server-only or // @client-only comment in a Vue SFC to have it be above the Page export. For this reason, the modification I suggested above is a natural solution.
The net result semantically is that your filename .page.xyz vs .page.server.xyz vs .page.client.xyz is always only setting where your Page export is available (it would also affect the behavior of the special VPS render export, but in most cases this won't matter). You *can't add a // @server-only or // @client-only comment to scope the Page export; the only way to scope Page is by changing the filename, making it special.
That's it. All other exports are available anywhere unless "scoped" using a // @server-only or // @client-only comment, and the special VPS ones just have logical implicit defaults.
What do you think? Hopefully this wouldn't break something I am not aware of.

👌 I really like your proposals 💯.
Especially the implicit default idea. Brilliant👌. We could even extend the idea to Custom Exports:
// vite.config.js

export default {
  ssr({
    customExports: {
      // Configure `export { query }` to live only on the server-side
      query: {
        defaultEnv: 'server'
      }
    }
  })
}

For Vue SFCs: maybe <template server> (HTML-only) / <template client> (SPA) / <template> (SSR) could be an option.

<template client>

Hello

</template>

Which would be the equivalent of:
// /pages/hello.page.js

// @client
export { Page }

I also like @client-only, it's more explicit. I'm just a little bit hesitant simply because @client is shorter than @client-only. But I think you are right: clarity is more important than saving keystrokes.

All-in-all, I think it would be quite ergonomic if we can enable the user to always create .page.js files while never having to create .page.client.js / .page.server.js / .page.route.js files.
For example, switching between SPA and SSR would only be a matter of switching between <template client> and <tempalte>. No more $ mv hello.page.vue hello.page.client.vue needed 👌.

(Also, @server-and-client is great naming ✨. Much much better than @isomorphic which I had in mind.)

As much as I'd love to have the whole <template server> or <template client> thing being a Vue guy myself, I almost feel like it's maybe a bit too framework specific, and users not super familiar with VPS or Vue might find themselves incorrectly looking in the Vue SFC docs to find out what that means. As I've used VPS, I've actually kind of come to like the fact that looking at the filename is enough to tell what mode the page runs in. The only thing I'm really going for personally is being able to have only one file for each page. Also, other frameworks (like Svelte) wouldn't also need extra logic if they use their own file syntax like Vue SFCs.
Also, allowing multiple files is sometimes still important (and intuitive) for cases such as HTML-only with some custom JS. To get that situation all in one file you'd need some sort of clientScript export function or something to put your client-side JS in, unless I'm missing something. And that might actually be pretty nice, but it kind of doesn't feel as clean as just have a page.client.js file. I think both options should still be available.
And yeah, @server-only, @client-only and @server-and-client (which you'd only ever need for onBeforeRender) are the most explicit and intuitive names I can think of.

That's some good arguments. I'll think about them.

@server-only, @client-only

Actually, I'm a little bit on the fence here; I think @server and @client is explicit enough.
I think, a user unfamiliar with vps will correctly guess what the following means.
// /pages/about.page.js

// @client
export { Page }

// @server
export { onBeforeRender }

The @ prefix helps a lot. @client = at client. Pretty clear, I think.

I still feel drawn to only having .page.js files...
Let me elaborate.
SFCs

users not super familiar with VPS or Vue might find themselves incorrectly looking in the Vue SFC docs to find out what that means

How about this instead?
<template>

</template>

Also, how about this:
// /renderer/_default.page.js

// Override the implicit default: make all pages SPA by default
export const exportsEnvironment = {
  Page: '@client'
}

// /marketing/_default.page.js

// Make all marketing pages SSR
export const exportsEnvironment = {
  Page: '@server-and-client'
}

This means that the user doesn't have to set @ markers to Page anymore. The only time the user would use  is when he wants to opt into SPA for only a single page, but that's pretty rare. Actually, he could create a _default.page.js next to about.page.vue.
All-in-all I would even say that it's fine if we don't even support .page.vue files. Which also addresses your concerns about other UI frameworks.
HTML-only

HTML-only with some custom JS. [...] I personally don't think is as clean as having a separate file.

I agree, how about this then:
// @client
import './my-client-side.js'

This means the the client-side custom JS does lives in an extra file, namely my-client-side.js.
(Note that we'd also need something like this for your design. If I understand your design correclty then .page.client.js is equivalent to .page.server.js with the exception of where Page lives, so we also need to determine which code goes the client-side.)
Render modes, made easier
Also, I think the idea of only having .page.js combined with implicit defaults can turn out to be quite powerful: the user only cares about defining .page.js files, while vps moves code in the right environment by default.
Actually, I'm just thinking, maybe we can make render modes as simple as this:
// _default.page.ts

export const renderMode: 'SPA' | 'SSR' | 'HTML' = 'SPA'

For example export const renderMode = 'HTML' would set @server to Page and @client to all imports like import './my-client-side.js' (JavaScript imports that don't import any value).

Buckle up, this is a long one.
I think we're definitely moving in the right direction here. 👍
The last part is my favorite. You're kind of starting to move in the direction of my "perfect" framework. Some sort of single export that "automagically" sets the configuration for the page, but doesn't prevent the user from going deeper if they want. All that's really happening is sensible defaults are being set.
That really aligns with my personal philosophy about simplifying configuration: Zero-config doesn't mean no-config, it means optional-config. It just means sensible defaults. The config is just for when you need to override those defaults.
So if you'll bear with me, I'm going to go wild and just describe my "perfect" config scenario for VPS. With complete imaginary freedom, for me it looks something like this:
// ————————————————————————————————————————————————————————————
// [name].page.js
// ————————————————————————————————————————————————————————————

// A single `rendering` export holds all of the VPS config for this
// page. It is totally optional. Export it from a `_rendering.page.js`
// file in the same directory instead if you want to keep it totally
// separate from the page, and that file will be used as the defaults
// for all pages in the directory. Also, pass it to the VPS plugin in
// `vite.config.js` and never create a `_rendering.page.js` file.
// No more `_default.page.server.js` and `_default.page.client.js`.
// (This means we could create VPS "presets" that set it up to work
// with frameworks without adding a single extra file to the project.)
export const rendering = {

  // If you set this to `server-and-client` (SSR), then `Page` is
  // available in both the `renderServer` and `renderClient` functions,
  // if `client-only` (SPA), just the `renderClient` function, and
  // if `server-only` (HTML), just the `renderServer` function.
  mode: "server-and-client" || "client-only" || "server-only",

  // Same as old `doNotPrerender`.
  // Don't panic, the old `prerender` is coming further down.
  prerender: false,

  // This is a list of exports from pages that will be made available
  // at `context.exports`. Each export will be made available in the
  // environment specified by the key value.
  exports: {
    title: "server-and-client",

    // You'll see what this is for further down.
    route: "server-only"
  },

  // This is a new optional function that helps to make the two functions below
  // a bit more "single concern". The return value will overwrite the `Page`
  // property in `renderServer` and `renderClient` below, so it's "real-world"
  // purpose is to perform the wrapping of your page with a layout.
  transformPage(context) {

    // For Vue.
    return {
      render() {
        return h(Layout, {}, {
          default: () => h(context.Page, context.props || {})
        });
      }
    };

    // For React.
    // (From what I can tell in the examples, I don't use React.)
    return (
      <Layout context={context}>
        <Page {...context.props} />
      </Layout>
    );
  },

  // This is equivalent to the old VPS `render` export from `[name].page.server.js`.
  renderServer(context) {

    // For Vue.
    const page = createSSRApp(context.Page);
    const pageStream = await renderToNodeStream(page);

    // Return value is "dedented", wrapped in `escapeInject`,
    // and minified all automatically by VPS.
    return `
      <!DOCTYPE html>

          <meta charset="UTF-8">
          <meta http-equiv="X-UA-Compatible" content="IE=edge">
          <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1">

          <title>${context.exports.title}</title>

${pageStream}

    `;
  },

  // Same as old `passToClient`.
  passContext: [ ... ],

  // This is equivalent to the old VPS `render` export from `[name].page.client.js`.
  renderClient(context) {
    const page = createSSRApp(context.Page);
    page.mount("#page");
  },

  // This object contains all of the routing specific configuration.
  routing: {

    // Route as a string.
    route: "/film/@id",

    // Route as a function.
    route: context => { ... },

    // Route as a `export const route = "...";` in our pages.
    // This is what `exports.route` up above was for.
    route: context => context.exports.route,

    // Same as old `filesystemRoutingRoot`.
    root: "/",

    // Same as old `clientRouting`.
    client: true,

    // Same as old `prefetchStaticAssets`.
    prefetch: "on-hover" || "on-visible" || false
  },

  // Any VPS function that would start with `on` is a hook.
  // Any future hooks would just be added in here.
  hooks: {
    onBeforeServerRender(context) { ... },
    onBeforeRoute(context) { ... },
    onClientPageTransitionStart() { ... },
    onClientPageTransitionEnd() { ... },

    // Same as old `prerender()`.
    onPrerender() { ... }
  },

  // If a user wants to change the name of the export that VPS will look
  // for to get the config or the page component, they can do that with
  // these config properties. They might do this if they or their framework
  // already uses the `Page` or `rendering` export for something else.
  // All potential naming export naming conflicts are solvable!
  configExport: "rendering",
  pageExport: "Page"
};

// The `rendering` export and this `Page` export are the ONLY exports
// from the page that VPS will consider as "belonging to the framework".
// All old VPS exports like `render`, `prerender`, `doNotPrerender`,
// `onBeforeRender` `passToClient` and whatever else are now properties
// inside of the `rendering` export above.
export { Page };

// Exports other than `rendering` or `Page` are ignored, unless they
// are listed under `rendering.exports`, in which case they will be
// available in the environment specified there.
export const title = "My Page Title";

Context Object
{
  env: "server" || "client" || "hydration",
  Page: FrameworkComponent,
  exports: {},

  route: {
    // Same as old `routeParams`.
    params: ...,

    // Room for potential future stuff...
  },

  url: {
    // Same as old `url`.
    original: ...,

    // Same as old `urlPathname`.
    path: ...,

    // Same as old `urlParsed.search`.
    params: ...,

    // Other stuff in here I don't want to detail, but you get the idea.
  },

  // Same purpose as old `is404`.
  error: null || {
    status: 404 || 500,

    // Arbitrary data user can pass to error when
    // doing `throw RenderErrorPage(...)`? Just an idea.
    data: {}
  }
}

Some Discussion Points

Why didn't you use SSR, SPA and HTML for the modes?
Those terms are commonly used to describe general approaches to rendering a web application, and there's also no standard at all for them. Some people use the term CSR instead of SPA. Technically all pages use HTML. At first impression, "server-side rendering" sounds like exactly what PHP or any other regular server with a templating engine does. But here in VPS, we're always trying to render a page using a JS framework that can render on both the client and the server, so the key question is: Do we want to render it on the server, the client, or both with hydration? I vote we throw the arcane acronyms out the window entirely.

Why no @server or @server-only or whatever comments?
I wasn't in love with this style to begin with. Relying on comments to do actual logic just seems like a bad idea from the start, in my humble opinion. Using the config above, they're no longer necessary.

What about import statements?
Instead of using comments to control the target environment for import statements, I propose a more "Vite-ish" style by using query parameters in the URL string. So like import "/styles/index.css?env=[server|client]"; to scope the import. Without the parameter it is by default in the client for the server-and-client and client-only modes, and by default in the server for server-only mode. I don't know if that would work, though? What are your thoughts?

Didn't you basically just describe a whole new plugin?
Kind of. I did say I was going to go wild. 😅

Why such an extreme change?
VPS's way of looking at page exports is super cool, but it clutters the page with a bunch of exports and has the adverse effect of unescapable potential naming conflicts and verbose export names. Putting the entire config under a single export is so much cleaner IMO. Also it ensure VPS won't conflict with any future framework... I think?

Why did you rename everything??
I just felt like most of the names are more verbose than necessary.  I still think the current VPS is absolutely amazing and I totally love it.

Alright... so, yea, it's quite a brutal change 😀. It took me a whole night's sleep AND a nap to realize that... I like it. A lot :-).
I came to see the beauty of it.

I like that the whole rendering "business logic" is defined in a single file _rendering.page.js. That's super attractive.
I like the clear seperation between the rendering logic VS defining pages. That's great and important in many regards.
I like that every export in /pages/about.page.js needs to be explicity defined in _rendering.page.js. That's solid.

Zero-config doesn't mean no-config, it means optional-config. It just means sensible defaults. The config is just for when you need to override those defaults.

Exactly. You are putting (very well said) words on a feeling of mine I've been having for quite some time now.

no @server or @server-only or whatever comments

:100: You're right.

import "/styles/index.css?env=[server|client]"

Great idea... why didn't I thought of this earlier! :-)

This means we could create VPS "presets" that set it up to work with frameworks without adding a single extra file to the project.

Yes, yes, and yes! That's exactly what I've in mind, since the beginning of vps. I first focused on getting users, but now that it has a user base and that we have a solid design, I think it's time we proceed with that vision. (Check out https://github.com/brillout/research/issues/4, but keep in mind that I revisited the archtiecture; a couple of comments I've made are wrong.)

function that would start with on is a hook.

Good idea! Actually, how about onServerRender() and onClientRender()?

transformPage()

That's a great idea. Because the user often needs to add wrapper components and it's great that the user can do this without having to modify onServerRender()/onClientRender() (especially when provided by an external npm package).

I did say I was going to go wild.

Feel free to go wild again and anytime you want :grinning:.

Your proposal is gold. On a high-level I believe you're right on everything.
Now, there are things I disagree with. But I'd like emphasize: the comments below are all low-level. What's most important to me is that we agree on a high-level, which we do!

Low-level

How about _render.js instead of _rendering.page.js?
And how about export default instead of export { rendering }?
I would also move all exports /pages/about.page.js to a single export export { page } (or export default for React users), e.g.: export { page: { Page, title, ... } }. Why? Because we can then wrap all exports with a TypeScript utility function like Vite's defineConfig().
We still, unfortunately, need escapeInject. (There is no way for vps to tell whether a part of the string was injected or if it's part of the template string.)
I'd keep the name pageContext, because it's clearer. (Users are often confused what pageContext is, e.g. see this note which I included to the docs because I made that comment quite a lot of times on Discord.)
I'd keep pageContext.routeParams because pageContext.route would always only have pageContext.route.params. (I don't foresee other properties for pageContext.route.)
Actually, I'd keep exports flat, e.g. export default { onPrerender } instead of export default { hooks: { onPrerender }}. It does make sense that pageContext can have nested properties, but I believe that exports should stay flat. Because pageContext.url.path adds value as the user can console.log(pageContext.url), whereas export { default { hooks: onPrerender } } doesn't add value.
I think passToClient is clearer than passContext.
We need two different prefetch settings: one for static assets prefetching, and another one for pageContext prefetching. (Once https://github.com/brillout/vite-plugin-ssr/issues/246 is implemented). That's why it has a verbose name prefetchSaticAssets.
I don't think we should allow the user to rename built-in exports (configExport, pageExport). I like convention over configuration for this one: it makes code more readable across projects, and I don't see the added value of being able to rename exports.
I agree with the pageContext.url renaming, but I'd do it a little bit later (because I already renamed some of them in 0.4).
We can (and I think we should!) move all vite.config.js#vitePluginSsr configs to _render.js.
We can completely get rid of 'server-and-client'/'server'/'client'. Instead in _render.js: exports default { exports: { Page: { passToServer: true, passToClient: true } } }. (We could then align the naming of passToClient for pageContext: export default { pageContext: { someData: { passToClient: true } } }.)
I think we should release this as 1.0.0. (To maintain trust that vps does care about keeping breaking changes to a minimum.)

most of the names are more verbose than necessary.

 On a more serious note: I like long/unique names because they are easily searchable in both documentation and source code. However, I do see sensible exceptions, e.g. I agree about your prerender & onPrerender() renaming, it's much better.

Why didn't you use SSR, SPA and HTML for the modes?

I agree, but I still think there could be some kind of shortcut. Because someone discovering a vps (project) would instantly understand export default { renderMode: SPA } whereas he wouldn't understand export defaut { exports: { Page: { passToServer: true, passToClient: true } } }.

Plan
The naming disagreements are not a blocker, so I think we can proceed with this.
I'll be busy in the next comings weeks with high prio tickets (e.g. writing the landing page https://stemjs.com + implementing Telefunc hooks), but we can start thinking of the implementation.
I guess the first step is the acorn parsing. Maybe as a standalone package with Vitest unit tests? There is some prior work done by https://github.com/galvez/acorn-strip-function but I didn't check it out.
If someone wants to chime in with the implementation of that package that'd be awesome!
As for the integration of that package into vite-plugin-ssr, it's likely best if I do it.

CC @aleclarson which I think you'll find this design very interesting.

I think the cool thing about this discussion is that in not understanding exactly everything about how VPS and SSR frameworks in general work at a low-level, I can describe how I would expect this to work from quite a "user-land" perspective as I continue to learn. Through feedback I can revise my ideas based on actual low-level requirements/limitation/whatever.
My Feedback
I'm so glad we're on the same page (no pun intended) here! I think the idea is beautiful too! Here are my comments.

How about _render.js instead of _rendering.page.js?

Sure! The root of what I was going for is getting rid of the word default. Although the config can live in it's own file acting as a "default" for a directory in a domain-driven file structure, it can also live right in with a page, in which case it's less of a default, and more of just a regular config/override. I personally see huge value in both of them being treated as really one and the same.

And how about export default instead of export { rendering }?

I'm not sure. I'll explain my reasoning, and you make the call on that.
I'm pretty sure some frameworks like Vue rely on export default for things like single-file components, and if someone wanted to use VPS for their own framework, isn't it entirely possible that they might want to use export default for their own purpose? Part of what I think makes VPS so amazing is it's framework agnostic approach. If some brave new amazing framework comes out tomorrow, wouldn't using export default potentially act as a roadblock to using it with VPS right away, due to a pontential conflict?
Having export const rendering or something like that (with the option to rename it) to me means that I don't have to care at all about how (for example) Vue implements SFCs, and whether VPS does anything special to support Vue SFCs. I can use whatever special file my framework gives me like [name].page.[my-frameworks-special-file-extension], and as long as I'm allowed to export whatever I want, I'm able to guarantee myself 100% that VPS won't conflict with however my framework likes to export things.
If VPS uses export default, but my framework does too, then I'm not sure exactly right now how to you'd set up VPS to ensure that I won't run into any issues with any brave new framework that gets released tomorrow.
Maybe I'm not understanding everything, or all the potential solutions or problems. But that was my reasoning behind not doing export default in my idea above.

I would also move all exports /pages/about.page.js to a single export export { page } (or export default for React users), e.g.: export { page: { Page, title, ... } }...

I think I understand what you're saying here, and I think it's a great idea with good reasoning behind it.
However, I fully admit that I see everything through a "Vue SFC lens", and I know other frameworks like Svelte also like having their own special files, so my initial train of thought is: How would this work in those cases? I don't think (as far as I am aware) that I can control how my "page" component is exported with Vue SFCs. I can control all of my other custom exports of course, but the Page export is sort of just handled by the framework (I think?), so that's where I'm not exactly sure. Sorry if I'm just not understand everything about how that works.

We still, unfortunately, need escapeInject...

Sorry, I wasn't saying to not use escapeInject, I was originally just thinking that maybe we could call it automatically for the user? However, I now see what you're saying, its a custom template string function, so it always has to be "called" where the string is in the source code.
What about something like this?
renderServer(pageContext, escape, unescape) {
  const pageHtmlOrStream = whateverYourFrameworkUses();

  return escape`
    <!DOCTYPE html>

        <title>${pageContext.exports.title}</title>

${unescape(PageHtmlOrStream)}

  `;
}

The user doesn't have to manually import escapeInject or dangerouslySkipEscape anymore, but I suppose they still could if they needed those functions anywhere else for some reason. Since the renderServer is probably the only place the user will ever use them, they just get passed as arguments to the function. This also means that "chronic renamers" like myself could even call the arguments whatever we want. 😁

I'd keep the name pageContext, because it's clearer...

After seeing your note, I understand your reasoning and agree. I almost feel like it could actually even actually be called page. Or maybe not. In any case though, it's a function argument, so really we have to assume that the user might call it anything.

I'd keep pageContext.routeParams...

So I just took a look at Nuxt 3 and maybe we can take a bit of inspiration? Basically what they're doing is making one object that combines both route and url stuff, which makes sense because they're closely related, if not the same thing.
https://v3.nuxtjs.org/api/composables/use-route

Actually, I'd keep exports flat, e.g. export default { onPrerender }...

Yeah, I see what you're saying. I agree. It kinda looks like the old Vue 2 way of defining components with an object config. In this case, that's not at all a bad thing. I think it's important for the config to be able to stay out of the way of whatever framework the user might be using.

I think passToClient is clearer than passContext.

Based on your point coming up below, I agree. I do wonder if there isn't some way we could make it clearer that this is only for data that's serializable to JSON? So people don't try to pass functions and stuff. Maybe I'm overthinking it.

We need two different prefetch settings...

Gotcha. I didn't realize that.

I don't think we should allow the user to rename built-in exports...

Generally, I agree. The reason I feel this case is a bit unique is because we're trying to create something that will work with any future framework and never conflict with any other exports. If you use export default and find a way to deal with that potential conflict then yeah, you can't really ever "rename" it, but if not, then we're hard pressed to pick a name that is simple and clear but probably won't ever conflict. This is for me a case of "let's just set a default that nobody will probably ever need to change", but provide an escape hatch if they do.

I agree with the pageContext.url renaming, but I'd do it a little bit later...

No worries! (Also, we could take inspiration from Nuxt as I mention above.)

We can (and I think we should!) move all vite.config.js#vitePluginSsr configs to _render.js.

Isn't part of the idea for the _render.js config for it to be overrideable by defining it in subfolders or in pages themselves, though? I think there are a few options where that doesn't make sense, for example disableAutoFullBuild.

We can completely get rid of 'server-and-client'/'server'/'client'...

I love this idea! My only question is again: How would this work for frameworks that use their own special files and handle the "page/default export" for you?
Also, server currently means that by default zero JS is sent to the browser, which is kind of still true, but now it just means that onClientRender is not called, which is not necessarily the same semantically, because now any import "/something.js?env=client"; line causes the client to still get JS. Not really a problem, just a thought.

I think we should release this as 1.0.0.

Wow! I feel honored to be able to contribute ideas towards such a huge milestone!

I like long/unique names because they are easily searchable in both documentation and source code. However, I do see sensible exceptions, e.g. I agree about your prerender & onPrerender() renaming, it's much better.

That makes sense. I think one of my main ideas was that by having all config under one object the longer names would be less important because there's no issues with potential naming conflicts.
I fully admit, though, that I'm just kind of a crippling perfectionist and like short "one or two word" type names. (At least I'm not like those competitive programmers that use "one or two letter" names. xD)

I still think there could be some kind of shortcut. (For SSR, SPA, HTML)

I actually agree. (You'll see I revised my position on it above.) So how about { preset: "SPA" }? We could support everyone's terminology by just having "presets" that alias back to one of a few core configs. We could have tons of presets for how anyone thinks, like:

Preset Name
Server / Client
Prerendering?

SPA
Client
Yes

SSR
Both
No

HTML[-only]
Server
Yes

CSR
Client
Yes

MPA
Server
Yes

SSR MPA
Server
No

SSR SPA
Client
No

CSR SPA
Client
Yes

CSR MPA
Server
Yes

Universal
Both
No

SSG
Both
Yes

I probably need to really hash out what all the permutations are and figure out what names each one goes by. I probably also need to think about how client routing fits into it too, and whether it would make sense for the preset to even be able to switch the router to run on the client side too for a directory (full SPA mode for only part of a project, each page file will be an SPA page). In general, hopefully this helps give you an idea of how I'm thinking though.
(Also, for framework presets we could just do { framework: "react" || "vue" || "whatever" } maybe?)

The naming disagreements are not a blocker...

Definitely agree. Sometimes I name things and later disagree with myself before the sun has even gone down.

I am super excited about this idea because it feels like it would bring maximum flexibility to JS web applications without maximum complexity. Whatever rendering mode you want, with whatever framework you want—even brand new ones!! That's the dream right there!

I personally see huge value in both of them being treated as really one and the same.

Yes! It's actually been a while I'm not happy with that _default naming. I'm glad this problem is finally solved!

but the Page export is sort of just handled by the framework

Apologies I wasn't being clear. What I meant is this:

<template>

Hello

</template>

(When export const page is defined, then vps treats export default as the UI component.)
// /pages/about.page.jsx

export const page = {
  title: 'Hello',
  route: '/about',
  Page,
  // ...
}

function Page() {
  return
Hello

}

(React users can use export const page.Page instead of export default.)

I'm pretty sure some frameworks like Vue rely on export default for things like single-file components

The idea is to use export default for _render.js while using export { page } for .page.vue.
I believe that approach addresses all your concerns.

What about something like this? [...] escape [...] unescape

I like that naming!

renderServer(pageContext, escape, unescape)

For HTML Fragments, we do need the user to be able to use these outside of renderServer().
While I share the sentiment, I think it's probably best to make it that there is only one way to access escape & unescape. Otherwise a user may ask himself whether there is differences between import { escape } and renderServer(_, escape). It's also problematic with renderServer(_, myCustomEscapeName) which makes things confusing.

page

One on hand I like to keep it short, on the other hand it does make it hard to search in the documentation. Actually, pageContext is one of the most searched keywords on https://vite-plugin-ssr.com (Algolia statistics).

combines both route and url stuff

I see, that makes sense.

So people don't try to pass functions and stuff.

The error message is pretty good when they try to do that, which I think does the trick.

there are a few options where that doesn't make sense, for example disableAutoFullBuild.

I agree and I think we should throw an error if the user provides a setting like disableAutoFullBuild in a _render.js that is not the root _render.js.
To keep all configs in one place, I think we can and should move it all to _render.js.

now it just means that onClientRender() is not called, which is not necessarily the same semantically

We can argue that, well, onClientRender() is simply being tree-shaked away :-).

Wow! I feel honored to be able to contribute ideas towards such a huge milestone!

This has been a very productive discussion :heart: I don't think I would have come up with our design on my own. You contributed a ton of great ideas here...

I fully admit, though, that I'm just kind of a crippling perfectionist

I think perfectionist tendencies always comes along with caring :-).

We could have tons of presets for how anyone thinks

I think we should pick & stick one set of naming though. Otherwise it will be confusing for the user as they'll be wondering whether there is a functional difference between these synonyms.
I think "SPA" and "SSR" fits well. As for "HTML": I've always been hestitating (and I still do :sweat_smile:) between "HTML", "HTML-only", and "HTML-first".

Definitely agree. Sometimes I name things and later disagree with myself before the sun has even gone down.

:grinning:

That's the dream right there!

I think so too :-).
I'm particularly exited about the frameworks being built on top of vps. Exciting times.

super excited

I'm glad you are as well :-).

Also, btw., I'm thinking of postponing some of the name changes. Because if we introduce this as a non-breaking change (by supporting both the current design and this design during some time) then in some situations we may want to keep the old names for a little longer. (We can change them later.)
Anyways, the important thing is that we agree on the north star, which I believe we do.

The main blocker for implementing the new design is this:

The first step is the acorn parsing. Maybe as a standalone package with Vitest unit tests? There is some prior work done by https://github.com/galvez/acorn-strip-function but I didn't check it out.

Any help here would be much appreciated, as I'm not that experienced with AST manipulation.
Once we have this, I can implement the new design fairly quickly.

Because the rendering object exported from the _rendering.js and [name].page.js files is just a plain object, it makes parsing the file to locate/extract the different functions pretty complex. It would be much simpler if we used named exports, like Next.js and Remix, although this comes at a cost to loss of flexibility. Unless, we restrict what is supported, and make that very clear to the user.
Unless I'm looking at this all wrong, because it's just an object, the infinite complexity of JavaScript has to be supported/considered by the parser when attempting to locate/extract the relevant functions. Here is a simple example of the kinds of things that would have to be supported by the parser. Extracting the relevant functions would be pretty difficult. (Unless I am just misunderstanding something.)
import initialOptions from 'initialOptions.json' assert { type: `json` }; // where initialOptions = {"mode": "ssr"}

let trans = "transform"

let addWrapper = (page) => `<div>${page}</div>`

let newOptions = {
  [trans + 'Page'] addWrapper
}

export default (Object.assign(initialOptions, newOptions))

Please let me know if I'm missing anything obvious here.

The parser could support a few common/likely patterns, and make it very explicit to the user that there is some magic going on, and to stick to those patterns.
Patterns like:
export default {
  functionName () {}
}

export default {
  functionName: function () {}
}

export default {
  functionName: () => {}
}

const ObjectName = {
  functionName() {}
}

export default ObjectName

Sorry if this is kind of a stupid question, since I don't fully understand all the complexity that goes into this, but why not just grab the whole exported config object instead of trying parse out bits and pieces of it? Is there some major performance issue with just grabbing the whole thing?

Actually, now that I've thought about the problem some more, I think I'm beginning to understand the problem?
Is doing something like this a potential solution?
// Original final config after merging `[name].page.js` with `_rendering.js`...
export const rendering = {
  someFunc() { ... },
  ...
};

Now, generate a virtual module with the config above with additional exports that just pick out the needed data. Like this:
export const someFunc = rendering.someFunc;

Some functions have to be ran on the server, and other functions on the client. I believe the role of the parser is to locate/extract those functions.

With the solution(?) above, couldn't you just do this on the server:
import { onServerRender } from "generated-virtual-module";

And then this for the client?
import { onClientRender } from "generated-virtual-module";

Because of the generated additional exports in there? Generating those should be pretty easy, right?

I'm unsure how the internal plumbing/bundling of Vite and Rollup work. I saw the request for a parsing module and thought I'd take a shot at implementing it.
Maybe @brillout can shed some light on if this approach would work?

The parser could support a few common/likely patterns, and make it very explicit to the user that there is some magic going on, and to stick to those patterns.

I think that's a very good point, and I don't think there is an alternative to it.
The bottom line being that we need to know what code is used for what:
// _rendering.js

import { renderToHtml, hydrateDom } from 'some-ui-framework'
import { escape, unescape } from 'vite-plugin-ssr'

export default {
  renderServer(pageContext) {
    // We need to know that `renderToHtml()` is used by `renderServer()`.
    const pageHtml = renderToHtml(pageContext.page)
      return escape`
        <!DOCTYPE html>

${unescape(pageHtml)}

        `
  },
  renderClient(pageContext) {
    // We need to know that `hydrateDom()` is used by `renderClient()`.
    hydrateDom(document.querySelector('#root'), pageContext.Page)
  }
}

Here, renderToHtml() should only ever be included to the server-side, whereas hydrateDom() should only ever be included to the client-side.
That's why we need to inspect the syntax of _rendering.js. And the only viable way to do that, that I'm aware of, is to inspect the AST.
I think we need to use acorn here (or some other AST parser).
But let me know if you believe I'm missing something here.

I could be completely wrong here, but with @AaronBeaudoin's proposed solution, wouldn't the client/server dependencies just be tree shaken from their respective bundles?
If that isn't the case, how do you feel about enforcing the use of dynamic imports only for the rendering object? Would this work? I could be way off base here.
// Final config after merging `[name].page.js` with `_rendering.js`...
export const rendering = {
  async renderServer() {
    const { default: sass } = await import('node-sass')
    ....
  },
  async renderClient() {
   const {default: reactQuery} = import('react-query')
    ...
  }
};

Only Rollup does tree shaking. Vite doesn't do any tree shaking. In other words, we have to do it ourselves.

the use of dynamic imports

We'd still need to prune them, because unused dynamic imports are problematic in the code splitting optimization process.

Next.js has shown it's possible with AST manipulation, so there is no reason we can't do it too. I agree that export default { renderClient, renderServer } is more tricky than export { renderClient, renderServer } but I think we can workaround that.

Only Rollup does tree shaking. Vite doesn't do any tree shaking. In other words, we have to do it ourselves.

Sorry, I think I misspoke, I meant code splitting. Also please excuse me if this is a dumb question. With @AaronBeaudoin's solution, couldn't Vite just code split on the generated virtual modules, and only utilize the imported modules that those respective functions actually use?
If that isn't the case.
Because JavaScript object key names can be any arbitrary expression, it's impossible to locate/extract the respective functions as long as the user retains this capability (as far as I can tell). This requires us to limit the capabilities of the user to rename/use expressions as keys. Or alternatively, just use named exports (Like Next.js and Remix) which can't be renamed.
However, once we limit what the user can do in a file that appears to be normal JavaScript file, with no indications that anything magical is afoot, I feel we might be breaking an unspoken agreement with the user.
If we are going to do something magical, why not lean in to it, and make it fully explicit that something magical is happening. Instead of hiding it, and hoping most users don't bump in to our limitations?
 And enforce the use of Identifiers instead of object keys for the VPS functions.
vps: function renderServer () {}

vps: function renderClient() {}

vps: passToClient = ['pageProps']

This could be a dumb idea, and I could be completely misunderstanding something. So thanks for your patience.

Sorry, I think I misspoke, I meant code splitting. Also please excuse me if this is a dumb question. With @AaronBeaudoin's solution, couldn't Vite just code split on the generated virtual modules, and only utilize the imported modules that those respective functions actually use?

I'm not sure what you mean.
The bottom line is that we need to determine what code should be pruned. Vite cannot magically do this for us. There is no way to circumvent the need for inspecting the AST. That's, btw., what Next.js does.

Why not attach a label to all VPS needed things instead of exporting them,

That would break IntelliSense.

This requires us to limit the capabilities of the user

Yes exactly.

I feel we might be breaking an unspoken agreement with the user.

That's a concern indeed. I think it's okay-ish though.

The reason I think export default { renderClient, renderServer } is better than export { renderClient, renderServer } is because I believe it's the only way to have TypeScript IntelliSense, e.g. export default defineConfig({ renderClient, renderServer }), see https://vitejs.dev/config/#config-intellisense.

I love that terminology. 100% agree that it is much clearer.
As an extension, I'd probably also do import "/something.js?env=browser"; instead of import "/something.js?env=client";. However, I think I'd keep import "/something.js?env=server";.

As an extension, I'd probably also do import "/something.js?env=browser"; instead of import "/something.js?env=client";. However, I think I'd keep import "/something.js?env=server";.

I agree.
env=server is a misnomer but I can't find a better alternative. E.g. env=node wouldn't fit edge platforms.

env=function? 👀

I like the env=render idea. I like the idea in principle but the thing I'm reluctant about is that render overlaps with UI framework terminology. E.g. in React's world rendering usually means client-side rendering.
How about env=node actually? Yes, it's a misnomer for edge platforms, but I'm thinking it's the clearest and I doubt there is a perfect word here?

Just being honest, I really don't like env=node. 😕 In a possible future where everyone is using Deno or Edge Functions, env=node won't just be a misnomer, it'll also be super confusing.
I personally don't think env=server is that much of a misnomer. I mean, for code that's not running in the browser, there are really only 3 scenarios I can think of:

It's running on a server.
It's running in a function (edge or serverless), which really is still a server.
It's running in a pre-render build step, which also is pretty much still a short-lived server.

All 3 of these fit the general idea of a "server". I think server is the only word that really makes sense.

Just last night I read through this https://github.com/sveltejs/kit/discussions/5748 discussion about refining their API for a 1.0 release. They went with a whole bunch of really interesting ideas that I think we can definitely take inspiration from for this discussion.
A few highlights that are relevant here:

They lean into the word server in their filenames where applicable. I think that is by necessity; there isn't really a better alternative. If you're not doing something on the client/browser, it's really got to be happing on some kind of "server".
They no longer let you do the Svelte equivalent of VPS's pages/home.page.js and pages/home/index.page.js to mean the same thing. Instead you always do something like pages/home/+page.js. I thought that was interesting, because I never thought this was a problem to begin with, but reading their rationale, it's kind of growing on me. The + feels weird, but their rationale is that it marks the file as a "route file", which to me seems to essentially mean that it is handled by the framework. All other files not starting with + are ignored. It feels weird, but I think that might just be because it's something we've never seen before. Also, we of course don't have to do the + thing.

I would highly recommend reading through the discussion, as well as SvelteKit's Routing section in their docs to take a peek at how they're doing things now. I would say personally that I find it to be really intuitive. I don't use Svelte at all right now, but I feel like I understand everything they're doing in there without needing to know anything else about the framework, which makes me feel that it's designed really well.

One more things they're doing over at SvelteKit that I thought was interesting: They're using global exports like export const ssr = false, similar to how you do currently in VPS. It's making me second-guess myself concerning whether the whole "one export config object" is really a good idea.
The real question I'm sort of debating is to what degree VPS should be a "framework" as opposed to a "framework for building frameworks". If VPS is a "framework" then I think enforced named "top-level" exports makes sense because all frameworks have conventions that they force you to follow. However, if it's a "framework for building frameworks" then I think the names of all exports used by VPS should be customizable so the eventual "framework" created over top of VPS can dictate exactly how the API should look.
Basically, maybe "top-level exports" like how we do it now is not so bad—but if, and only if, the names of the exports can be customized in the VPS plugin config in vite.config.js. The idea is that you could now build a framework with VPS where you can't even tell that you're using VPS underneath because VPS didn't enforce any API style. Just an idea.

I personally don't think env=server is that much of a misnomer. I mean, for code that's not running in the browser, there are really only 3 scenarios I can think of:

I've actually also come to that conclusion. I don't see any alternative.
And yes, when framing SSG as "pre-rendering" then "server" actually makes sense. A short lived server, as you said.

The + feels weird, but their rationale is that it marks the file as a "route file", which to me seems to essentially mean that it is handled by the framework.

Yes, I also like that + idea. The weirdness to it is actually nice because it makes it "in your face" that this is a special file. That's good.
The vps equivalent is the .page.js suffix which serves a similar purpose. I think .page.js is fine for now. But, yea, I think that +render.js is nicer than _render.js. I'm in. Actually, I'm just seeing that vim doesn't like the + prefix.
$ vim +hello.js
Error detected while processing command line:
E492: Not an editor command: hello.js

So, while I do like the + idea, it probably should be another character.

SvelteKit's Routing section in their docs to take a peek at how they're doing things now.

It's actually quite similar to vps, expect of (nested) layouts and API routes. I'm against API routes because AFAICT Telefunc is superior in every way compared to API routes.
As for (nested) layouts, I'd say that SvelteKit is currently superior to vps. (Nested) Layouts are on my radar. Although I like vps's idea of using "Custom Exports" for layouts a lot (https://vite-plugin-ssr.com/exports#example-page-layout). It's both simple and powerful. I'll think about extending that idea to match SvelteKit's nested layout DX.

VPS [...] is a "framework for building frameworks" then I think the names of all exports used by VPS should be customizable so the eventual "framework" created over top of VPS can dictate exactly how the API should look.

I know you like to be able to customize names. But I think that "convention over configuration" is the way to go for names. Because it makes things simpler when switching from one vps project to another. And, concretely, I can't see why rigid names would cause any problem.

We should consider that having client-side code and server-side code living in the same file does add non-negligible confusion.
For example:
// _render.js

let someVar;

export default {
  onRenderBrowser() {
     // `someVar` here is actually a different variable here
  },
  onRenderHtml() {
    // `someVar` here is actually a different variable here
  }
}

It will be confusing to many users that someVar isn't shared between onRenderBrowser() and onRenderHtml(). I think it's a problem and I'm not sure if we can address it.
There are also other issues but I think they are fixable.
This makes me think that having the .client.js and .server.js suffixes is a significantly clearer DX.
@AaronBeaudoin The problem is that your proposal heavily relies on having a single file. Maybe we can workaround this?

Ok I want to make sure I'm understanding this right.
So in the case you mention, I can still use someVar in both onRenderBrowser and onRenderHtml and it will work fine. The key issue you're describing is that if I change someVar in onRenderBrowser I won't actually see that change in onRenderHtml (or vice versa) because the variable is getting "duplicated" into each environment which is expected since they are totally separate.
The thing is though, if you have a .client.js file and a .server.js file that bothimport { someVar } from "some.js"; which looks like this:
// some.js

export let someVar;

You have a similar problem. The variable is going to be different in .client.js than it is in .server.js.
So really, the issue here is just that developers naturally wouldn't expect a variable in the same file to be subject to behavior like this. Personally, I think that this is just the nature of trying to write code for two JS environments in one project.
I'd just make sure to document this. Something like:

onRenderBrowser and onRenderHtml are each run in totally separated JavaScript environments, so any higher-scoped variables they use are not shared between the functions. For example, if you assign to or mutate someVar inside onRenderBrowser, the value will not be available in onRenderHtml, and vice versa.

Exactly.
The thing is that .client.js and .server.js make it explicit ("in your face" really) that onRenderBrowser() and onRenderHtml() live in two different environments. So when importing the same variable, the user will be much less surprised that someVar is actually two different variables.
Whereas a single file hides the fact that there are two different environments.
It's a leaky abstraction.
I'm not categorically against the idea, but I think the added confusion is something we should consider.
Maybe for some higher-level framework a single file thingy could be worth it, even if it decreases clarity. Let's see.
I wonder how your proposal would look like if we want to preserve the .client.js and .server.js suffixes?

He brings up some good points. I'm also considering what you've mentioned recently as well. I think the one-file approach has it's pros and cons vs the current multiple-file approach. I think maybe the two concepts could be merged in a tasteful way, but I'm not sure how I would do it yet. I'll definitely be back here when I come up with something.

I think maybe the two concepts could be merged in a tasteful way

Agreed.

I'll definitely be back here when I come up with something.

💯 Very much looking forward to it 👀.

Related, seems like React Server Components are moving away from .client.js and .server.js file conventions, in favor of a use 'client'; directive inside files: https://github.com/reactjs/rfcs/pull/227

@redbar0n Yea I've seen. I think it's a great move.

How about making _render.js more like a "Header File":
// _render.js

import { onRenderHtml } from './onRenderHtml'

const prerender = true

export default {
  // ❌ Forbidden: assignment to a function
  onRenderHtml() {
    /* ... */
  },
  // ❌ Forbidden: assignment to a variable
  prerender
}

// _render.js

export default {
  // ✅ Allowed: assignment to an import
  onRenderHtml,
  // ✅ Allowed: assignment to a value
  prerender: true
}

I believe this solves all problems.
I would even argue that it's a good thing that _render.js isn't allowed to have real JavaScript code: it ensures that _render.js is kept small and therefore easy to read.

I like this plugin's claim "Hundreds of lines of code are enough to build your own Next.js / Nuxt". I think to truly fulfill it there needs to be an example of an actual Next.js or Nuxt clone. And having a single-page experience is part of that.
I also wonder what you think about qwik's attempt to even further erase the line between server and client. What would it take to build a qwik clone with vite-plugin-ssr?

Warning: This comment might be a bit of a mess. I figured rather than taking forever to think through every little detail it would be better to just get something out there so we can move the conversation forward.
From my perspective, it seems that VPS has 3 primary concerns, which are all related but separate:

Server-Side Rendering
Pre-Rendering
Client-Side Routing

Below, I've written out "pseudo-docs" for a potential VPS API redesign with the following principles at heart:

No more .page.route.js. Routing is important, but putting it in a separate file isn't ergonomic.
No more .page.server.js or .page.client.js. Instead, use a renderMode export in .page.js.
Two .server.js and .client.js files now somewhat replace .page.server.js and .page.client.js. Their purpose is not the same however. .page.js is now just the central file where pages' framework-specific root components live, with a few special VPS config exports. .server.js and .client.js are responsible for containing any "extra" code you want to run on the server or the client not related to your framework's page component itself. They are simply overrides for your _config.server.js and _config.client.js files.

Server-Side Rendering API
This section documents the VPS API for server-side rendering.
To pass data from the server to the client, add it under a _passToClient property on the return object, which will be merged with the context and serialized to the client.
_config.server.[js|ts]
Purpose: Contains configuration for the current directory and all subdirectories related to rendering on the server. Can be placed in a _config directory for better organization.
export onBeforeRenderHtml(context): Object
Return object is merged into the received context.
export onRenderHtml(context): Object
Return object is merged into the received context. It should have an _documentHtml property whose value is a string which should be sent as the response body to the client. This function is where you put your framework specific code for rendering your page component to an HTML string.
_config.client.[js|ts]
Purpose: Contains configuration for the current directory and all subdirectories related to rendering on the client. Can be placed in a _config directory for better organization.
export onBeforeRenderHead(context): Object
Return object is merged into the received context. Code from this function is included and run in the document .
export onBeforeRenderBody(context): Object
Return object is merged into the received context. Code from this function is included and run at the top of the document .
export onBeforeRenderBrowser(context): Object
Return object is merged into the received context. Code from this function is included and run at the bottom of the document .
export onRenderBrowser(context): Object
Return object is merged into the received context. This function is where you put your framework specific code for rendering your page component to the DOM.
<name>.page.<ext>
Purpose: Contains a framework-specific export which can be rendered to HTML in onRenderHtml(). If <name> is _error, the page will be rendered whenever applicable based on the current VPS rules.
export renderMode: String
Sets the environment in which the page should be rendered. Default is server-and-client.

Value
Behavior

server-and-client
The page should be rendered to an HTML string, inserted under some root element on the server, then rendered to the DOM in the browser again on the client, replacing the HTML under the root element.

server-only
The page should be rendered to an HTML string on the server only.

client-only
The page should be rendered to the DOM in the browser on the client only.

export routing: Object
Holds the configuration for routing.
<name>.server.[js|ts]
Purpose: Override the currently applicable _config.server.[js|ts] file for <name>.page.<ext>.
<name>.client.[js|ts]
Purpose: Override the currently applicable _config.server.[js|ts] file for <name>.page.<ext>.
Pre-Rendering API
This section documents the VPS API for pre-rendering.
_config.server.[js|ts] Additions
export onPreRender(context): Object
Return object is merged into the received context.
<name>.page.<ext> Additions
export prerender: Boolean
Sets whether the page should be prerendered at build time. Default is false.
Client-Side Routing API
Not sure about this part, this comment is already long enough, and I'm not sure anything I'd write under this section would change the core philosophy I'm going for here.

Hopefully my root idea here makes sense. I know I'm missing some things and probably made some errors here and there, but my core mindset is to make it such that you have some "base" configuration that can be overridden, but that whole mechanism is separate from the concern of defining your pages' framework-specific components.
So, basically, the definitions of framework-specific components are "single file" as discussed in this issue, but everything else that happens around them are in separate .server or .client files. This makes my new proposal a bit closer to how VPS is currently designed, while (hopefully) providing a clearer separation of concerns.
Maybe there are technical or logical reasons this won't work that I haven't considered. That's why I'm interest to hear what your thoughts are on this whole idea, and whether you think it even makes sense to begin with.

It seems to me that we have similar goals and I think that the design of my previous comment does cover your aspirations.
Let me clarify.
The _define.js files are the only VPS files. That's the beauty of it. If you open all _define.js files then you're browsing the entire interface between the user and VPS.
// /pages/product/_define.ts

import type { Define } from 'vite-plugin-ssr'
import { Page } from './Page'
import { onBeforeRender } from './onBeforeRender'
import { onPrerender } from './onPrerender'

// This file replaces:
//  - /pages/product/index.page.js
//  - /pages/product/index.page.server.js
//  - /pages/product/index.page.client.js
//  - /pages/product/index.page.route.js

export default {
  renderMode: 'spa', // => `Page` is loaded only in client
  // renderMode: 'ssr'  => `Page` is loaded in client & server
  Page, // (would normally live in `.page.client.js`)
  route: '/product/@id', // (would normally live in `.page.route.js`)
  onBeforeRender, // (would normally live in `.page.js`)
  onPrerender, // (would normally live in `.page.server.js`)
  exports: {
    onBeforeRender: {
      // By default, onBeforeRender() runs only on the server-side.
      // But we can configure to run it also on the client-side:
      env: ['server', 'client']
    }
  },
  prerender: false
} satisfies Define // (New TypeScript 4.9 operator)

// /pages/_define.ts

// This file replaces:
//  - /renderer/_default.page.server.js
//  - /renderer/_default.page.client.js
//  - VPS config defined in vite.config.js

import type { Define } from 'vite-plugin-ssr'
import { onRenderHtml } from './onRenderHtml'
import { onRenderClient } from './onRenderClient'

export default {
  onRenderHtml, // Is loaded only on the server-side
  onRenderClient, // Is loaded only on the client-side
  prerender: true, // Default value for all pages
  renderMode: 'ssr', // Default value for all pages
  includeAssetsImportedByServera: true, // (would normally live in vite.config.js)
  // Custom exports:
  exports: {
    title: {
      env: ['client', 'server']
    },
    description: {
      env: ['server']
    }
  }
} satisfies Define

The magic here is that _define.ts isn't real JavaScript. It's a subset:
// _define.js

export default {
  // ❌ Forbidden: functions cannot be defined in `_define.js`
  onRenderHtml() {
    /* ... */
  }
}

// _define.js

import { onRenderHtml } from './onRenderHtml'

export default {
  // ✅ Allowed: functions are imported and then re-exported over `_define.js#default`
  onRenderHtml
}

_define.js is like a header file: it links user code with VPS.
This is the crux of the idea as it enables VPS to load code in the right environemnt.
Single Route File
It enables a natural way to define a "Single Route File":
// /pages/_define.ts

import { Landing } from './Landing'
import { About } from './About'
import { Jobs } from './Jobs'
import type { Define } from 'vite-plugin-ssr'

export default {
  pages: [
    {
      Page: Landing,
      route: '/'
    },
    {
      Page: About,
      route: '/about'
    },
    {
      Page: Jobs,
      route: '/jobs'
    }
  ]
} satisfies Define

The beauty here is that VPS can automatically code-split the Landing/About/Jobs components. VPS knows that the Jobs component is only need for the route /jobs. (Behind the curtain, VPS generates a bunch of virtual files: one virtual file for the global settings that apply to all pages + one virtual file per each page X each environement. This all happens automatically without the user having to do anything.)
Nested Layouts
It even enables a natural way to define nested Layouts:
// /pages/product/_define.ts

import { Overview } from './Overview'
import { onBeforeRender } from './onBeforeRender'
import { Reviews } from './reviews'
import { onBeforeRenderReviews } from './reviews/onBeforeRender'
import { Details } from './details'
import type { Define } from 'vite-plugin-ssr'

export default {
  Page: Overview,
  route: '/product/@id',
  onBeforeRender,
  nested: [
    {
      Page: Details,
      route: '/product/@id/details',
    },
    {
      Page: Reviews,
      route: '/product/@id/reviews',
      onBeforeRender: onBeforeRenderReviews
    }
  ]
} satisfies Define

Frameworks
This design also further enables frameworks built on top of VPS.
IDE Plugin
Plugins can easily be written to tell the user which file is loaded in what environment. This is easy to implement as it can be done by statically anlayzing code.
From React's Server Components RFC:

As for the problem that the user doesn't know in what environments components are loaded (the uncanny valley problem), I think the "ultimate solution" would be IDE plugins showing in what env the component is loaded. For example a plugin changing the background color of the current opened file: green background => Server Component, blue background => Client Component, yellow background => Shared Component.

@AaronBeaudoin Good point. How about this?
// /pages/product/_define.ts

import type { Define } from 'vite-plugin-ssr'

export default {
  route: '/product/@id',
  renderMode: 'HTML',
  clientCode: './client.ts'
} as Define

// /pages/product/client.ts

// When `renderMode` is 'HTML' then this reprents the *entire* clent-side code

For enabling IntelliSense, we could even do this:
// _define.ts
export default {
  clientCode: import('./client')
}

This works since _define.ts is never run but merely a meta file for VPS.
Using import() would align slightly better with the practice of importing all hooks. Let's see.

Just renamed clientCode to client. (Although clientCode has the advantage of being googleable and searchable in the docs.)

I'm thinking of allowing users to skip creating _define.js:
/renderer/+onRenderHtml.ts # The '+' sign denotes a VPS property
/renderer/+onRenderClient.ts
/pages/index/+Page.ts
/pages/product/+Page.ts
/pages/product/+route.ts
/pages/product/+onBeforeRender.ts

A +define.js file is still useful to define default values and for Single Route Files and Nested Layouts:
// /pages/+define.ts

import type { Define } from 'vite-plugin-ssr'

export default {
  // Pages are SSR'd by default
  ssr: true
} satisfies Define

// /pages/admin/+onBeforeRender.ts

import type { Define } from 'vite-plugin-ssr'

export default {
  // The admin panel doesn't need SSR
  ssr: false,
  // Single Route File + Nested Layouts
  pages: [
    {
      route: '/admin',
      Page: './Dashboard.ts'
    },
    {
      route: '/admin/invoices',
      Page: './Invoices.ts'
    },
    {
      route: '/admin/db',
      Page: './DataViewer.ts',
      nested: [
        {
          route: '/admin/db/users',
          View: './Users'
        },
        {
          route: '/admin/db/products',
          View: './Products'
        }
      ]
    }
  ]
} satisfies Define

Custom Exports can then be defined with exportables:
// /renderer/+exports.ts

import type { Exports } from 'vite-plugin-ssr'

export default {
  exportables: [
    {
      name: 'title',
      env: 'SERVER_ONLY' // | 'CLIENT_ONLY' | 'CLIENT_AND_SERVER'
    }
  ]
} satisfies Exports

// /pages/index/+title.ts

// The file is loaded only in Node.js (at server run-time, or at build-time while pre-rendering)
export default 'Welcome to the new VPS design'

I actually really love those ideas!
The +whatever.xyz file naming scheme reminds me of all the Svelte stuff I was reading about, and I think it's pretty nice due to how it marks the files as being "meta-framework" concerns.
First, I just want to make sure I understand what all the options are here for structuring a page's code. Are all of these options below valid?
http://site.com/example
pages/
  example.jsx

pages/
  example/
    index.jsx

pages/
  example/
    +Page.jsx

pages/
  example/
    +exports.js # No `Page` key.
    index.jsx

pages/
  example/
    +exports.js # No `Page` key.
    +Page.jsx

pages/
  example/
    +exports.js # pages: [{ route: "/", Page: "./custom.jsx" }]
    custom.jsx

A huge value for me of that last option is the fact that you can give your "page" file a unique name so your IDE tabs don't look like <index.jsx> <index.jsx> <index.jsx> or <+Page.jsx> <+Page.jsx> <+Page.jsx>. Also, I put / as the route because I'm hoping that the routes in the pages of the config would be relative to the current directory. I can't imagine a circumstance where it would ever be a good idea to need to define a route up a level (..).
Also, what do you think of the idea of using the name renderable or component instead of Page? Because semantically a "page" might have other files that are related to it like for example a client.js file for custom JS.

Also, is there some option for nesting all of the +whatever.xyz files under some subdirectory for organization, sort of like how the current _default directory works under pages/? Here's sort of a "kitchen sink" example:
pages/
  example/
    +exports/ # Not sure if this is the right name for this...
      +exports.js # client: "../client.js"
      +onBeforeRender.js
      +onRenderClient.js
    index.jsx
    client.js

If this isn't an option, I think it probably should be. Because I think now we're probably looking at the addition of a lot of +whatever.js files everywhere, and without a "drawer" to stuff them in, the filesystem is going to get a bit chaotic. Also, it makes things pretty clean because I can by convention choose to always put all VPS-related stuff in a predictable place.

Also, just my two cents, but the name +exports makes me think semantically at first glance that the file is only for defining custom exports like title as you described, but not for VPS exports such as onBeforeRender. I think the most intuitive name is something else like +config or +setup or +base or +default or +path or +pages (trying to just dump a lot of ideas here). My favorite is actually +pages, which seems to convey the intent to define some config/exports for all the pages at or below the current level, aligning with the name of the root directory pages/ itself. Alternatively, I also think +config is quite clear.

The + naming convention itself signals "this file is a VPS thing" as you mentioned, which I think is a good thing, but then with the way you're doing "exportables", now anyone can add a new +anything.js file. So then new users later are probably Googling +title.js or whatever and trying to figure out why it isn't documented anywhere. Not ideal in my opinion.
So I think maybe you should have some convention +export.title.js or +export.server.title.js or +export.client.title.js, all of which would automatically get picked up by VPS and defined in exportables with no extra work required. I know the typical order feels like it should be something like title.export.js, but I actually think doing it "backwards" like this with a + at the start results in:

Superior readability and sorting in the filesystem.
Retaining the signaling of the file as a "VPS thing".
Simplicity when define custom exports (no +exports file needed).

The + is already at the start of the file anyways (as opposed to <whatever>.vps.js or something like that). But as you mentioned a while back, in the end naming isn't a dealbreaker. I do personally feel like excellent developer ergonomics tend to draw a crowd of dedicated users though, so that's why I love providing feedback in this area.

Lastly, I do want to mention that at first I looked at this and thought "oh this will make integrating with Svelte more difficult", but then I realized that the + thing is a SvelteKit thing, not a Svelte thing, and obviously you're not going to be trying to use SvelteKit and VPS at the same time, so I think we're fine. The way I understand it, VPS is compatible with any JavaScript UI framework, but incompatible with every JavaScript meta-framework. Hopefully I'm understanding that correctly. We'll have to see whatever happens if more UI frameworks start to implement "meta-framework dependent features" like React server components.

The +whatever.xyz file naming scheme [...] it's pretty nice due to how it marks the files as being "meta-framework" concerns.

Exactly! The + is very clear that something is being added/defined.

Are all of these options below valid?
http://site.com/example
pages/
  example.jsx

pages/
  example/
    index.jsx

No: if you don't define any + file then your repository is empty from VPS's persective. The entire interface between VPS and the user happens over + files.

pages/
  example/
    +Page.jsx

Yes

pages/
  example/
    +exports.js # No `pages` property.
    index.jsx

No: +exports.js doesn't define any page and index.jsx isn't referenced by any +exports.js.

pages/
  example/
    +exports.js # No `pages` property.
    +Page.jsx

Yes

pages/
  example/
    +exports.js # pages: [{ route: "/", Page: "./custom.jsx" }]
    custom.jsx

Yes

so your IDE tabs don't look like <index.jsx> <index.jsx> <index.jsx> or <+Page.jsx> <+Page.jsx> <+Page.jsx>.

Yes. Also I'm thinking to allow this:
/pages/index.+page.vue
/pages/about.+page.tsx
<EMAIL_ADDRESS><EMAIL_ADDRESS>

Also, I put / as the route because I'm hoping that the routes in the pages of the config would be relative to the current directory. I can't imagine a circumstance where it would ever be a good idea to need to define a route up a level (..).

I'm actually leaning towards enforcing route strings to always be absolute. I think the extra verbosity is worth it.

Also, what do you think of the idea of using the name renderable or component instead of Page? Because semantically a "page" might have other files that are related to it like for example a client.js file for custom JS.

In my expereince, this doesn't really happen in practice. Over 99% of the time, I expect +page.ts to actually denote a page. For other kinds of exports, you can define a new "exportable".

Also, is there some option for nesting all of the +whatever.xyz files under some subdirectory for organization, sort of like how the current _default directory works under pages/? Here's sort of a "kitchen sink" example:
pages/
  example/
    +exports/ # Not sure if this is the right name for this...
      +exports.js # client: "../client.js"
      +onBeforeRender.js
      +onRenderClient.js
    index.jsx
    client.js

If this isn't an option, I think it probably should be. Because I think now we're probably looking at the addition of a lot of +whatever.js files everywhere, and without a "drawer" to stuff them in, the filesystem is going to get a bit chaotic. Also, it makes things pretty clean because I can by convention choose to always put all VPS-related stuff in a predictable place.

What you can do is this:
/pages/product.+config.js # { page: './product/ProductPage.vue', onBeforeRender: './product/onBeforeProductRender.js' }
/pages/product/ProductPage.vue
/pages/product/onBeforeProductRender.js

I think the most intuitive name is something else like +config or +setup or +base or +default or +path or +pages (trying to just dump a lot of ideas here).

Good idea! I actally love the +config.js name. Very clear.

My favorite is actually +pages, which seems to convey the intent to define some config/exports for all the pages at or below the current level, aligning with the name of the root directory pages/ itself.

The thing is that +config.js may define only one page.
In the following example, naming +pages.js instead of +config.js would be confusing.
/pages/index/+config.js # { page: '/Page.vue', onBeforeRender: './onBeforeRender.js' }
/pages/index/Page.vue
/pages/index/onBeforeRender
/pages/about/+config.js # { page: '/Page.vue', onBeforeRender: './onBeforeRender.js' }
/pages/about/Page.vue
/pages/about/onBeforeRender

Alternatively, I also think +config is quite clear.

I think we can settle on +config then :ok_hand:.
I really like the +config.js name. I was actually quite unhappy with +export.js. I'm glad it's off the table now.

The + naming convention itself signals "this file is a VPS thing" as you mentioned, which I think is a good thing, but then with the way you're doing "exportables", now anyone can add a new +anything.js file. So then new users later are probably Googling +title.js or whatever and trying to figure out why it isn't documented anywhere. Not ideal in my opinion.

The idea is that the end-user almost never defines custom exports. It's almost always going to be the VPS framework. (I'm going to release a prototype showcasing how such VPS framework will look like.)
That's why I think it's a good thing to keep the same + naming for custom exports.

VPS is compatible with any JavaScript UI framework, but incompatible with every JavaScript meta-framework.

Exactly. (FYI Telefunc's Vite plugin needs to work with any UI Framework and with any meta framework.)

Yes. Also I'm thinking to allow this:
/pages/index.+page.vue
/pages/about.+page.tsx
<EMAIL_ADDRESS><EMAIL_ADDRESS>

I changed my mind. I don't think index.+page.vue is a good idea anymore.
A much cleaner approach is a new option singleFileConvention: true.
// /pages/+config.ts

import type { Config } from 'vite-plugin-ssr'

const marketingPagesConfig = {
  Layout: './layouts/LayoutMarketingPages.vue',
  ssr: true,
}
const adminPagesConfig = {
  title: 'Admin Panel',
  Layout: './layouts/LayoutAdminPages.vue',
  ssr: false
}

export default {
  // If `singleFileConvention: true` then only one `+` file is allowed (this file). If there is
  // anothoer `+` file, then VPS shows a warning.
  singleFileConvention: true,
  pages: [
    { ...marketingPagesConfig, Page: './LandingPage.vue', route: '/'        , title: 'Awesome Startup'                          },
    { ...marketingPagesConfig, Page:    './JobsPage.vue', route: '/jobs'    , title: 'We're hiring!'                            },
    { ...marketingPagesConfig, Page:  './VisionPage.vue', route: '/vision'  , title: 'Our mission <3'                           },
    {     ...adminPagesConfig, Page:   './AdminPage.vue', route: '/admin'   , onBeforeRender:   './AdminPage-onBeforeRender.ts' },
    {     ...adminPagesConfig, Page: './AdminDbPage.vue', route: '/admin/db', onBeforeRender: './AdminDbPage-onBeforeRender.ts' }
  ]
} satisfies Config

The whole thing ends up being quite simple:

With singleFileConvention: true, there is only a single +config.ts file (and no other "export files" such as +onBeforeRender.ts), i.e. +config.ts is the only + file in the entire user's repository.
With singleFileConvention: false (the default), there can be multiple +conifg.ts files and multiple "export files" such as +onBeforeRender.ts.

That's it.
I foresee singleFileConvention: true to be used for large-scale complex apps, while singleFileConvention: false is convenient to quickly get started like Next.js.
Alright, I think this is ripe for an RFC.

Edit: renamed singleFileConvention to singleConfigFile.

Closing in favor of https://github.com/brillout/vite-plugin-ssr/issues/578.

Yes. Also I'm thinking to allow this:
/pages/index.+page.vue
/pages/about.+page.tsx
<EMAIL_ADDRESS><EMAIL_ADDRESS>

I changed my mind. I don't think index.+page.vue is a good idea anymore.
A much cleaner approach is a new option singleConfigFile: true.
// /pages/+config.ts

import type { Config } from 'vite-plugin-ssr'

const marketingPagesConfig = {
  Layout: './layouts/LayoutMarketingPages.vue',
  ssr: true,
}
const adminPagesConfig = {
  title: 'Admin Panel',
  Layout: './layouts/LayoutAdminPages.vue',
  ssr: false
}

export default {
  // If `singleConfigFile: true` then only one `+` file is allowed (this file). If there is
  // anothoer `+` file, then VPS shows a warning.
  singleConfigFile: true,
  pages: [
    { ...marketingPagesConfig, Page: './LandingPage.vue', route: '/'        , title: 'Awesome Startup'                          },
    { ...marketingPagesConfig, Page:    './JobsPage.vue', route: '/jobs'    , title: 'We're hiring!'                            },
    { ...marketingPagesConfig, Page:  './VisionPage.vue', route: '/vision'  , title: 'Our mission <3'                           },
    {     ...adminPagesConfig, Page:   './AdminPage.vue', route: '/admin'   , onBeforeRender:   './AdminPage-onBeforeRender.ts' },
    {     ...adminPagesConfig, Page: './AdminDbPage.vue', route: '/admin/db', onBeforeRender: './AdminDbPage-onBeforeRender.ts' }
  ]
} satisfies Config

The whole thing ends up being quite simple:

With singleConfigFile: true, there is only a single +config.ts file (and no other "export files" such as +onBeforeRender.ts), i.e. +config.ts is the only + file in the entire user's repository.
With singleConfigFile: false (the default), there can be multiple +conifg.ts files and multiple "export files" such as +onBeforeRender.ts.

That's it.
I foresee singleConfigFile: true to be used for large-scale complex apps, while singleConfigFile: false is convenient to quickly get started like Next.js.
Alright, I think this is ripe for an RFC.

could you show me how it works in the sandbox?

@champ7champ There isn't an example for this yet.

@brillout Will it be possible to try it soon?

Yes, soon, the overall V1 architecture is already implemented. It's now about implementing the long tail of details.


Drain workers API needs to be implemented

We need a way to drain a specific worker, so no new jobs are scheduled on the worker.
We already have a "is_paused" flag, so this might be the right place:
https://github.com/TraceMachina/turbo-cache/blob/5e2e81af8999482fef202b50ee880509e8811e6f/cas/scheduler/worker.rs#L97

I'd like to start working from this issue.

@steed924 Thanks a lot for taking this ❤
If you're running into any issues please  let me know what inconveniences you encountered when working with the Turbo-Cache codebase as it's my goal to make DX for the codebase as good as possible 😇

@aaronmondal, @allada .  Thanks for your offer.
Since I'm new to this project, I'd like to ask some questions to get my hands dirty in this project.
I found out that there are already functions declared to remove the worker with its id in simple_scheduler.rs.
Is drain workers API mean just the API to make the worker's is_paused flag to false?
Sorry for my sillly question. Thanks.
https://github.com/TraceMachina/turbo-cache/blob/5e2e81af8999482fef202b50ee880509e8811e6f/cas/scheduler/simple_scheduler.rs#L129C1-L129C74


Hyper CLI doesn't work on macOS

Hey
I've installed Hyper twice now on my brand new Macbook, and even though I restart the machine I can't use hyper i verminal or any other commands. What am I doing wrong?

@sebastianks I just installed on my Mac and it's working fine.

What kind of output do you get?
Can you lunch hyper not through the terminal?

@sebastianks Hi👋 It would be great if you could provide us with more information to help you 🙏 ❤

Hi both. All of a sudden it's working today. Even though I restarted my mac several times it didn't work, but today after turning on my mac it's working just fine. Weird, but great.
Thank you for your time 👍


Bug: Fix recursion error when using streaming with tools

Added additional param to allow independent turning off for streaming to ensure recursion errors don't occur when calling generate from stream with streaming=True on BedrockChat

Fixes #80

@3coins @bigbernnn can you please have a look? I confirmed this PR is working

@munday-tech Looks good! Please fix small linting error.

@bigbernnn Should be fixed now.

Is there any update on this? I am facing the same issue

I did a quick test with this solution. While this fixes the recursion bug, the tokens are still not being streamed (for claude-3 models)

@munday-tech  could you review the changes requested and also confirm if the tokens get streamed with this fix ?

Working on another PR, that allows streaming to work as expected. The change here uses interplay between _stream and _generate and does not return the chunks independently. See https://github.com/langchain-ai/langchain-aws/pull/119

Fixed by #119


Random Chorus Fruit Growing Sounds

Minecraft: Fabric Loader 0.12.12 (Minecraft 1.18.1)
Mod: 2.0.2 for 1.18
For whatever reason while playing normally, I occasionally every now and then hear random Chorus Fruit growing sounds playing for some reason. Nothing appears on the subtitles either, although I still hear the sound frequently.

A similar issue has been fixed earlier, but should be okay in 2.0.2 - might be another mod incompatibility. Could you perhaps post your mod list?

#24 this other issue is the same as this but this one describes it better.

Closing as unable to reproduce. I may have fixed this issue earlier accidentally, but I cannot get this effect with the same mods as listed in #24. If you still face this bug in 2.3.0, please reopen the issue with a log.