Skip to content

Commit

Permalink
[feat]
Browse files Browse the repository at this point in the history
- 將專案改成 SSR 的渲染模式。
  • Loading branch information
naremloa committed Mar 2, 2021
1 parent 3f13584 commit 213d088
Show file tree
Hide file tree
Showing 7 changed files with 604 additions and 23 deletions.
6 changes: 3 additions & 3 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite App</title>
<!--preload-links-->
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.js"></script>
<div id="app"><!--app-html--></div>
<script type="module" src="/src/entry-client.js"></script>
</body>
</html>
13 changes: 10 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,19 @@
"name": "phelomi",
"version": "0.0.0",
"scripts": {
"dev": "vite",
"build": "vite build",
"serve": "vite preview"
"start": "cross-env NODE_ENV=production node server",
"dev": "node server",
"build": "yarn build:client && yarn build:server",
"build:client": "vite build --ssrManifest --outDir dist/client",
"build:server": "vite build --ssr src/entry-server.js --outDir dist/server"
},
"dependencies": {
"@vue/server-renderer": "^3.0.6",
"axios": "^0.21.1",
"compression": "^1.7.4",
"cross-env": "^7.0.3",
"express": "^4.17.1",
"serve-static": "^1.14.1",
"swiper": "^6.4.14",
"vue": "^3.0.5",
"vue-router": "4"
Expand Down
81 changes: 81 additions & 0 deletions server.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
/* eslint-disable import/no-extraneous-dependencies */
/* eslint-disable global-require */
/* eslint-disable import/no-unresolved */
const fs = require('fs');
const path = require('path');
const express = require('express');

const PORT = process.env.PORT || 8080;

async function createServer(
root = process.cwd(),
isProd = process.env.NODE_ENV === 'production',
) {
const resolve = (p) => path.resolve(__dirname, p);

const indexProd = isProd
? fs.readFileSync(resolve('dist/client/index.html'), 'utf-8')
: '';

const manifest = isProd
? require('./dist/client/ssr-manifest.json')
: {};

const app = express();

let vite;
if (!isProd) {
vite = await require('vite').createServer({
root,
logLevel: 'info',
server: {
middlewareMode: true,
},
});
app.use(vite.middlewares);
} else {
app.use(require('compression')());
app.use(
require('serve-static')(resolve('dist/client'), {
index: false,
}),
);
}

app.use('*', async (req, res) => {
try {
const url = req.originalUrl;

let template;
let render;
if (!isProd) {
// always read fresh template in dev
template = fs.readFileSync(resolve('./index.html'), 'utf-8');
template = await vite.transformIndexHtml(url, template);
render = (await vite.ssrLoadModule('/src/entry-server.js')).render;
} else {
template = indexProd;
render = require('./dist/server/entry-server.js').render;
}

const [appHtml, preloadLinks] = await render(url, manifest);

const html = template
.replace('<!--preload-links-->', preloadLinks)
.replace('<!--app-html-->', appHtml);

res.status(200).set({ 'Content-Type': 'text/html' }).end(html);
} catch (e) {
if (vite) vite.ssrFixStacktrace(e);
console.log(e.stack);
res.status(500).end(e.stack);
}
});

return { app, vite };
}

createServer().then(({ app }) => app.listen(PORT, () => {
console.log(`App listening on port ${PORT}`);
console.log('Press Ctrl+C to quit.');
}));
8 changes: 8 additions & 0 deletions src/entry-client.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { createApp } from './main';

const { app, router } = createApp();

// wait until router is ready before mounting to ensure hydration match
router.isReady().then(() => {
app.mount('#app');
});
52 changes: 52 additions & 0 deletions src/entry-server.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/* eslint-disable import/no-extraneous-dependencies */
import { renderToString } from '@vue/server-renderer';
import { createApp } from './main';

function renderPreloadLink(file) {
if (file.endsWith('.js')) {
return `<link rel="modulepreload" crossorigin href="${file}">`;
}
if (file.endsWith('.css')) {
return `<link rel="stylesheet" href="${file}">`;
}
// TODO
return '';
}

function renderPreloadLinks(modules, manifest) {
let links = '';
const seen = new Set();
modules.forEach((id) => {
const files = manifest[id];
if (files) {
files.forEach((file) => {
if (!seen.has(file)) {
seen.add(file);
links += renderPreloadLink(file);
}
});
}
});
return links;
}

export async function render(url, manifest) {
const { app, router } = createApp();

// set the router to the desired URL before rendering
router.push(url);
await router.isReady();

// passing SSR context object which will be available via useSSRContext()
// @vitejs/plugin-vue injects code into a component's setup() that registers
// itself on ctx.modules. After the render, ctx.modules would contain all the
// components that have been instantiated during this render call.
const ctx = {};
const html = await renderToString(app, ctx);

// the SSR manifest generated by Vite contains module -> chunk/asset mapping
// which we can then use to determine what files need to be preloaded for this
// request.
const preloadLinks = renderPreloadLinks(ctx.modules, manifest);
return [html, preloadLinks];
}
19 changes: 14 additions & 5 deletions src/main.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,22 @@
import { createApp } from 'vue';
import { createSSRApp } from 'vue';
import { createRouter } from './router';
import App from './App.vue';
import './styles/index.css';

import { worker } from './mocks/browser';

worker.start();
// SSR requires a fresh app instance per request, therefore we export a function
// that creates a fresh app instance. If using Vuex, we'd also be creating a
// fresh store here.

const app = createApp(App);
const router = createRouter();
app.use(router);
app.mount('#app');
// import { worker } from './mocks/browser';

// worker.start();

export function createApp() {
const app = createSSRApp(App);
const router = createRouter();
app.use(router);
return { app, router };
}
Loading

0 comments on commit 213d088

Please sign in to comment.