携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第4天,点击查看活动详情
- 历史章节
服务端渲染就是由服务端返回页面给客户端,老牌技术有
jsp、aps、php等技术,而像vue、react等技术是属于客户端渲染。
客户端渲染的优势就是更少的带宽,更快的响应速度(因为请求和响应的内容变少)。
问题就是seo不友好,Time-To-Content更长(因为要等所有的资源加载完毕才会开始渲染页面)。
SSR(Server Side Render)正是为了解决这些问题而出现的技术。
本质上,SSR 是一种在服务端将组件渲染 HTML 字符串并发送到浏览器,最后在浏览器上将这些 HTML 片段“激活”为客户端上可交互的应用技术。
搭建vue环境
本次项目为新搭建的环境,所以不使用之前的配置文件,这里默认是空的目录。
这边建议新建一个空目录,然后npm init -y生成一个package.json就可以跟着我开始了。
- 根目录下新建
src目录 src目录下新建App.vue文件,内容如下
<template>
<div :class="['main', cls]">
<h3>{{ message }}</h3>
<button @click="handleClick">Toggle</button>
</div>
</template>
<script>
import { ref, computed } from "@vue/reactivity";
export default {
setup() {
const isActivity = ref(false);
const cls = computed(() => (isActivity.value ? "activate" : "deactivate"));
const handleClick = () => {
isActivity.value = !isActivity.value;
};
return {
isActivity,
message: "Hello World",
cls,
handleClick,
};
},
};
</script>
<style>
h3 {
color: #42b983;
}
.main {
position: fixed;
top: 0;
left: 0;
bottom: 0;
right: 0;
padding: 20px 12px;
transition: background 0.3s linear;
}
.activate {
background: #000;
}
.deactivate {
background: #fff;
}
</style>
需要为客户端、服务端环境分别准备项目
Entry文件,所以需要创建客户端的entry-client.js和服务端的entry-server.js
src目录下新建entry-client.js,内容如下
import { createSSRApp } from "vue";
import App from "./App.vue";
createSSRApp(App).mount("#app");
src目录下新建entry-server.js,内容如下
import { createSSRApp } from "vue";
import App from "./App.vue";
export default () => {
return createSSRApp(App);
};
搭建ssr环境
ssr需要有两份资源文件,一份client客户端,一份server服务端。
- 根目录下新建
webpack.base.js文件,主要是复用配置
const path = require('path');
const { VueLoaderPlugin } = require("vue-loader");
module.exports = {
output: {
filename: '[name].js',
path: path.resolve(__dirname, 'dist')
},
module: {
rules: [
{
test: /.js$/,
use: {
loader: 'babel-loader',
}
},
{
test: /.vue$/,
use: {
loader: 'vue-loader'
}
}
]
},
resolve: {
extensions: ['.ts', '.js'],
},
plugins: [
new VueLoaderPlugin()
]
}
- 根目录下新建
webpack.client.js文件,主要是生成客户端资源的配置
const Merge = require("webpack-merge");
const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const {WebpackManifestPlugin} = require("webpack-manifest-plugin");
const base = require("./webpack.base");
// 继承自 `webpack.base.js`
module.exports = Merge.merge(base, {
mode: "development",
entry: {
// 入口指向 `entry-client.js` 文件
client: path.join(__dirname, "./src/entry-client.js"),
},
output: {
publicPath: "/",
},
module: {
rules: [{test: /.css$/, use: ["style-loader", "css-loader"]}],
},
plugins: [
// 这里使用 webpack-manifest-plugin 记录产物分布情况
// 方面后续在 `server.js` 中使用
new WebpackManifestPlugin({fileName: "manifest-client.json"}),
// 自动生成 HTML 文件内容
new HtmlWebpackPlugin({
templateContent: `
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Webpack App</title>
</head>
<body>
<div id="app" />
</body>
</html>
`,
}),
],
});
- 根目录下新建
webpack.server.js文件,主要是生成服务端资源的配置
const Merge = require("webpack-merge");
const path = require("path");
const {WebpackManifestPlugin} = require("webpack-manifest-plugin");
const base = require("./webpack.base");
module.exports = Merge.merge(base, {
mode: "production",
entry: {
server: path.join(__dirname, "src/entry-server.js"),
},
target: "node",
output: {
// 打包后的结果会在 node 环境使用
// 因此此处将模块化语句转译为 commonjs 形式
libraryTarget: "commonjs2",
},
module: {
rules: [
{
test: /.css$/,
use: [
// 注意,这里用 `vue-style-loader` 而不是 `style-loader`
// 因为 `vue-style-loader` 对 SSR 模式更友好
"vue-style-loader",
{
loader: "css-loader",
options: {
esModule: false,
},
},
],
},
],
},
plugins: [
// 这里使用 webpack-manifest-plugin 记录产物分布情况
// 方面后续在 `server.js` 中使用
new WebpackManifestPlugin({fileName: "manifest-server.json"}),
],
});
为了方便构建,所以我这里还是将生成资源文件的命令封装到了
package.json文件中,如下
{
"scripts": {
"build:client": "npx webpack --config webpack.client.js",
"build:server": "npx webpack --config webpack.server.js"
}
}
完成上述操作后,执行npm run build:client和npm run build:server分别生成客服端和服务端的资源文件。
使用node搭建发布环境
- 根目录下新建
server.js,内容如下
const express = require("express");
const path = require("path");
const { renderToString } = require("@vue/server-renderer");
// 通过 manifest 文件,找到正确的产物路径
const clientManifest = require("./dist/manifest-client.json");
const serverManifest = require("./dist/manifest-server.json");
const serverBundle = path.join(
__dirname,
"./dist",
serverManifest["server.js"]
);
// 这里就对标到 `entry-server.js` 导出的工厂函数
const createApp = require(serverBundle).default;
const server = express();
server.get("/", async (req, res) => {
const app = createApp();
const html = await renderToString(app);
const clientBundle = clientManifest["client.js"];
res.send(`
<!DOCTYPE html>
<html>
<head>
<title>Vue SSR Example</title>
</head>
<body>
<!-- 注入组件运行结果 -->
<div id="app">${html}</div>
<!-- 注入客户端代码产物路径 -->
<!-- 实现 Hydrate 效果 -->
<script src="${clientBundle}"></script>
</body>
</html>
`);
});
server.use(express.static("./dist"));
server.listen(3000, () => {
console.log("服务启动成功:http://localhost:3000");
);
- 命令行:
node server.js运行查看效果吧
由于这里涉及到的依赖比较多,所以这一节就没有讲安装依赖的代码了,所以这里直接附上
package.json文件
{
"name": "vue-ssr",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"build:client": "npx webpack --config webpack.client.js",
"build:server": "npx webpack --config webpack.server.js",
"test": "echo "Error: no test specified" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"@babel/core": "^7.18.9",
"@vue/server-renderer": "^3.2.37",
"babel-loader": "^8.2.5",
"css-loader": "^6.7.1",
"express": "^4.18.1",
"html-webpack-plugin": "^5.5.0",
"style-loader": "^3.3.1",
"vue": "^3.2.37",
"vue-loader": "^17.0.0",
"vue-style-loader": "^4.1.3",
"webpack": "^5.74.0",
"webpack-cli": "^4.10.0",
"webpack-manifest-plugin": "^5.0.0",
"webpack-merge": "^5.8.0"
}
}
总结
本文介绍如何使用 Webpack 开发 Vue 应用,从最基础的 Vue SFC 文件编译;到复用更多基础编译工具;再到 SSR、SSG 等高阶用法;以及如何借助 Vue CLI 迅速搭建开发环境。
虽然 Vue 官方团队已经推出了一套更轻、更快的编译工具:Vite,但相关生态还不太成熟,短期内使用 Webpack 依然会是更保险可控的方案,因此依然值得投入时间精力深入学习 Webpack。
我这里把
vue-cli的部分给省略了,我是学习webpack的,所以只记录和webpakc相关的,其他的当做拓展学习,这里提一下Nuxt.js、Quasar,感兴趣的可以自己去找资源学习,瑞思拜。
示例代码:vue-ssr