这是一篇教程,从创立我的项目到革新我的项目
vue-cli 创立一个我的项目
在放你做 demo 的中央,创立一个我的项目
vue create vue-ssr
// 如果你装置了 vue-cli4,抉择 vue2 的版本,以下的改良过程是按 vue2 来做的
通过漫长的期待,下载好文件开始咱们的革新之路
文件目录
进入 vue-ssr 文件夹,应用命令
vue ui
把 vue-router 装上
先装置几个依赖插件
// 安不上用 cnpm,yarn,npx
npm i vue-server-renderer express -D
npm install webpack-node-externals lodash.merge -D
npm i cross-env -D
批改 package.json 文件
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"lint": "vue-cli-service lint"
}
改成
"scripts": {
"build:client": "vue-cli-service build",
"build:server": "cross-env WEBPACK_TARGET=node vue-cli-service build --mode server",
"build": "npm run build:server && npm run build:client"
}
根目录下创立 vue.config.js
// 服务器渲染的两个插件,管制 server 和 client
const VueSSRServerPlugin = require("vue-server-renderer/server-plugin"); // 生成服务端包
const VueSSRClientPlugin = require("vue-server-renderer/client-plugin"); // 生成客户端包
const nodeExternals = require("webpack-node-externals");
const merge = require("lodash.merge");
// 环境变量:决定入口是客户端还是服务端,WEBPACK_TARGET 在启动项中设置的,见 package.json 文件
const TARGET_NODE = process.env.WEBPACK_TARGET === "node";
const target = TARGET_NODE ? "server" : "client";
module.exports = {
css: {extract: false},
outputDir: "./dist/" + target,
configureWebpack: () => ({
// 将 entry 指向应用程序的 server / client 文件
entry: `./src/entry-${target}.js`,
// 对 bundle renderer 提供 source map 反对
devtool: "source-map",
// 这容许 webpack 以 Node 实用形式解决动静导入(dynamic import),// 并且还会在编译 Vue 组件时告知 `vue-loader` 输送面向服务器代码(server-oriented code)。target: TARGET_NODE ? "node" : "web",
node: TARGET_NODE ? undefined : false,
output: {
// 此处配置服务器端应用 node 的格调构建
libraryTarget: TARGET_NODE ? "commonjs2" : undefined
},
// 外置化应用程序依赖模块。能够使服务器构建速度更快,并生成较小的 bundle 文件。externals: TARGET_NODE
? nodeExternals({
// 不要外置化 webpack 须要解决的依赖模块。// 能够在这里增加更多的文件类型。例如,未解决 *.vue 原始文件,// 你还应该将批改 `global`(例如 polyfill)的依赖模块列入白名单(以前叫 whitelist,为了防止美国的人种歧视,改成了 allowlist)allowlist: [/\.css$/]
})
: undefined,
optimization: {splitChunks: TARGET_NODE ? false : undefined},
// 这是将服务器的整个输入构建为单个 JSON 文件的插件。// 服务端默认文件名为 `vue-ssr-server-bundle.json`
// 客户端默认文件名为 `vue-ssr-client-manifest.json`
plugins: [TARGET_NODE ? new VueSSRServerPlugin() : new VueSSRClientPlugin()]
}),
chainWebpack: config => {
config.module
.rule("vue")
.use("vue-loader")
.tap(options => {
merge(options, {optimizeSSR: false});
});
}
};
批改路由文件
import Vue from 'vue';
import Router from 'vue-router';
import Home from '../views/Home.vue';
import About from '../views/About.vue';
Vue.use(Router);
// 这里为什么不导出一个 router 实例?// 每次用户申请都须要创立新 router 实例,如果用户申请屡次都用一个实例会造成数据净化
export function createRouter() {
return new Router({
// 肯定要 history 模式,因为,hash 模式更改门路不会刷新,具体起因自行查问
mode: 'history',
routes: [{path: '/', name: 'Home',component: Home},
{path: '/about', name: 'About', component: About},
]
})
}
批改 main.js 文件
import Vue from "vue";
import App from "./App.vue";
import {createRouter} from "./router";
import store from "./store";
Vue.config.productionTip = false;
const router = createRouter();
// 这里的挂载 ($mount("#app")) 放到 entry-client.js 文件外面,前面会说到
export function createApp() {
const app = new Vue({
router,
store,
render: (h) => h(App),
});
return {app, router};
}
在 src 下增加 entry-client.js 和 entry-server.js 文件
entry-client.js
import {createApp} from './main.js';
const {app, router} = createApp();
router.onReady(()=>{app.$mount("#app");
})
entry-server.js
import {createApp} from "./main.js";
// context 实际上就是 server/index.js 外面传参,前面会说到 server/index.js
export default context => {return new Promise((resolve, reject) => {const {app, router} = createApp();
router.push(context.url)
router.onReady(()=>{
// 是否匹配到咱们要用的组件
const matchs = router.getMatchedComponents();
if(!matchs) {return reject({code: 404})
}
resolve(app);
}, reject);
})
}
在 src 下创立 server/index.js
// nodejs 服务器
const express = require("express");
const fs = require("fs");
// 创立 express 实例和 vue 实例
const app = express();
// 创立渲染器
const {createBundleRenderer} = require("vue-server-renderer");
const serverBundle = require("../../dist/server/vue-ssr-server-bundle.json");
const clientManifest = require("../../dist/client/vue-ssr-client-manifest.json");
// 这儿引入的文件是不同于 index.html 的问题,具体文件上面会讲到
const template = fs.readFileSync("../../public/index.ssr.html", "utf-8"); // 宿主模板文件
const renderer = createBundleRenderer(serverBundle, {
runInNewContext: false,
template,
clientManifest,
});
// 中间件解决动态文件申请
app.use(express.static("../../dist/client", { index: false})); // 为 false 是不让它渲染成 dist/client/index.html
// app.use(express.static('../dist/client'))
// 前端申请什么我都不关怀,所有的路由解决交给 vue
app.get("*", async (req, res) => {
try {
const context = {
url: req.url,
title: "ssr test",
};
// nodejs 流数据,文件太大,用 renderToString 会卡
const stream = renderer.renderToStream(context);
let buffer = [];
stream.on("data", (chunk) => {buffer.push(chunk);
});
stream.on("end", () => {res.end(Buffer.concat(buffer));
});
} catch (error) {console.log(error);
res.status(500).send("服务器外部谬误");
}
});
app.listen(3000, () => {console.log("渲染服务器启动胜利");
});
在 public 上面创立 index.ssr.html 文件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<!--vue-ssr-outlet-->
<!-- 下面这个肯定要留着,它是服务端渲染模版的标记,没有就会报错,不信能够删了试一下 -->
</body>
</html>
而后所有的革新实现,运行命令
// 先构建两个 json 文件
npm run build
再到 server 文件夹下运行
node index.js
// 如果显示: ` 渲染服务器启动胜利 `, 在浏览器关上 `localhost:3000` 端口,就能看到咱们的页面
整完这,你再去玩儿 nuxt,你感觉好多了,因为 nuxt 不必配路由,本人生成,连路由传参都设定好了