事情的起因是这样的,最近有相当一部分的精力都在做项目的性能优化上,之前有一个项目出现了一个老大难的问题纠结很久了,一直没时间去看,正好一并解决一下。这个问题很简单:我用vue-cli创建的项目,按照vue的路由懒加载写法,打包后却发现代码并没有分割,全部都打包到app.js中了,导致app.js体积过大,且没有路由的按需加载了。

找出问题的原因

我开始思考问题原因可能是以下几点造成的:

  1. 路由懒加载写法不对;
  2. vue-cli版本问题;
  3. 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);


不死小强
2.2k 声望587 粉丝

前端开发