Skip to content

Commit 86cdb39

Browse files
author
naremloa
committed
[feat]
- 將專案改成 SSR 的渲染模式。
1 parent f0cd1b9 commit 86cdb39

File tree

7 files changed

+1178
-583
lines changed

7 files changed

+1178
-583
lines changed

index.html

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,12 @@
22
<html lang="en">
33
<head>
44
<meta charset="UTF-8" />
5-
<link rel="icon" href="/favicon.ico" />
65
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
76
<title>Vite App</title>
7+
<!--preload-links-->
88
</head>
99
<body>
10-
<div id="app"></div>
11-
<script type="module" src="/src/main.js"></script>
10+
<div id="app"><!--app-html--></div>
11+
<script type="module" src="/src/entry-client.js"></script>
1212
</body>
1313
</html>

package.json

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,19 @@
22
"name": "phelomi",
33
"version": "0.0.0",
44
"scripts": {
5-
"dev": "vite",
6-
"build": "vite build",
7-
"serve": "vite preview"
5+
"start": "cross-env NODE_ENV=production node server",
6+
"dev": "node server",
7+
"build": "yarn build:client && yarn build:server",
8+
"build:client": "vite build --ssrManifest --outDir dist/client",
9+
"build:server": "vite build --ssr src/entry-server.js --outDir dist/server"
810
},
911
"dependencies": {
12+
"@vue/server-renderer": "^3.0.6",
1013
"axios": "^0.21.1",
14+
"compression": "^1.7.4",
15+
"cross-env": "^7.0.3",
16+
"express": "^4.17.1",
17+
"serve-static": "^1.14.1",
1118
"swiper": "^6.4.14",
1219
"vue": "^3.0.5",
1320
"vue-router": "4"

server.js

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
/* eslint-disable import/no-extraneous-dependencies */
2+
/* eslint-disable global-require */
3+
/* eslint-disable import/no-unresolved */
4+
const fs = require('fs');
5+
const path = require('path');
6+
const express = require('express');
7+
8+
const PORT = process.env.PORT || 8080;
9+
10+
async function createServer(
11+
root = process.cwd(),
12+
isProd = process.env.NODE_ENV === 'production',
13+
) {
14+
const resolve = (p) => path.resolve(__dirname, p);
15+
16+
const indexProd = isProd
17+
? fs.readFileSync(resolve('dist/client/index.html'), 'utf-8')
18+
: '';
19+
20+
const manifest = isProd
21+
? require('./dist/client/ssr-manifest.json')
22+
: {};
23+
24+
const app = express();
25+
26+
let vite;
27+
if (!isProd) {
28+
vite = await require('vite').createServer({
29+
root,
30+
logLevel: 'info',
31+
server: {
32+
middlewareMode: true,
33+
},
34+
});
35+
app.use(vite.middlewares);
36+
} else {
37+
app.use(require('compression')());
38+
app.use(
39+
require('serve-static')(resolve('dist/client'), {
40+
index: false,
41+
}),
42+
);
43+
}
44+
45+
app.use('*', async (req, res) => {
46+
try {
47+
const url = req.originalUrl;
48+
49+
let template;
50+
let render;
51+
if (!isProd) {
52+
// always read fresh template in dev
53+
template = fs.readFileSync(resolve('./index.html'), 'utf-8');
54+
template = await vite.transformIndexHtml(url, template);
55+
render = (await vite.ssrLoadModule('/src/entry-server.js')).render;
56+
} else {
57+
template = indexProd;
58+
render = require('./dist/server/entry-server.js').render;
59+
}
60+
61+
const [appHtml, preloadLinks] = await render(url, manifest);
62+
63+
const html = template
64+
.replace('<!--preload-links-->', preloadLinks)
65+
.replace('<!--app-html-->', appHtml);
66+
67+
res.status(200).set({ 'Content-Type': 'text/html' }).end(html);
68+
} catch (e) {
69+
if (vite) vite.ssrFixStacktrace(e);
70+
console.log(e.stack);
71+
res.status(500).end(e.stack);
72+
}
73+
});
74+
75+
return { app, vite };
76+
}
77+
78+
createServer().then(({ app }) => app.listen(PORT, () => {
79+
console.log(`App listening on port ${PORT}`);
80+
console.log('Press Ctrl+C to quit.');
81+
}));

src/entry-client.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import { createApp } from './main';
2+
3+
const { app, router } = createApp();
4+
5+
// wait until router is ready before mounting to ensure hydration match
6+
router.isReady().then(() => {
7+
app.mount('#app');
8+
});

src/entry-server.js

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
/* eslint-disable import/no-extraneous-dependencies */
2+
import { renderToString } from '@vue/server-renderer';
3+
import { createApp } from './main';
4+
5+
function renderPreloadLink(file) {
6+
if (file.endsWith('.js')) {
7+
return `<link rel="modulepreload" crossorigin href="${file}">`;
8+
}
9+
if (file.endsWith('.css')) {
10+
return `<link rel="stylesheet" href="${file}">`;
11+
}
12+
// TODO
13+
return '';
14+
}
15+
16+
function renderPreloadLinks(modules, manifest) {
17+
let links = '';
18+
const seen = new Set();
19+
modules.forEach((id) => {
20+
const files = manifest[id];
21+
if (files) {
22+
files.forEach((file) => {
23+
if (!seen.has(file)) {
24+
seen.add(file);
25+
links += renderPreloadLink(file);
26+
}
27+
});
28+
}
29+
});
30+
return links;
31+
}
32+
33+
export async function render(url, manifest) {
34+
const { app, router } = createApp();
35+
36+
// set the router to the desired URL before rendering
37+
router.push(url);
38+
await router.isReady();
39+
40+
// passing SSR context object which will be available via useSSRContext()
41+
// @vitejs/plugin-vue injects code into a component's setup() that registers
42+
// itself on ctx.modules. After the render, ctx.modules would contain all the
43+
// components that have been instantiated during this render call.
44+
const ctx = {};
45+
const html = await renderToString(app, ctx);
46+
47+
// the SSR manifest generated by Vite contains module -> chunk/asset mapping
48+
// which we can then use to determine what files need to be preloaded for this
49+
// request.
50+
const preloadLinks = renderPreloadLinks(ctx.modules, manifest);
51+
return [html, preloadLinks];
52+
}

src/main.js

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,19 @@
1-
import { createApp } from 'vue';
1+
import { createSSRApp } from 'vue';
22
import { createRouter } from './router';
33
import App from './App.vue';
44
import './styles/index.css';
55

6-
const app = createApp(App);
7-
const router = createRouter();
8-
app.use(router);
9-
app.mount('#app');
6+
// SSR requires a fresh app instance per request, therefore we export a function
7+
// that creates a fresh app instance. If using Vuex, we'd also be creating a
8+
// fresh store here.
9+
10+
// import { worker } from './mocks/browser';
11+
12+
// worker.start();
13+
14+
export function createApp() {
15+
const app = createSSRApp(App);
16+
const router = createRouter();
17+
app.use(router);
18+
return { app, router };
19+
}

0 commit comments

Comments
 (0)