webpack构建优化小记
webpack相信大家都不陌生了,在工作当中相信大家会经常用到,在处理资源构建打包的时候非常的有用,本文分享一下我在使用webpack进行项目构建的过程中总结的一些优化经验。
模块的查找
当我们在项目中使用到第三方模块的时候,webpack在构建时,会先去当前目录下的 ./node_modules
目录下去找想找的模块,如果没找到就去上一级目录 ../node_modules
中找,再没有就去 ../../node_modules
中找,以此类推,到这里,是不是觉得和nodejs里面模块的寻找机制很相似
当我们在安装第三方模块时,默认会安装在当前项目的根目录的 ./node_modules
文件夹里面,因此,为了提高webpack的查找速度,我们可以通过配置来指明第三方模块的存放路径,如下:
1 | module.exports = { |
babel-loader的处理
由于ES6、ES7、ES8的新语法特性的快速普及,现代浏览器(chrome、safari、firefox、edge等)大多都已支持,因此我们可以使用这些新的特性进行编程,但是由于市面上各大浏览器厂商更新迭代过快,导致浏览器版本过多,对于新语法特性的支持也参差不齐,因此我们需要使用babel-loader
进行语法转换成ES5,如下:
1 | module.exports = { |
第三方模块构建处理
因为我们在文件里面可能会引入第三方模块来使用,第三方模块往往都是编译好的ES5的代码,并不需要loader再去处理,因此我们可以排除掉以提高loader的处理速度,如下:
1 | // 这里也可使用正则,如:/node_modules/,webpack也可以识别 |
缓存的处理
当我们的文件非常多的时候,构建的速度就会非常的慢了,babel-loader提供了一个cacheDirectory
属性,这个属性默认是false
不开启的,如果设置了这个参数并开启的话,被转换的结果将会被缓存起来,当webpack再次编译时,将会首先尝试从缓存中读取转换结果,以此避免资源浪费,使用如下:
1 | use: ['babel-loader?cacheDirectory'] |
ES(6|7|8)新的API的处理
由于Babel默认只转换新的javascript句法(syntax),对于新的API是不做处理的,比如Iterator、Generator、Set、Maps、Symbol、Promise等这些全局对象,以及一些定义在全局对象上的方法(比如Object.assign)都不会转码,这样如果浏览器不支持这些新的api,那么它们将不会工作,同时页面也将会报错误异常。
针对这个问题,我们可以使用bable-ployfill
来处理,这里有个新的问题,babel-ployfill
文件巨大,它默认把所有的新的API进行了重写,污染了全局变量,因此并不适合我们,针对现代浏览器,我们需要有针对的对不支持的API进行按需处理,因此便有了babel-plugin-transform-runtime
,使用它loader在处理的时候,会自动的为所需要的API进行自动添加,不会去污染全局API,使用的时候我们需要先提前安装好,如下:
1 | npm install babel-plugin-transform-runtime babel-preset-env babel-preset-stage-2 -S |
在项目根目录新建.babelrc
文件来进行配置,如下:
1 | { |
tree-shaking处理
当我们在项目里面使用ES6的module语法(export和import)的时候,需要告知babel-loader
不要将ES6的模块先转成CommonJS模块,使用如下:
1 | { |
当然,我们也可以在项目根目录新建.babelrc
文件来进行配置,如下:
1 | { |
使用 ParallelUglifyPlugin
webpack默认提供了UglifyJS插件来压缩JS代码,但是它使用的是单线程压缩代码,也就是当我们有多个js需要压缩的时候,它需要一个个文件进行压缩。因此在构建的时候会非常慢(因为压缩JS代码需要先把代码解析成用Object抽象表示的AST语法树,再去应用各种规则分析和处理AST,导致这个过程耗时非常大)。
这个时候我们的ParallelUglifyPlugin
就上场了,他会开启多个子进程,把对多个文件压缩的工作分别给多个子进程去完成,每个子进程其实还是通过 UglifyJS 去压缩代码,但是变成了并行执行,这样我们的构建效率就可以更快了,使用如下:
1 | const ParallelUglifyPlugin = require('webpack-parallel-uglify-plugin') |
在通过 new ParallelUglifyPlugin() 进行实例化时,支持以下参数:
- test:使用正则去匹配哪些文件需要被 ParallelUglifyPlugin 压缩,默认是 /.js$/,也就是默认压缩所有的 .js 文件。
- include:使用正则去命中需要被 ParallelUglifyPlugin 压缩的文件。默认为 []。
- exclude:使用正则去命中不需要被 ParallelUglifyPlugin 压缩的文件。默认为 []。
- cacheDir:缓存压缩后的结果,下次遇到一样的输入时直接从缓存中获取压缩后的结果并返回。cacheDir 用于配置缓存存放的目录路径。默认不会缓存,想开启缓存请设置一个目录路径。
- workerCount:开启几个子进程去并发的执行压缩。默认是当前运行电脑的 CPU 核数减去1。
- sourceMap:是否输出 Source Map,这会导致压缩过程变慢。
- uglifyJS:用于压缩 ES5 代码时的配置,Object 类型,直接透传给 UglifyJS 的参数。
- uglifyES:用于压缩 ES6 代码时的配置,Object 类型,直接透传给 UglifyES 的参数。
如下:
1 | new ParallelUglifyPlugin({ |
使用 HappyPack
happypack的工作原理是,告诉 HappyPack 核心调度器如何通过一系列 Loader 去转换一类文件,并且可以指定如何给这类转换操作分配子进程(和上面的ParallelUglifyPlugin
类似)。
下面是一个相对比较全面的针对js、css、图片等的一个处理:
1 | const os = require('os'); |
从上面可以看到,我们的配置有了一些改变,我们把文件的处理交给了happypack/loader
来处理了,并且在后面还有一个id
的参数,参数的值表示我们在使用happypack
插件去实例化的时候该选择哪一个实例去处理
在插件(plugins)配置里面,我们实例化了三个happypack
的实例,每个实例对应的都有一个id
,它的值就是我们上面在处理loader
的时候所携带的参数的值是一样相对应的,如?id=babel
,loaders的属性配置也和原来使用的loader的配置相同
同样的,happypack
还支持其它一些参数,如下:
- threads 代表开启几个子进程去处理这一类型的文件,默认是3个,类型必须是整数(如果你的CPU够强悍,可以多开启几个子进程,构建速度可以快的飞起)。
- verbose 是否允许 HappyPack 输出日志,默认是 true。
- threadPool 代表共享进程池,即多个 HappyPack实例都使用同一个共享进程池中的子进程去处理任务,以防止资源占用过多
希望以上的一些构建性能优化的点,可以在大家使用webpack进行资源编译的时候能够有所帮助!