【javascript】webpack code split && tree shaking

Dear 丶 2022-05-27 09:11 294阅读 0赞

webpack打包vue项目之后的文件太大,本身我们项目的体量也比较大,首次加载太慢。

所以尝试代码分割,对打包之后的app.js进行拆分。

1,动态加载 -(路由懒加载) -(按需加载)

  1. 现在vue项目里面,有很多路由,一个路由对应着或者多个路由对应着一个组件,如果不进行代码拆分,所有的这些组件在打包的时候都会被打包在app.\[hash\].js里面,但是很多组件在首次访问的时候是不需要的。
  2. 我们可以将不同路由对应的组件,打包成不同的文件,在访问某一个特定路由的时候,才去加载这个路由所对应组件的js。访问项目首页的时候,我们只需要加载index page所需的组件即可。
  3. 比如我们之前的路由加载首页组件是这样的:

import Vue from ‘vue’

import Router from ‘vue-router’

import PlainComponent from ‘@/components/PlainComponent’

import Index from ‘@/components/Index’

Vue. use( Router)

export default new Router({

routes: [

{

path: ‘/‘,

name: ‘index’,

component: Index

},

{

path: ‘/plain-component’,

name: ‘PlainComponent’,

component: PlainComponent

}

]

})

  1. 然后我们加入其他的路由,在routes节点增加新的item,比如有新的需求,组件名为PlainComponent,是访问路由/plain去访问然后加载的。在webpack.prod.config.js里面,将 UglifyJsPlugin暂时先注释,关闭sourceMap,跑npm run build后,查看打包文件,你会发现 PlainComponent的内容被打包到了app.js里面。这不是我们需要的,我们首页访问的时候 不需要加载这个组件。
  2. 这时候我们可以将这个组件设置为[动态导入][Link 3]:
  3. 我们动态引导入此组件,webpack会自动将该组件打包成一个chunk文件,可以手动命名,命名方式长这样:

/*webpackChunkName: “your-chunk-name”*/

component : () => import( /* webpackChunkName: “DynamicComponent” */ ‘../components/DynamicComponent’)

为了方便对比,我把普通组件,和动态导入组件的路由都留到了项目的路由文件中, 现在路由文件长这样:

export default new Router({

routes: [

{

path: ‘/‘,

name: ‘index’,

component: Index

},

{

path: ‘/plain-component’,

name: ‘PlainComponent’,

component: PlainComponent

},

{

path: ‘/dynamic-component’,

name: ‘DynamicComponent’,

component : () => import( /* webpackChunkName: “DynamicComponent” */ ‘../components/DynamicComponent’)

}

]

})

tip-1: 到这一步,我们已经拆分了app.js,但是你可能会有疑问,如果路由里面多个路由对应一个组件,比如/dynamic-component-2 也会对应使用组件DynamicComponent组件【实际业务也会遇到】,这时候我们仍然可以使用动态导入,无论是否使用同样的webpackChunkName。这个组件只会被打包一次,最后只会生成一个chunk文件,如果name不一样以第一次命名为主。

tip-2: 随着动态导入的组件越多,各个组件里面的引入的其他模块可能会被重复打包,比如说上面除了第一个动态导入的DynamicComponent组件,还有另外的动态导入的组件DynamicComponentTwo 和 DynamicComponentThree,都使用到了lodash的omit方法。打包之后,查看对应的三个js文件,你会发现omit被导入了三次,这样也就重复了三次。所以我们需要将这三个chunk的公用部分给提取出来,这时候需要用到 CommonsChunkPlugin。【代码请查看后面的demo连接】

  1. 现在我们的路由文件长这样:

export default new Router({

routes: [

{

path: ‘/‘,

name: ‘index’,

component: Index

// component: require(‘../components/Index’).default

},

{

path: ‘/plain-component’,

name: ‘PlainComponent’,

component: PlainComponent

},

{

path: ‘/dynamic-component’,

name: ‘DynamicComponent’,

component : () => import(

/* webpackChunkName: “DynamicComponent” */

‘../components/DynamicComponent’)

},

{

path: ‘/dynamic-component-two’,

name: ‘DynamicComponentTwo’,

component : () => import(

/* webpackChunkName: “DynamicComponentTwo” */

‘../components/DynamicComponentTwo’

)

},

{

path: ‘/dynamic-component-three’,

name: ‘DynamicComponentThree’,

component : () => import(

/* webpackChunkName: “DynamicComponentThree” */

‘../components/DynamicComponentThree’

)

}

]

})

2, CommonsChunkPlugin

  1. commonsChunksPlugin可以帮助我们,抽取多个chunk的公共部分,正如上面我们的例子,三个动态导入的chunk,都用到了lodashomit方法,我们需要抽取出来。
  2. 这时候我们在webpack.prod.config.js文件里面plugins添加一个item

new webpack. optimize. CommonsChunkPlugin({

name: ‘app’,

async: “vendor-async”,

minChunks: 3

})

我们先明白这个CommonsChunkPlugin的参数是什么意思。

  1. name首先分为两种,第一种是属性值是已知的chunk块的名称,这里是app,说明我们要对app的子模块进行公共代码的抽取。【如果使用了新的name,则表示生成新的chunk文件,在本例的源代码中尝试会有问题,因为三个子模块都是动态导入的,若是三个非动态引入name + chunks(所有非动态引入组件)+ minChunks是可以执行的
  2. async是抽取异步chunk的意思,【和filename是冲突的,设置filename则会生成以filename属性命名的chunk,但是是同步加载,当加载首页的时候,会同时加载以filename命名的js文件】,异步的意思是加载首页,我们不需要加载这一块公共代码,只有某些特定的模块使用了这个chunk的数据时,加载这个特定的模块才会去加载这个异步chunk

minChunks这里设置为3,表示至少3个模块以上出现的同样的代码,才会将代码提取出来生成common chunk。

我们没有抽取异步公共组件之前,三个动态导入的组件打包后是这样的:

70

然后我们跑构建命令,然后查看会多出来一个 vendor-async.xxx.js的文件,这个文件里面就是三个动态引入的组件公共的代码,也就是lodash部分,然后再看三个文件的大小,由70多k变成了1k以下:

70 1

3, tree shaking

到这一步就完成了吗?完成了大部分的压缩拆分文件的效果了,但是打开vendor-async.xxx.js文件,你会发现,lodash的所有方法都被加载进了这个文件 ,并且在访问动态路由的时候,我们直接在chrome 的console里面直接输入_.isNil,或者其他lodash方法都会被打印出来,uglifyJS没有移除那些没有使用的方法,明明我们项目使用的只有一个omit方法,也就是说理想的情况是vendor-async 这个chunk不应该那么大,所以我们应该考虑tree shaking 【移除没有使用的代码】。 因为lodash没有使用ES6的模块化语法,tree shaking的前提是webpack处理的代码,如果采用了ES6模块化语法,uglifyJS会自动帮我们去除的。

所以去掉lodash中没有的代码,需要用到额外的插件:babel-plugin-lodash

加入之后看vendor-async.xxx.js的大小

70 2

-—————————-

上文提到的两个例子demo:

  1. 1 [webpack-code-splitting][]
  2. 2, [ tree shaking][tree shaking]

第二个例子是基于第一个来做的,你可以查看打包之后的vendor-async.xxx.js的大小对比,然后各自启一个web server 查看在加载动态组件的时候,console控制台里面关于 _ 的差异

发表评论

表情:
评论列表 (有 0 条评论,294人围观)

还没有评论,来说两句吧...

相关阅读

    相关 tree shaking 及其工作原理

    写在前面 今天这道题目是在和小红书的一位面试官聊的时候: 我:如果要你选择一道题目来考察面试者,你最有可能选择哪一道? 面试官:那应该就是介绍一下`tree shak