Better

Ethan的博客,欢迎访问交流

了解一哈webpack 4.x

在系统学习完 webpack 3.x系列后,大家都知道 webpack 4.x 已经 release 了有一段时间了,那么他做了哪些升级呢?于是查找资料了解了一下,为日后迁移做准备!

特性

webpack 4发布后,议论最多的两大特性,其一是零配置,其二是速度快

注意:webpack4中webpack cli从webpack中分离,因此还需要单独安装:npm install webpack webpack-cli webpack-dev-server --save-dev

零配置

一言以蔽之,约定优于配置。通过mode属性将开发/生产(development/production)环境中常用的功能设置好默认值,用户即来即用。mode 不可缺省,需要二选一。在没有写配置文件的情况下,也可以直接打包。

其实还有第三种方式,none值,但我想一般不会使用他吧

production 模式

  • 默认提供所有可能的优化,如代码压缩/作用域提升等
  • 不支持 watching
  • process.env.NODE_ENV 的值不需要再定义,默认是 production
// webpack 2/3 
module.exports = {
    plugins: [
            new UglifyJsPlugin(/* ... */),
            new webpack.DefinePlugin({ "process.env.NODE_ENV": JSON.stringify("production") }),
            new webpack.optimize.ModuleConcatenationPlugin(),
            new webpack.NoEmitOnErrorsPlugin()
        ]
    }

// webpack 4  
module.exports = {
    mode: 'production'
}

development 模式:

  • 主要优化了增量构建速度和开发体验
  • process.env.NODE_ENV 的值不需要再定义,默认是 development
  • 开发模式下支持注释和提示,并且支持 eval 下的 source maps
module.exports = {
        plugins: [
            new webpack.NamedModulesPlugin(),
            new webpack.DefinePlugin({ "process.env.NODE_ENV": JSON.stringify("development") })
        ]
    }

// webpack 4  
module.exports = {
    mode: 'development'
}

打包速度快

Webpack 4取消了四个常用的用于性能优化的plugin(UglifyjsWebpackPlugin,CommonsChunkPlugin,ModuleConcatenationPlugin,NoEmitOnErrorsPlugin),转而提供了一个名为optimization的配置项,用于接手以上四位的工作。

v2-ee213b1ff1ceead751e2ea1cb27bb060_r.jpg

理论分析

Tree shaking

废弃插件:UglifyjsWebpackPlugin

新增属性:sideEffects、minimize、minimizer等

Tree shaking:影响tree shaking的根本原因在于side effects(副作用),其中最广为人知的一条side effect就是动态引入依赖的问题。

得益于ES6的模块化实现思路,所有的依赖必须位于文件顶部,Webpack可以在绘制依赖图的时候进行静态分析,从而将真正被引用的exports添加到bundle文件中,减少打包体积。

然而很多热度较高的第三方库为了考虑兼容性往往采用UMD实现,而其所支持的动态引入依赖的功能则导致真实的依赖图可能要到运行时才能确定,使得静态分析难以发挥真正威力,tree shaking采用了保守策略,导致我们发现没有被用到的方法依然出现在了bundle文件中。一个好消息是许多第三方库相继推出了es版,配合tree-shaking食用,口感更佳,这也是官方号称提速98%的重要前提之一(冷漠脸)。

除此以外,为了防止用户不小心修改输出元素的属性,有些库会将最终的输出元素用Object.freeze方法包裹起来,这也属于side effects之一,同样也会对tree shaking产生影响。

坏消息是ES6其实也提供import()方法支持动态引入依赖,导致 tree-shaking 不能发挥全部功效。

除此以外,为了防止用户不小心修改输出元素的属性,有些库会将最终的输出元素用Object.freeze方法包裹起来,这也属于side effects之一,同样也会对tree shaking产生影响。

回到Webpack 4,官方提供了sideEffects属性,通过将其设置为false,可以主动标识该类库中的文件只执行简单输出,并没有执行其他操作,可以放心shaking。除了可以减小bundle文件的体积,同时也能够提升打包速度。为了检查side effects,Webpack需要在打包的时候将所有的文件执行一遍。而在设置sideEffects之后,则可以跳过执行那些未被引用的文件,毕竟已经明确标识了“我是平民”。因此对于一些我们自己开发的库,设置sideEffects为false大有裨益。

Scope hoisting

废弃插件:ModuleConcatenationPlugin

新增属性:concatenateModules

//开启前
[
    /* 0 */
    function(module, exports, require) {
        var module_a = require(1)
        console.log(module_a['default'])
    }

    /* 1 */
    function(module, exports, require) {
        exports['default'] = 'module A'
    }
]
//开启后
[
    function(module, exports, require) {
        var module_a_defaultExport = 'module A'
        console.log(module_a_defaultExport)
    }
]

concatenateModules被开启之后,可以看出bundle文件中的函数声明变少了,因而可以带来的好处,其一,文件的体积比之前更小了,其二,运行代码时创建的函数作用域变少了,开销也随之变少了。不过scope hoisting的效果同样也依赖于静态分析。

Code splitting

废弃插件:CommonsChunkPlugin

新增属性:splitChunks,runtimeChunk, occurrenceOrder等

在 webpack 3 中,性能优化时,我们通常会设置多个 entry。一个作为业务代码的入口,一个作为vendors的入口。不过存在两个问题,第一个问题是,尽管vendors被单独设置了entry point,但是在每次启动本地服务的时候,尽管打包的结果不变,hash值不变,浏览器的缓存文件也被充分利用了,它的打包过程依然会执行,所以启动时间并不会缩短,第二个问题是,许多人在使用CommonsChunkPlugin的时候并没有注意到Webpack会将runtime一起打包进vendors文件,所以每次启动的时候,尽管你并没有修改任何第三方依赖,但是vendors文件的hash值却变了,导致浏览器缓存实际上并没有被利用起来。要解决这个问题,需要配置CommonsChunkPlugin将runtime单独打包成一个文件。

runtimeChunk之所以被单独设置为一个配置项,应该就是为了主动帮助用户避免上文所述的问题吧。

occurrenceOrder应用的场景是如果不手动设置chunk的名字,而采用默认值的话,Webpack将会用更短的名字去命名引用频度更高的chunk。

默认配置(splitChunks)

  • 新切分出来的chunk是被共同依赖的,或是从node_modules中来的
  • 新的chunk大于30kb
  • 在按需加载时,最大并行下载chunk的数量应该小于等于5
  • 在首屏加载的时候,最小的并行下载chunk数量应该要小于等于3

迁移问题

首当其冲的就是兼容性的问题,很多我们常用的loader,plugin尚未对这次升级做好准备,找到合适的替代工具以及积极改造自研的工具将成为升级过程中一场重要战役。

  • CommonsChunkPlugin + DllPlugin
  • Ts-loader(awesome-typescript-loader暂时未合并)
  • MiniCssExtractPlugin 替代 ExtractTextWebpackPlugin
  • InlineChunkWebpackPlugin(Webpack 4尚未支持),上文说到通过配置runtimeChunk为true,可以将运行时打包成独立的chunk,然而这个chunk体积很小,单独占用一个http请求稍显浪费,inline显然是更好的选择。

总结与未来

总结

  • 零配置运行
  • 打包速度提高90%
  • tree shaking es6
  • runtimeChunk和splitChunks更好用

未来

  • CSS成原生模块
  • HTML成原生模块
  • URL/FILE成为原生模块
  • 不需要extract-text-webpack-plugin
  • 不需要CSS FILE/URL loader
  • CSS Code Splitting
  • HTML entry

Refer



留言