vue ssr和vue-server-renderer原理
原理程序想要成为可交互的spa就必须要用client bundle// 安装npm i vue-server-renderer express -D步骤:1)用renderToString()把vue实例挂载的内容变成html,返回给客户端1、把vue实例变成html// nodejs服务器const express = require("express");const Vu...
·
什么是ssr
进阶1:把vue实例变成html
用renderToString()把vue实例挂载的内容变成html,返回给客户端
// 安装
npm i vue-server-renderer express -D
// nodejs服务器
const express = require("express");
const Vue = require("vue");
// 创建express实例和vue实例
const app = express();
// 创建渲染器
// 是一个工厂函数:一个函数返回一个对象
const renderer = require("vue-server-renderer").createRenderer();
const page = new Vue({
data: { title: "哈哈哈" },
template: "<div>{{title}}<div> hello, vue ssr! </div></div>"
});
app.get("/", async (req, res) => {
try {
const html = await renderer.renderToString(page);
console.log(html);
res.send(html);
} catch (err) {
res.status(500).send("服务器内部错误");
}
});
app.listen(3000, () => {
console.log("服务器启动成功");
});
进阶2:结合vue-router来访问不同页面
src
├── components
│ ├── Detail.vue
│ └── Index.vue
├── App.vue
├── app.js # 通用入口
├── entry-client.js # 客户端入口,仅运行于浏览器
└── entry-server.js # 服务端入口,仅运行于服务器
// 安装
npm install webpack-node-externals lodash.merge -D
// 脚本配置
npm i cross-env -D
// package.json
"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
// webpack插件
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");
// 环境变量:决定入口是客户端还是服务端
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: {
// 此处告知 server bundle 使用 Node 风格导出模块
libraryTarget: TARGET_NODE ? "commonjs2" : undefined
},
// 外置化应用程序依赖模块。可以使服务器构建速度更快,并生成较小的 bundle 文件。
externals: TARGET_NODE
? nodeExternals({
// 不要外置化 webpack 需要处理的依赖模块。
// 可以在这里添加更多的文件类型。例如,未处理 *.vue 原始文件,
// 你还应该将修改 `global`(例如 polyfill)的依赖模块列入白名单
whitelist: [/\.css$/]
})
: undefined,
optimization: {
splitChunks: undefined
},
// 这是将服务器的整个输出构建为单个 JSON 文件的插件。
// 服务端默认文件名为 `vue-ssr-server-bundle.json`
plugins: [TARGET_NODE ? new VueSSRServerPlugin() : new VueSSRClientPlugin()]
}),
chainWebpack: config => {
config.module
.rule("vue")
.use("vue-loader")
.tap(options => {
merge(options, {
optimizeSSR: false
});
});
}
};
// router.js
import Vue from 'vue'
import Router from 'vue-router'
import Index from '@/components/Index'
import Detail from '@/components/Detail'
Vue.use(Router)
// 这里为什么不导出一个router实例哪?
// 每次用户请求都需要创建新router实例,如果用户请求多次都用一个实例会造成数据污染
export default function createRouter() {
return new Router({
mode: 'history',
routes: [
{path: '/', component: Index},
{path: '/detail', component: Detail},
]
})
}
// server/index.js
/* eslint-disable no-console */
// 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');
const renderer = createBundleRenderer(serverBundle, {
runInNewContext: false,
template: fs.readFileSync('../public/index.temp.html', 'utf-8'), // 宿主模板文件
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'
}
// console.log(req.url);
const html = await renderer.renderToString(context); // 之前接收vue实例,现在接收上下文
// console.log(html);
res.send(html);
} catch (error) {
console.log(error);
res.status(500).send("服务器内部错误");
}
});
app.listen(3000, () => {
console.log("渲染服务器启动成功");
});
vue-server-renderer源码解读
- serverBundle(生成的vue-ssr-server-bundle.json):提供sourceMap,里面包含了源代码编译后的所有内容,将用于服务端渲染输出html的正文。
- clientManifest(生成的vue-ssr-client-manifest.json):注入首次渲染需要加载script标签。
renderToString(context)的时候就可以根据context的url,去serverBundle提供的sourceMap,匹配搭配出要渲染的内容和客户端Bundle提供的script内容。(程序想要成为可交互的spa就必须要用client bundle)
更多推荐
已为社区贡献8条内容
所有评论(0)