not a better man

前端技术

vue代码分割实践记录

vue代码分割的形象示意图

vue代码分割示意图

背景

在上一篇文章,介绍了利用webpack怎么建立长期有效的缓存机制,这篇文章将在此基础上,对vue代码进行分割的操作,这个是我们使用vue架构大型项目的时候,需要考虑的点。

一般使用vue开发项目的时候,我们把vue,vue-router,vuex,axios基础库代码打包成一个vendor.js文件,把业务代码打包成一个main.js类似名字的代码。库代码基本在较长时间内会一直保存不变,这样我们能够对这类代码进行长期缓存,减少带宽压力,减少http请求次数,最多增加304请求问题。

但是,随着项目越来越大,业务代码随之变大,如我在vipkid 参与的学习中心的业务代码已达到1.2MB,虽然服务器对代码进行了gzip压缩,在网络上的传输的体积也有将近300KB,当用户的网络环境比较差的时候,首屏加载变慢,有些情况下进入界面需要4s-5s 这对于用户来说,是不可忍受。在此情况下,我们需要降低首屏加载时间,希望能控制的1s-1.5s之内。

解决方案

对于上述问题,需要解决的问题是降低首屏加载时间,其实根本就是减少首屏代码体积。目前有两种解决方案,一种是服务端渲染  ,另一种是利用vue的异步组件,和webpack的代码分割功能结合,进行代码的懒加载。两种的侧重点不同,但是都能够减少首屏加载时间。

第一种采用服务端渲染,也能进行代码分割,主要针对是,1.解决搜索引擎的SEO问题2.首屏减少了了API请求时间,能够使用户更快的看到想要看得内容。

第二种解决问题的核心就是异步加载代码。

在webpack 我们需要对chunk type的概览进行了解,上一篇文章,我提到了chunk type的概念,并详细进了说明和解读。在这篇文章我不进行说明了。webpack一共有三种chunk ,分别是Entry Chunk,Normal Chunk,Intial Chunk 。我们解决的问题就是要把首页用不到业务代码,也就是首页不需要的组件,在首页不加载,只有需要它的时候,才请求加载出来。 这样一看,思路估计就出来了,那什么时候,我们需要其他组件的代码,其实就是当我从A路由,转B路由的时候,可能会对应不同的组件,那么在切换路由的,会触发对应的事件,这个时候,建立一个script标签,请求需要的组件代码,然后执行该代码,(类似jsonp)原理大致就是这样。

Vue代码分割

vue代码分割,我们一般以路由为界限进行分割,我们结合Vue的异步组件和Webpack的代码分割功能。在webpack中(webpack2+支持)使用动态import语法来定义代码分割点。

// 这种写法会按照moudle的编号进行对分割的js命名,如0.js,或1.js
const Foo = () => import('./Test.vue');

代码demo地址为https://gitee.com/lmz2423/code-split  

webpack配置如下

const path = require('path');
const webpack = require('webpack');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
entry:{
main:'./src/main.js',
vendor:[
'vue',
'vue-router'
]
},
module:{
loaders:[
{
test: /\.vue$/,
loader:'vue-loader',
options:{
extractCSS:false,
esModule:false
}
},{
test:/\.js$/,
loader:'babel-loader',
exclude:/node_modules/
},{
test:/\.css$/,
loader:'css-loader'
}]
},
plugins:[
newHtmlWebpackPlugin({
template:'./src/index.html',
filename:'index.html'
}),
newwebpack.HashedModuleIdsPlugin(),
newwebpack.optimize.CommonsChunkPlugin({
name:'vendor',
}),
newwebpack.optimize.CommonsChunkPlugin({
name:'manifest',
chunks:['vendor']
})
],
output:{
path:path.resolve(__dirname,'dist'),
filename:'[name]-[chunkhash].js',
chunkFilename:'[name]-[chunkhash].bundle.js',
publicPath:'/'
}
}

当我们没有进行代码分割之前

路由配置如下:

import a from '../componets/a.vue';
import b from '../componets/b.vue';
import c from '../componets/c.vue';
export default[
{
     path:'/',
     component:a
},
{
    path:'/b',
    component:b
},
{
   path:'/c',
   component:c
}
]

首页的视图如下:

webpack打包的文件如下

这个时候我们没有对业务代码main进行了拆分,而是打包一个文件,现在我打算把代码按照路由组件进行分割。

//import a from '../componets/a.vue';
const a = ()=> import('../componets/c.vue');
//import b from '../componets/b.vue';
const b = ()=> import('../componets/b.vue');
//import c from '../componets/c.vue';
const c = ()=> import('../componets/c.vue');
export default[
{
 path:'/',
 component:a
},
{
 path:'/b',
 component:b
},
{
 path:'/c',
 component:c
}]

打包文件如下

当我们点击按钮b时和按钮c时,对应的js才开始加载,然后缓存起来

我们看一下<head>添加的东西

其实就是类似jsonp的形式添加了回调函数。但是我们分割的代码是[id]-[chunkname].js的形式,如果我们想自己命名呢。可以利用chunkname来进行

import a from '../componets/a.vue';
//import b from '../componets/b.vue';
// const b = ()=> import('../componets/b.vue');
const b = ()=> import(/*webpackChunkName:'b' */'../componets/b.vue');
//import c from '../componets/c.vue';
// const c = ()=> import('../componets/c.vue');
const c = ()=> import(/*webpackChunkName:'c'*/'../componets/c.vue');
export default[
{
path:'/',
component:a
},
{
path:'/b',
component:b
},
{
path:'/c',
component:c
}]

打包之后

上述就是以路由为分割点的懒加载。详细代码见https://gitee.com/lmz2423/code-split 

参考文档

https://router.vuejs.org/zh-cn/advanced/lazy-loading.html

https://medium.com/js-dojo/3-code-splitting-patterns-for-vuejs-and-webpack-b8fff1ea0ba4?source=linkShare-9edcb424d359-1514366213 

发表评论