Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Webpack代码分割 #7

Open
Adamwu1992 opened this issue Dec 27, 2017 · 1 comment
Open

Webpack代码分割 #7

Adamwu1992 opened this issue Dec 27, 2017 · 1 comment
Labels
开发工具 关于工作流中涉及的工具的用法或者原理

Comments

@Adamwu1992
Copy link
Owner

Adamwu1992 commented Dec 27, 2017

单页面应用打包

代码仓库

entry入口分割

  • 使用entry分离入口模块
entry: {
        index: ['./src/index.js'],
        chunk: ['./src/utils/Array.js', './src/utils/String.js']
},

以上的配置会让webpack从两个入口打包,最终会生成两个文件,index.js里会包含index.js的代码以及所有依赖模块的代码,chunk.js里会包含Array.jsString.js的代码以及它们依赖的代码,同时以上两个模块里还会包含webpack的运行时代码。

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

  • 使用CommonChunlPlugin提取公共代码
entry: {
        index: ['./src/index.js'],
        chunk: ['./src/utils/Array.js', './src/utils/String.js']
    },
...
plugins: [
        new webpack.optimize.CommonsChunkPlugin({
            name: 'chunk'
        }),
    ]

这种配置下打出来的chunk.js和第一种配置时一样,但是会把index.jschunk.js里的共用代码都从index.js里抽离出来,webpack运行时代码也会抽离出来,index.js的体积会减小很多。

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

  • 抽离webpack运行时代码
...
plugins: [
        new webpack.optimize.CommonsChunkPlugin({
            name: 'chunk'
        }),
        new webpack.optimize.CommonsChunkPlugin({
            name: 'manifest',
            chunks: ['chunk']
        }),
        new BundleAnalyzerPlugin({
            analyzerMode: 'static'
        })
    ]

增加一个插件,chunks参数指定从chunk.js模块里提取代码,然后生成一个manifest.js模块,原本在chunk.js里的运行时代码会被提取出来,这样每次改动业务代码(index.js)的时候,只有index.jsmanifest.js代码hash会发生变动,达到了优化的目的。

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

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

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

@Adamwu1992
Copy link
Owner Author

Adamwu1992 commented Dec 28, 2017

options配置参数

参考文档:

名词解释:

  • chunk: 插件在处理代码时操作的单位,任何一段代码可以视为一个chunk
  • entry chunk: 在options.entry里指定的文件里的代码被称作entry chunk,如果是一个数据,多个文件的代码被合并成一个entry chunk
  • children chunk: 从入口文件开始,webpack会根据split point(比如异步引入等)分割代码,被分割出来的代码块称为children chunk
  • source chunk: CommonsChunkPlugin将要作用的代码块,通常由chunks或者children指定;
  • commons chunk: CommonsChunkPlugin source chunk里根据规则提取出来的公共代码块,需要注意的是,一个chunk并不等于最后打包出来的一个文件,通常多个chunk会被合并成一个文件减少下载请求;

children & async & deepChildren

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

先上结论:

  • childrenname属性需要设置为一个已经存在的entry chunksource chunk中提取出来的commons chunk都会被合并到指定的entry chunk中。
  • async: name属性需要设置为一个已经存在的entry chunk,从source chunk中的children chunk里提取出的commons chunk被放进一个独立的异步模块。
  • deepChildren: name属性需要设置为一个新的名字,从source chunk中的entry chunk里提取出来的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里的代码提取成一个异步模块,总算把所有的代码都分开了

@Adamwu1992 Adamwu1992 added the 开发工具 关于工作流中涉及的工具的用法或者原理 label Feb 2, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
开发工具 关于工作流中涉及的工具的用法或者原理
Projects
None yet
Development

No branches or pull requests

1 participant