手把手教你搭建 Vue 服务端渲染项目(下)

简介: 手把手教你搭建 Vue 服务端渲染项目(下)

打包文件 vue-ssr-client-manifest.jsonvue-ssr-server-bundle.json

webpack 需要对源码打包两次,一次是为客户端环境打包的,一次是为服务端环境打包的。

为客户端环境打包的文件,和以前我们打包的资源一样,不过多出了一个 vue-ssr-client-manifest.json 文件。服务端环境打包只输出一个 vue-ssr-server-bundle.json 文件。

vue-ssr-client-manifest.json 包含了客户端环境所需的资源名称:

从上图中可以看到有三个关键词:

  1. all,表示这是打包的所有资源。
  2. initial,表示首页加载必须的资源。
  3. async,表示需要异步加载的资源。

vue-ssr-server-bundle.json 文件:

  1. entry, 服务端入口文件。
  2. files,服务端依赖的资源。

填坑记录

1. [vue-router] failed to resolve async component default: referenceerror: window is not defined

由于在一些文件或第三方文件中可能会用到 window 对象,并且 node 中不存在 window 对象,所以会报错。

此时可在 src/app.js 文件加上以下代码进行判断:

// 在 app.js 文件添加上这段代码,对环境进行判断
if (typeof window === 'undefined') {
    global.window = {}
}

2. mini-css-extract-plugin 插件造成 ReferenceError: document is not defined

使用 mini-css-extract-plugin 插件打包的的 server bundle, 会使用到 document。由于 node 环境中不存在 document 对象,所以报错。

解决方案:样式相关的 loader 不要放在 webpack.base.config.js 文件,将其分拆到 webpack.client.config.jswebpack.client.server.js 文件。其中 mini-css-extract-plugin 插件要放在 webpack.client.config.js 文件配置。

base
module: {
    rules: [
        {
            test: /\.vue$/,
            loader: 'vue-loader',
            options: {
                compilerOptions: {
                    preserveWhitespace: false
                }
            }
        },
        {
            test: /\.js$/,
            loader: 'babel-loader',
            exclude: /node_modules/
        },
        {
            test: /\.(png|svg|jpg|gif|ico)$/,
            use: ['file-loader']
        },
        {
            test: /\.(woff|eot|ttf)\??.*$/,
            loader: 'url-loader?name=fonts/[name].[md5:hash:hex:7].[ext]'
        },
    ]
}

client

module: {
    rules: [
        {
            test: /\.css$/,
            use: [
                {
                    loader: MiniCssExtractPlugin.loader,
                    options: {
                        // 解决 export 'default' (imported as 'mod') was not found
                        esModule: false,
                    },
                },
                'css-loader'
            ]
        }
    ]
}

server

module: {
    rules: [
        {
            test: /\.css$/,
            use: [
                'vue-style-loader',
                'css-loader'
            ]
        }
    ]
}

3. 开发环境下跳转页面样式不生效,但生产环境正常。

由于开发环境使用的是 memory-fs 插件,打包文件是放在内存中的。如果此时 dist 文件夹有刚才打包留下的资源,就会使用 dist 文件夹中的资源,而不是内存中的资源。并且开发环境和打包环境生成的资源名称是不一样的,所以就造成了这个 BUG。

解决方法是执行 npm run dev 时,删除 dist 文件夹。所以要在 npm run dev 对应的脚本中加上 rimraf dist

"dev": "rimraf dist && node ./server/dev-server.js --mode development",

4. [vue-router] Failed to resolve async component default: ReferenceError: document is not defined

不要在有可能使用到服务端渲染的页面访问 DOM,如果有这种操作请放在 mounted() 钩子函数里。

如果你引入的数据或者接口有访问 DOM 的操作也会报这种错,在这种情况下可以使用 require()。因为 require() 是运行时加载的,所以可以这样使用:

<script>
// 原来报错的操作,这个接口有 DOM 操作,所以这样使用的时候在服务端会报错。
import { fetchArticles } from '@/api/client'
export default {
  methods: {
    getAppointArticles() {
      fetchArticles({
        tags: this.tags,
        pageSize: this.pageSize,
        pageIndex: this.pageIndex,
      })
      .then(res => {
          this.$store.commit('setArticles', res)
      })
    },
  }
}
</script>

修改后:

<script>
// 先定义一个外部变量,在 mounted() 钩子里赋值
let fetchArticles
export default {
  mounted() {
    // 由于服务端渲染不会有 mounted() 钩子,所以在这里可以保证是在客户端的情况下引入接口
      fetchArticles = require('@/api/client').fetchArticles
  },
  methods: {
    getAppointArticles() {
      fetchArticles({
        tags: this.tags,
        pageSize: this.pageSize,
        pageIndex: this.pageIndex,
      })
      .then(res => {
          this.$store.commit('setArticles', res)
      })
    },
  }
}
</script>

修改后可以正常使用。

5. 开发环境下,开启服务器后无任何反应,也没见控制台输出报错信息。

这个坑其实是有报错信息的,但是没有输出,导致以为没有错误。

setup-dev-server.js 文件中有一行代码 if (stats.errors.length) return,如果有报错就直接返回,不执行后续的操作。导致服务器没任何反应,所以我们可以在这打一个 console.log 语句,打印报错信息。

小结

这个 DEMO 是基于官方 DEMO vue-hackernews-2.0 改造的。不过官方 DEMO 发表于 4 年前,最近修改时间是 2 年前,很多选项参数已经过时了。并且官方 DEMO 需要翻墙才能使用。所以我在此基础上对其进行了改造,改造后的 DEMO 放在 Github 上,它是一个比较完善的 DEMO,可以在此基础上进行二次开发。

如果你不仅仅满足于一个 DEMO,建议看一看我的个人博客项目,它原来是客户端渲染的项目,后来重构为服务端渲染,绝对实战。

参考资料

更多文章,敬请关注

目录
相关文章
|
3天前
|
JavaScript
vue消息订阅与发布
vue消息订阅与发布
|
2天前
|
JavaScript
vue尚品汇商城项目-day07【vue插件-50.(了解)表单校验插件】
vue尚品汇商城项目-day07【vue插件-50.(了解)表单校验插件】
11 4
|
2天前
|
JavaScript
vue尚品汇商城项目-day07【51.路由懒加载】
vue尚品汇商城项目-day07【51.路由懒加载】
12 4
|
4天前
|
JavaScript 前端开发
Vue学习笔记8:解决Vue学习笔记7中用v-for指令渲染列表遇到两个问题
Vue学习笔记8:解决Vue学习笔记7中用v-for指令渲染列表遇到两个问题
|
2天前
|
JavaScript
vue尚品汇商城项目-day07【vue插件-54.(了解)生成二维码插件】
vue尚品汇商城项目-day07【vue插件-54.(了解)生成二维码插件】
8 2
|
5天前
|
JavaScript
vue组件中的插槽
本文介绍了Vue中组件的插槽使用,包括单个插槽和多个具名插槽的定义及在父组件中的使用方法,展示了如何通过插槽将父组件的内容插入到子组件的指定位置。
|
4天前
|
JavaScript 前端开发 IDE
Vue学习笔记5:用Vue的事件监听 实现数据更新的实时视图显示
Vue学习笔记5:用Vue的事件监听 实现数据更新的实时视图显示
|
4天前
|
JavaScript 前端开发 API
Vue学习笔记4:用reactive() 实现数据更新的实时视图显示
Vue学习笔记4:用reactive() 实现数据更新的实时视图显示
|
4天前
|
JavaScript 前端开发 API
Vue学习笔记7:使用v-for指令渲染列表
Vue学习笔记7:使用v-for指令渲染列表
|
4天前
|
JavaScript
vue 函数化组件
vue 函数化组件
下一篇
无影云桌面