- Tiny core, extendable via a plugin architecture
- Vite Dev server in SSR mode
- Multi-Page App
- Serverless template building
npm i @ssr-glue/vite-plugin
// vite.config.js
import { defineConfig } from 'vite'
import SSRGluePlugin from '@ssr-glue/vite-plugin'
export default defineConfig({
plugins: [
SSRGluePlugin()
]
})
The client side entry script is the one you specified in your index.html
.
To enable SSR, you should have a server entry script counterpart.
By default, the plugin will try to load <project-root>/src/main-server.(js|ts)
for the server entry script.
You can also specify it explicitly via the serverEntryScriptsMap
option.
For example:
import path from 'path'
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import SSRGluePlugin from '@ssr-glue/vite-plugin'
export const resolvePath = (filepath: string) => path.resolve(__dirname, filepath)
export default defineConfig({
plugins: [
SSRGluePlugin({
serverEntryScriptsMap: {
[resolvePath('index.html')]: resolvePath('src/main-server.ts'),
[resolvePath('landing-page/index.html')]: resolvePath('landing-page/src/main-server.ts'),
},
}),
vue(),
],
})
Here the key of serverEntryScriptsMap
is the path of your index.html
file, while the value is the path of your server entry script for rendering that index.html.
To make SSR work, you need 2 type of scripts:
- Server Entry Script
- Client Entry Script
The "client entry script" is the script you've given in the index.html
.
Obviously, if you only have such a script, it's just a normal SPA app, to enable SSR what you need is a "server entry script" for rendering the HTML at the server side.
This plugin expects your server entry scripts exports an instance of ServerSideApplication
of ssr-glue.
Which allow you to specify the ssr-glue plugins you'd like to use for your SSR app.
It will be more clear to see an actual example setup.
So for example, the structure of the src
directory looks like this:
src
├── App.vue
├── main-client.ts
├── main-server.ts
├── main-universal.ts
└── pages
├── About.vue
└── Home.vue
Here is our server entry script:
//main-server.ts
import App from './App.vue'
import { vueAppPlugin } from 'ssr-glue-plugin-server-vue'
import { generateRoutesFromPages } from './main-universal'
import { ServerSideApplication } from '@ssr-glue/server-libs'
import { useHeadPlugin } from 'ssr-glue-plugin-server-vue-usehead'
const routes = generateRoutesFromPages()
export default function ServerSideApplicationBuilder() {
return new ServerSideApplication({
plugins: [
vueAppPlugin({
app: App,
routes,
}),
useHeadPlugin(),
],
})
}
// main-universal.ts
// This is used by both server and client entry script
export function generateRoutesFromPages() {
const pages = import.meta.glob(`./pages/*.vue`)
return Object.keys(pages).map((path) => {
const name = path.match(/\.\/pages(.*)\.vue$/)![1].toLowerCase()
return {
path: name === '/home' ? '/' : name,
component: pages[path], // () => import('./pages/*.vue')
}
})
}
Here, two things you may have noticed:
- The default exported object is a function that returns an instance of
ServerSideApplication
- We're using 2 ssr-glue plugins:
ssr-glue-plugin-server-vue
andssr-glue-plugin-server-vue-usehead
ssr-glue-plugin-server-vue
is used for rendering the Vue.js app, in addition, ssr-glue-plugin-server-vue-usehead
is
used for rendering the meta tags, html/body attributes(see vueuse/head for more detail).
Also note that the order of the plugin matters, make sure you put ssr-glue-plugin-server-vue-usehead
after ssr-glue-plugin-server-vue
.
As the example shows, you should pass the App
component(your root component of your app) and the routes
to the plugin,
then the plugin will create the app and router on behalf of you.
Here we are generating the routes ourselves, but you really should delegate the task to another plugin like vite-plugin-pages.
If you want to access the created app instance, you could pass a callback like this:
vueAppPlugin({
app: App,
routes,
onAppCreated(app){
// app.use(...)
// app.component(...)
}
})
Now, let's see how would our client entry script look like.
// main-client.ts
import App from './App.vue'
import { vueAppPlugin } from 'ssr-glue-plugin-client-vue'
import { generateRoutesFromPages } from './main-universal'
import { ClientSideApplication } from '@ssr-glue/client-libs'
import { useHeadPlugin } from 'ssr-glue-plugin-client-vue-usehead'
const routes = generateRoutesFromPages()
const app = new ClientSideApplication({
plugins: [
vueAppPlugin({
app: App,
routes,
}),
useHeadPlugin(),
],
})
app.boot()
Looks like the exactly same as the server entry script...?
No! If you look carefully, all server***
has been replaced with client***
.
For instance:
ServerSideApplication
--->ClientSideApplication
ssr-glue-plugin-server-vue
--->ssr-glue-plugin-client-vue
One more difference is that the server entry script export a default function that returns an ServerSideApplication
instance, whereas
the client entry script does not, instead it calls the boot
method of the ClientSideApplication
.
Why? Because ClientSideApplication
is going to run immediately on the browser, whereas ServerSideApplication
should be boot
on every single incoming HTTP request.
Actually, you can use any code in your client entry script, but as a result, it should be synced with the server entry script. So ssr-glue plugin provides you the "client" counterpart plugins as well just for convenient.
How about the index.html
would look like? Here is an example:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<link rel="icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<!--head-tags-->
</head>
<body>
<div id="app"><!--app-html--></div>
</body>
</html>
where <!--app-html-->
will be replaced with The rendered HTML for App
component. This is possible because we are
using the ssr-glue-plugin-server-vue
ssr-glue plugin.
[ Note! ]
Make sure there are no spaces around the <!--app-html-->
, otherwise you will get an error for the client side hydration.
Here is a BAD example:
<div id="app">
<!--app-html-->
</div>
after you've got the above setup done, when you run the vite
command, it should work in the SSR mode now.
If you want to run it in SPA mode for some reason, you can set the environment variable SPA_MODE
to true
,
For example:
(Don't forget also install the cross-env
package)
{
"scripts": {
"dev": "vite",
"dev:spa": "cross-env VITE_SPA_MODE=true vite"
}
}
In addition to the normal build, you are also going to build for the server entry script.
Our scripts in package.json
would be something look like this:
{
"scripts": {
"dev": "vite",
"build": "yarn build:client && yarn build:server",
"build:client": "vite build --outDir dist/client",
"build:server": "vite build --outDir dist/server --ssr src/main-server.ts"
}
}
The build:server
is for our server entry bundling.
Note that you must give the --ssr
flag and specify the entry script explicitly.
For more details, see Building for Production of Vite official guide.
As for how to use the built assets, it's out of topic, but you can see a full example include the node server implementation under the playground directory.
Waiting for bugfix in Vite...
Contribution are always welcomed, feel free to send PRs 🖐🏼