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

间隙填充
正睿科技  发布时间:2024-08-24 17:34:36  浏览数:652
关于正睿.png

打包文件 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 包含了客户端环境所需的资源名称:

dxrdxmxsuvtcc_978061426f884716ac80c1d6433c1ad4.png

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

  1. all,表示这是打包的所有资源。

  2. initial,表示首页加载必须的资源。

  3. async,表示需要异步加载的资源。

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

dxrdxmxsuvtcc_8726cdf05ffa457bb9394a51417b5876.png

  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 语句,打印报错信息。

dxrdxmxsuvtcc_d2c4426944e14b80a41860881538e569.png

小结

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

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

参考资料

更多文章,敬请关注

疑问没解决? 我们帮您!

如果您在本文中未能找到解决当前疑问的办法,不用担心——正睿专业技术支持团队随时待命

服务项目.png

获取更多帮助

文章来源:阿里云开发者社区 作者:谭光志