Open brillout opened 4 years ago
I had some additional thoughts on this topic as I’ve been converting more of my app to use Goldpage. Hopefully this is somewhat useful as you are considering solutions.
The friction originates in the .page.js
files: that's where I should import .vue
files, which means the .page.js
files must be fed to webpack as entry points for both the server and client builds.
This has the undesirable side effect of forcing the .page.js
files to be universal: no modules may be imported that can’t run in both Node.js and a browser. This feels far too restrictive.
And while a solution like Wildcard API does mitigate this somewhat, it still feels unnatural that I'm forced to move page-specific data-fetching logic like database queries outside of .page.js
config files and into an API layer long before it makes sense to build an API separate from the page-rendering logic.
The .page.js
files also force me to use a programming model that is wholly different from what I'm using in the rest of my app. (In my case, most of my Node.js server is written in Express, though the same argument applies for other frameworks like Hapi or Koa.)
For me, the ideal interface for SSRing Vue components is just a function—let’s call it render()
—that:
.vue
source file and some additional options (such as initial props / data, a page title, or an HTML template / function),Promise
of an HTML string that can be returned to the browser that is a complete HTML page, including any supporting resources (linked or embedded JS, CSS, etc),For what it's worth, here’s the Vue SSR programming model that I would love (though I have no doubt this isn’t easy to pull off):
// In an ideal world, this file doesn't get built (it’s not a webpack entry point).
// Could be written with `require()` instead of `import` if not using `esm`.
import { render } from "@goldpage/vue";
import express from "express";
import { query } from "~/db";
import { meta, analytics } from "~/util";
const app = express();
app.get("/", async (req, res, next) => {
// heavily inspired by Goldpage `.page.js` config files
const options = {
// seems reasonable that `renderToHtml = true` be the default,
// so I shouldn't need to include it here
renderToHtml: true,
// what we really want is to pass *data* to the component, not *props*.
// `data` is internal to the component and therefore modifiable by the component.
// `props` are external to the component (Vue warns when modifying a prop)
// (though I go back and forth on this. it's nice to be able to declare expected
// `props` and have Vue do the type checking for me)
data: {
user: req.user,
transactions: await query("SELECT * FROM transactions"),
},
// simple mechanism for having complete control over the final HTML output
htmlTemplate: (result) => `
<!DOCTYPE html>
<html lang="en" class="loading">
<head>
<title>Transactions</title>
${ meta }
${ result.head }
</head>
<body id="transactions">
${ result.body }
${ analytics }
</body>
</html>
`,
};
const html = await render("./index.vue", options);
res.send(html);
});
app.listen(8000);
The code snippet I've written above somewhat forces the following assumptions:
.vue
files.vue
files.vue
files so we don't pay the compilation cost for every call to render()
.vue
files to there's no compilation in production.vue
file and return a promise of a (compiled) Vue instance.vue
filepropsData
(or set its data
attribute, not clear which is the better pattern) and then be rendered as an HTML string (plus supporting resources)I don't at all presume you should take this approach, just offering some thoughts in case it triggers some useful insights. Given Goldpage's desire to be framework agnostic, I also don't know how well these ideas translate to other frameworks like React.
I hugely like the simplicity of having such render
function.
It comes with couple of problems but I'll try to find a way.
This is super interesting, thanks for the inspiration.
I keep you updated.
Btw about data
vs props
: I find props
and the following to be more idiomatic.
Vue.component('transactionsView', {
props: ['transactions'],
data: function () {
return {
mutableTransactions: this.transactions.slice(),
}
}
});
props
are external to the component
The props do come from outside the component since they are provided by Goldpage.
This is super interesting, thanks for the inspiration.
So great to hear 🙏 Really excited to hear if there's a way to make that work.
If something like this turns out to be doable, it could be especially exciting for Express users if Goldpage could offer an Express template engine for Vue components.
import express from "express";
import { query } from "~/db";
const app = express();
app.set("view engine", "vue");
app.get("/", function (req, res) {
// not sure where to set other options (page title, attributes on `<html>` or
// `<body>` elements, custom `<meta>` tags, analytics `<script>`s, etc)
const props = {
user: req.user,
transactions: await query("SELECT * FROM transactions"),
};
res.render("index", props);
});
(Though, that said, I tend to use template libs directly and not as Express template engines because that gives me more control when something doesn't work how I want it to.)
Btw about
data
vsprops
: I findprops
and the following to be more idiomatic.
Thanks for sharing the code snippet. Can definitely follow that pattern in the pages I write. 👍
:+1:
Let's see about templates if/once we get this render
design implemented.
In case you're curious; I'm currently looking into how to implement SSR with Parcel.
That's exciting. I do love the no-config spirit of Parcel.
Just found Servue, which seems to do something very similar: https://futureaus.github.io/servue/
The promise is great. Haven't tried it yet, so I just don't know how well it delivers on that promise.
In particular, this seems really promising:
Supports server-side Rendering Supports head management No build or compile files, all in-memory Supports custom [HTML] templating
Oh cool - I'll have a look at it. Thanks!
How did you find this library? And how did you find Goldpage btw.?
I think I found both Goldpage and Servue searching for possible options for server-rendering Vue components in Node.js.
I think I learned about Goldpage after arriving at https://github.com/brillout/awesome-universal-rendering.
I may have learned about Servue by way of an email newsletter than mentioned Ream and then finding https://vue-community.org/guide/ecosystem/server-side-rendering.html#available-options-for-vue.
Thanks!
I would like to raise a point.
Vue code should be universal, and you shouldn't ever need server-side libraries in a vue app, I would consider that an anti-pattern since vue is for the client side
In my experience, i've never had any issues building universal code. If you need to access window and document, you can do so in mounted()
, otherwise you can rely on the render functions and templates to do what you need to do
What you need is basically done by servue, which does it's job perfectly fine. However, there are some issues that might arise in big apps.
When a page is rendered, a bundle is created for every page, which means every dependency included in that bundle, which is stored in memory - this means high memory usage for larger apps
Compared to what I am currently building, my webpack bundler creates a full app with vue-router for client-side routing, with lazy-loading supported, however all files created are stored in a dist folder instead of in-memory, and accessible to the client via a static dist folder (webpack magic).
It's also better because code-splitting works and different pages can refer to the same dist files, and it supports hot-reloading
It's much faster, but it means that build files are generated in a folder. Although, it is possible to still store this type of app in a memory file system, less than that of servue (since pages can share code-split dependencies and lazy-loaded modules)
Paired with http2, i've reached some pretty good lighthouse stats (node, koa, koa-router, vue, vue-router, webpack)
Vue code should be universal, and you shouldn't ever need server-side libraries in a vue app, I would consider that an anti-pattern since vue is for the client side
That's the currently the case with Goldpage. Vue and Goldpage are independent of each other.
As discussed with @chriscalo in #11 we want a Goldpage user to be able to use Node.js code to add
initialProps
. Enabling the Goldpage user to use any SQL/ORM query in order to retrieve data that is being rendered by Vue/React.This is already possible today but the ergonomics could be improved.