Adamwu1992 / adamwu1992.github.io

My Blog
2 stars 0 forks source link

Webpack代码分割 #7

Open Adamwu1992 opened 6 years ago

Adamwu1992 commented 6 years ago

单页面应用打包

代码仓库

entry入口分割

这样的配置没有什么卵用,因为如果index.jsArray.js都引用了一个A.js的模块,那么这个模块会分别被打进两个模块里,并不会达到提取公共模块的目的,此时需要CommonChunkPlugin插件了。

这样做的目的是给chunk.js设置一个较长时间的缓存提升性能,因为chunk.js按照设想都是第三方库,每次打包hash都不会发生变化。但是现实是,改动了index.js里的内容,chun k.js也会发生改变。原因是webpack的运行时代码包含么个模块的引用id,模块发生变化会导致运行时代码变化,而我们的运行时代码是包含在chunk.js里的。

new webpack.optimize.CommonsChunkPlugin({
            names: ['chunk', 'manifest']
        }),

我测试了用这种配置也可以达到目的,我猜测大概的是因为CommonChunkPlugin会把webpack的运行时代码打包到最后一个模块里,区别就是:配置1在生成manifest.js模块时只会去提取chunk.js模块,配置2下,插件会运行两遍,只不过第二遍没有公共代码可以提取里,所以manifest.js里只包含运行时代码。

chunks缺省时插件会提取所有的entry chunk,所以生成manifest.js时指定模块效率更佳。

Adamwu1992 commented 6 years ago

options配置参数

参考文档:

名词解释:

children & async & deepChildren

我一开始被这三个参数困扰了好久,尤其是文档中描述的children of the commons chunkdescendants of the commons chunk到底有何差异,一直没能找到例子体现。

先上结论:

首先,下面是源码的结构:

+ utils
  - babel-polypill.js
  - sillyname.js
+ src
  - main.js
  - cat.js
  - dog.js

模块的依赖关系是:

main.js (main entry)
  + babel-polyfill
  + src/cat (dynamic)
    + sillyname
  + src/dog (dynamic)
    + sillyname

babel-polyfill被入口文件直接引用,而catdog是以异步加载的方式引用。首先使用以下的配置打包:

entry: {
        main: ['./apps/src/main.js']
    },
    plugins: [
        new webpack.optimize.CommonsChunkPlugin({
            name: 'vendor',
            minChunks: ({ resource }) => ( 
                resource !== undefined && 
                    resource.indexOf('utils') !== -1 
            )
        }),
        new webpack.optimize.CommonsChunkPlugin({
            name: 'manifest',
            minChunks: Infinity
        }),
        new BundleAnalyzerPlugin({
            analyzerMode: 'static'
        })
    ]

使用minChunks函数从entry chunk里提取公共模块,由于只有一个main是入口模块,catdog会被webpackcode split分离成单独的模块,所以只有main会被提取,所以打包出来的依赖关系应该如下:

vendor
  + babel-polyfill
main
  + src
1
  + src/cat
  + sillyname
0
  + src/dog
  + sillyname
manifest

修改配置,使用children属性看看有什么变化:

...
new webpack.optimize.CommonsChunkPlugin({
            name: 'main',
            children: true,
            minChunks: ({ resource }) => ( 
                resource !== undefined && 
                    resource.indexOf('utils') !== -1 
            )
        }),

If true all children of the commons chunk are selected. ----webpack

the word "commons chunk" should probably be replaced by "entry chunk". ----stack overflow

这个配置下,webpack会从entry chunkchildren中提取,并且将生成的commons chunk合并到自身,通常使用chidlren属性是,name都会被指定为一个已经存在的entry chunk,我尝试做了一些不通常的操作,我改变name的值为一个新的模块名,但是插件忽略了这个名字,仍然将提取出来的common chunk合并到对应的entry chunk中,此处的children可以认为是通过code split分割出来的额模块,所以sillyname也会被提取,打包出来的依赖关系如下:

main
  + src
  + babel-polyfill
  + sillyname
1
  + src/cat
0
  + src/dog
manifest

如果将上面的children换成deepChildren,会有什么变化呢?打包依赖关系如下:

main
  + src
  + babel-polyfill
1
  + src/cat
  + sillyname
0
  + src/dog
  + sillyname
manifest

这简直是没有任何提取嘛,一定是打开方式不对。 改变name,让插件生成一个新的commons chunk:

new webpack.optimize.CommonsChunkPlugin({
            name: 'vendor',
            deepChildren: true,
            minChunks: ({ resource }) => ( 
                resource !== undefined && 
                    resource.indexOf('utils') !== -1 
            )
        }),

生成的依赖如下:

main
  + src
vendor
  + babel-polyfill
1
  + src/cat
  + sillyname
0
  + src/dog
  + sillyname
manifest

sillyname不会被提取,所以deepChildren指定的chunk里不包括entry chunk分割出来的children chunk,此时得到第一个结论,childrendeepChildren的区别是,前者的source chunk为entry chunkchildren chunk,后者为entry chunk,而且设置children情况下,那么需要为一个已经存在的entry chunk,设置deepChildren时则需要生成一个新的commons chunk。

回到上一个场景,设置children为true时,插件会把entry chunk和children chunk中提取出的commons chunk都合并到name(此时name指定为一个已经存在的entry chunk)指定的entry chunk中,会导致一个模块的体积过大,且有不必要的下载浪费。 我们设置async,生成的依赖关系如下:

main
  + src
  + babel-polyfill
0
  + sillyname
2
  + src/cat
1
  + src/dog
manifest

从结果来看,插件提取了children chunk,并且生成了一个新的异步模块,而entry chunk中的提取出的commons则被合并到entry chunk中,就像指定了children: true时的打包方式一样。我经过测试,如果删除children配置结果并不会改变。

测试了这么多,发现要么是children chunk里的公共代码不会被提取到新文件,要么是entry chunk里的代码不会被提取到新文件,达不到我们对前端极致的性能追求啊。尝试组合一下配置:

plugins: [
        new webpack.optimize.CommonsChunkPlugin({
            name: 'vendor',
            minChunks: ({ resource }) => ( 
                resource !== undefined && 
                    resource.indexOf('utils') !== -1 
            )
        }),
        new webpack.optimize.CommonsChunkPlugin({
            name: 'main',
            children: true,
            async: true,
            minChunks: ({ resource }) => ( 
                resource !== undefined && 
                    resource.indexOf('utils') !== -1 
            )
        }),
        new webpack.optimize.CommonsChunkPlugin({
            name: 'manifest',
            minChunks: Infinity
        }),
        new BundleAnalyzerPlugin({
            analyzerMode: 'static'

打包后的依赖关系:

vendor
  + babel-polyfill
main
  + src
0
  + sillyname
2
  + src/cat
1
  + src/dog
manifest

第一个插件将entry chunk里的代码提取到vender里,第二个插件将children chunk里的代码提取成一个异步模块,总算把所有的代码都分开了