《Webpack5 核心原理与应用实践》学习笔记-> Vue服务端渲染

简介: 《Webpack5 核心原理与应用实践》学习笔记-> Vue服务端渲染


服务端渲染就是由服务端返回页面给客户端,老牌技术有jspapsphp等技术,而像vuereact等技术是属于客户端渲染。

客户端渲染的优势就是更少的带宽,更快的响应速度(因为请求和响应的内容变少)。

问题就是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:clientnpm 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.jsQuasar,感兴趣的可以自己去找资源学习,瑞思拜。

示例代码:vue-ssr


目录
相关文章
|
5天前
|
JavaScript 算法 编译器
vue3 原理 实现方案
【8月更文挑战第15天】vue3 原理 实现方案
16 1
|
1天前
|
JavaScript 前端开发 开发者
Vue学习之--------深入理解Vuex、原理详解、实战应用(2022/9/1)
这篇文章详细介绍了Vuex的基本概念、使用场景、安装配置、基本用法、实际应用案例以及注意事项,通过一个数字累加器的实战示例,帮助开发者深入理解Vuex的原理和应用。
|
5天前
|
JavaScript API
Vue学习之--------列表排序(ffilter、sort、indexOf方法的使用)、Vue检测数据变化的原理(2022/7/15)
这篇博客文章讲解了Vue中列表排序的方法,使用`filter`、`sort`和`indexOf`等数组方法进行数据的过滤和排序,并探讨了Vue检测数据变化的原理,包括Vue如何通过setter和数组方法来实现数据的响应式更新。
Vue学习之--------列表排序(ffilter、sort、indexOf方法的使用)、Vue检测数据变化的原理(2022/7/15)
|
5天前
|
JavaScript
Vue学习之--------列表渲染、v-for中key的原理、列表过滤的实现(2022/7/13)
这篇博客文章详细介绍了Vue中列表渲染的基础知识、`v-for`指令的使用、`key`的原理和列表过滤的实现。通过代码实例和测试效果,展示了如何遍历数组和对象、使用`key`属性优化渲染性能,以及如何实现列表的动态过滤功能。
Vue学习之--------列表渲染、v-for中key的原理、列表过滤的实现(2022/7/13)
|
5天前
|
JavaScript 前端开发
Vue学习之--------绑定样式、条件渲染、v-show和v-if的区别(2022/7/12)
这篇博客文章讲解了Vue中绑定样式和条件渲染的方法,包括类样式绑定的不同写法、`v-show`和`v-if`的条件渲染区别以及它们的使用场景和特点,并通过代码实例和测试效果来展示具体应用。
Vue学习之--------绑定样式、条件渲染、v-show和v-if的区别(2022/7/12)
|
10天前
|
JavaScript 前端开发 算法
vue底层原理实现方案
【8月更文挑战第10天】vue底层原理实现方案
25 2
|
11天前
|
JavaScript UED
强制 Vue 重新渲染组件的5种方法,解决你开发过程中数据和视图无法同步的Bug。
强制 Vue 重新渲染组件的5种方法,解决你开发过程中数据和视图无法同步的Bug。
|
7天前
|
JavaScript
VUE——三元表达式动态渲染样式
VUE——三元表达式动态渲染样式
16 0
|
7天前
|
JavaScript
VUE——filemanager-webpack-plugin报错TypeError: Cannot read property 'isFile' of undefined
VUE——filemanager-webpack-plugin报错TypeError: Cannot read property 'isFile' of undefined
15 0
|
11天前
|
缓存 JavaScript UED
使用 v-once 和 v-memo 进行 Vue 渲染优化
使用 v-once 和 v-memo 进行 Vue 渲染优化