事情的起因是这样的,最近有相当一部分的精力都在做项目的性能优化上,之前有一个项目出现了一个老大难的问题纠结很久了,一直没时间去看,正好一并解决一下。这个问题很简单:我用vue-cli创建的项目,按照vue的路由懒加载写法,打包后却发现代码并没有分割,全部都打包到app.js中了,导致app.js体积过大,且没有路由的按需加载了。
找出问题的原因
我开始思考问题原因可能是以下几点造成的:
- 路由懒加载写法不对;
- vue-cli版本问题;
- vue-cli的配置问题。
但是这三个可能得原因很快排除了,因为有一个项目上面三个都一样,代码分割正常,那只能是代码问题了。但是那么多文件总不能全部review一遍吧,毫无头绪之下只能采用朴素但实用二分法的方式定位问题文件了。一番体力活下来终于让我找到了两个罪魁祸首,通过观察这两个文件发现都用了同一种的文件引用方式,类似代码如下:
let form = null; let cpnName = this.template.name; this.$options.components[cpnName] = require('@/' + this.template.path).default; form = <cpnName /> return ( <div>{form}</div> )
组件通过拼接入参的路径来动态引入组件,其实看到这里我心里大概就知道什么原因了,因为是动态路径,webpack打包时是静态解析依赖,根本无法确认文件的具体地址,所以导致代码全部都打到app.js中。为了证明我的想法,我到webpack的github issue中也找到了跟我类似的场景:
这个老哥是想根据传入的图片名称来动态引入图片,但是打包时候发现其他目录的图片也都被打包进来了,webpack的维护者也回答了说,这就是require的工作机制,它不知道你会用哪个资源,它就把它们全部都打包了。
验证问题
为了验证这个问题,我创建了一个项目,来复现一下问题:
动态引入的组件代码如下:
// src/components/common/DynamicRequireCpn.vue <script> export default { name: 'DynamicRequireCpn', props: { template: Object }, render () { let form = null; let cpnName = this.template.name; this.$options.components[cpnName] = require('@/' + this.template.path).default; form = <cpnName /> return ( <div>{form}</div> ) } } </script>
路由代码如下:
// src/router/index.js const routes = [ { path: '/', name: 'home', component: HomeView }, { path: '/about', name: 'about', component: () => import(/* webpackChunkName: "about" */ '../views/AboutView.vue') } ];
打包结果如下:
发现代码还是都打包到一起了,about组件并没有分割出来。而且我还在app.js中发现了没有引用的代码。也就是说这种情况下,webpack把src目录下所有的文件都打包了。
解决问题的方案
按照上面的实验和require的工作原理,我想通过缩小require的查到范围是不是能解决问题呢?
<script> export default { name: 'DynamicRequireCpn', props: { template: Object }, render () { let form = null; let cpnName = this.template.name; this.$options.components[cpnName] = require('@/components/common/' + this.template.path).default; form = <cpnName /> return ( <div>{form}</div> ) } } </script>
这下我把require的动态路径精确到@/components/common/
,重新打包看看:
Bingo!看到了about组件对应的分割文件,而且搜索app.js文件,也没有发现未引用的代码了,问题解决了!
总结
在使用webpack时,应该尽量减少资源的动态路径引入,如果必须这样引入的话,那也要尽量传入更短的文件路径,或者将要动态引入的文件放到一个目录下面,防止webpack找到非目标目录下面。
GOOD
require('@/components/common/' + this.template.path);
BAD
require('@' + this.template.path);