Webpack-Vue 分片优化——为什么使用懒加载() => import() 里面的组件没有分片打包
环境 vue2,webpack3公司的vue项目需要做一下前端优化,主要问题是网页首屏加载缓慢。作为单页应用的vue,往往是第一次加载时间会稍长,后续由于利用浏览器缓存,再次打开就快了。且单页应用将页面都打包到一个js(一般是app.js)文件中,在路由跳转的时候不会有闪屏,通过js动态渲染页面元素,无需发起额外的请求获取页面。但是,为什么还要优化呢——vue 遇见了iframe此次我收到的项目是
环境 vue2,webpack4
公司的vue项目需要做一下前端优化,主要问题是网页首屏加载缓慢。
作为单页应用的vue,往往是第一次加载时间会稍长,后续由于利用浏览器缓存,再次打开就快了。且单页应用将页面都打包到一个js(一般是app.js)文件中,在路由跳转的时候不会有闪屏,通过js动态渲染页面元素,无需发起额外的请求获取页面。
但是,为什么还要优化呢——
vue 遇见了iframe
此次我收到的项目是作为其他项目的内嵌形式来加载的,没错,就是iframe。这个项目通过js来控制iframe,来达到类似浏览器的Tab标签页管理的效果。每次打开页面就生成一个iframe,去加载指定的url(我现在的vue项目的页面)。这样就发挥不了vue路由跳转的好处了,每次都需要加载app.js,虽然已经有缓存了,下载时间可以省略,但是仅运行这个app.js可能都需要1秒左右的时间,这个时间包括浏览器对js文件的读取,预编译,执行等balabala。
所以,如何减小app.js的文件大小,提升首屏加载速度就显得尤为重要了。
webpack有切片的功能,主要体现在提取公共依赖(css,js等静态文件),和组件懒加载
懒加载在vue中意思为,打开页面或变换路由的时候,由浏览器发起请求获取需要用到的文件,路由每次变化都会请求对应.vue文件打包后的.js文件。简单来说就是“按需加载”
vue的懒加载在网上许多地方都有说明,需要使用es6 的import()方法来代替原先的require或者import xx from xx的写法。
import User from '@/view/user.vue'
//或者
const User = require('@/view/user.vue')
new VueRouter({
routes: [
{ path: '/user', component: User }
]
})
//----------------
//变成下面这样的写法
//----------------
new VueRouter({
routes: [
{ path: '/user', component: () => import('@/view/user.vue') }
]
})
// 这样访问/user路由获取app.js后,浏览器还会再请求user.js(webpack chunkFilename)
有的文章还提到babel的syntax-dynamic-import,才能使用import()语法。
首先要知道babel常常用于ES6或更高版本的ES特性转换为es5或更低版本的代码,以应对一些特殊环境和浏览器的需要。
而现在es2020已经支持import()方法了,我并未引入syntax-dynamic-import,也可以正常运行import()函数。
现在问题来了,我使用() => import()写法,webpack并未将每个.vue文件单独打包成一个文件。
作为小白经几日研究,发现是代码中原先的的require()造成的
因为我们这个项目比较特殊,有多个路由配置文件,举例如下三个同目录文件
/* router/router1.js */
exports default [
{ path: '/r1', component: 'r1' }
]
/* router/router2.js */
exports default [
{ path: '/r2', component: 'r2' }
]
/* router/index.js */
let pathArr = ['router/router1.js','router/router2.js'];
let routersArr = [];
pathArr.forEach( path => { // 多个文件中获取路由数组
// 坑
let router = require(`@/router/${path}.js`).default;// 因为文件使用es6的 export default
routersArr = routersArr.concat(router);
});
routersArr.forEach( item => { // 添加component前后缀
item.component = require('@/view/${item.component}.vue').default;
});
new VueRouter({
routes: routesArr
});
这个是通过动态载入的路由,也就是程序运行了才知道require的是什么文件。
经过多方了解和学习后,终于知道了为什么不能分片的原因。
我们都知道webpack配置中需要指定一个entry的入口文件(大多是main.js),那么这个入口文件中大体的结构一般是这样写的
/* main.js */
import Vue from 'vue';
import App from './app.vue';
import routes from './routes/index.js'; //如果文件名为index的话可以省略,如'./routes'
//还可以引入 css框架,i18n,其他组件等,省略
//.....
new Vue({
routes,
render: h => h(App)
}).$mount('#app');
webpack进行的是预编译工作,是不执行js文件的。(不信把入口文件的import xxx from xxx 删除,还是能够打包成功)
- 当webpack 在main.js文件中发现import x from x语法的时候,如import routes from './routes'。
- 进入routes/index.js文件中分析导入语法,当碰到require()语法的时候。
- 重点来了,如果在入口或入口依赖的任何文件中,有使用例如require('@/view/' + 变量 + '.js')这么来导入的话,那么,webpack将会匹配'@/view/'路径下面所有的.js文件进行打包成一个文件。因为webpack是不执行js文件的,所以并不知道实际执行的时候require的参数是什么,会忽略变量进行匹配。同理也不建议require()传入其他函数。
- routes/index.js等文件的依赖收集完毕后,打包到main.js文件中。
- 若导入其他的文件被识别有相同的依赖,则webpack不会重复打包。(具体还未深入了解)
这就是为什么() => import()语法没有被webpack打包成单一的vue文件的原因。
require()将符合条件的文件打包到一个文件中,() => import()会将符合条件的文件单独打包。
而且我发现 require() 语法和import()语法同时存在时,在webpack3和4中 都是优先处理 require()语法的。表现为已经根据上面第3步收集的依赖,import()不会重复收集,所以就没有将.vue文件单独打包成为.js文件了。
解决办法就是不要滥用require(),参数尽量写详细!在webpack根据require()来匹配收集依赖后,import()匹配的依赖没有被收集,那么import()中的依赖就会被单独打包出来。
我的解决办法如下:
/* router/index.js */
let router1 = require('router/router1.js').default;
let router2 = require('router/router2.js').default;
let routersArr = [...router1, ...router2];
routersArr.forEach( item => { // 添加component前后缀
// 现在使用import就可以了
item.component = () => import('@/view/${item.component}.vue'); // webpack将会把view/目录下的.vue文件单独打包出来
});
new VueRouter({
routes: routesArr
});
相信一定有更灵活的解决办法。
有人说这么做了怎么还是没有单独打包文件出来呢。
- 我认为还是仔细检查一下入口文件(main.js)的import from依赖,可能其他依赖中也在require()参数中传入了其他参数,导致webpack匹配了变参外的条件。
- 确保import()中的依赖,没有先被require()匹配收集。
补充chunkFilename:
关于webpack配置中output的chunkFilename 问题,使用'[name].js'来指定文件名称,输出仍然是id.js的问题
已知两种解决办法
// 第三个参数指定chunkFilename文件名称,不要动态参数----参考资料2,3
require.ensure([], (require) => require(filePath), 'fileName');
// webpack magic comment 使用webpack注释指定chunkFilename文件名称----参考资料3,4
// fileName相同的文件会打包到一起
() => import(/*webpackChunkName: "fileName"*/ filePath);
//若import使用动态参数 ----参考资料3,4
() => import(/*webpackChunkName: "[request]"*/ filePath);
结论:
入口文件或入口文件依赖中,使用require(变量)方法导入,导致与import()的组件冲突
参考资料
- es6 import()函数
- 代码分割 - 使用 require.ensure
- webpack中动态import()打包后的文件名称定义
- Webpack Magic Comments(注释指定import名称)
若有错误,敬请指出更正
更多推荐
所有评论(0)