webpack配置知识梳理
压缩
图片压缩、使用 DataURL,以及基本的代码压缩
图片压缩:
之前提及使用 file-loader 来处理图片文件,在此基础上,我们再添加一个 image-webpack-loader 来压缩图片文件。
image-webpack-loader 的压缩是使用 imagemin 提供的一系列图片压缩类库来处理的,详细查看文档了解。
简单的配置如下:
1 | module.exports = { |
使用DataURL:
项目中个别不能使用svg图,搞成 CSS Sprites又觉得麻烦,就可以使用这种方式
使用url-loader,一般情况仅使用 limit 即可详细查看官方文档:
1 | module.exports = { |
代码压缩:
webpack 4.x mode 为 production 即会启动压缩 JS 代码的插件。
webpack 3.x
Js压缩:JS 的压缩使用uglify插件比较彻底(替换掉长变量等)
HTML、CSS压缩:虽然只能移除空格换行等无用字符,但也能在一定程度上减小文件大小。在 webpack 中的配置使用也不是特别麻烦,所以我们通常也会使用。
【对HTML的压缩】在html-webpack-plugin插件配置中。
html-webpack-plugin这个插件是使用 html-minifier 来实现 HTML 代码压缩的,minify 下的配置项直接透传给 html-minifier,配置项参考 html-minifier 文档即可。
1 | module.exports = { |
【对CSS的压缩】,通过css-loader:
css-loader 是使用 cssnano 来压缩代码的,minimize 字段也可以配置为一个对象,来将相关配置传递给 cssnano。更多详细内容请参考 cssnano 官方文档。
1 | module.exports = { |
代码分离
代码分离的原因:
在没有异步加载的时候,除去vender.js、manifest.js 其余的css、js会打包到一个js中,此时无论js的小改动还是css的小改动都会在用户刷新页面时重新加载。
解决方案:提取公共的css到单独的文件,分离不同模块的js,这样公共css可以缓存,分离的模块虽然里边也有css但是只有这部分修改时只重新加载这部分代码还是能接受的。之所以不把模块的css也分离出来,是因为分离出来后多加载分离的css文件也是一种时间损耗。
webpack 4.x代码分离:
1 | module.exports = { |
上边配置后页面引入commons.js和entry.bundle.js即可,或者使用html-webpack-plugin自动引入,没使用这个插件需要从 stats 的 entrypoints 属性来获取入口应该引用哪些 JS 文件。
进一步
相对于我们写的代码的公共代码,第三方类库的更新频率更低,所以我们可以把这部分代码再单独抽出来一个公共文件,更好的利用缓存。
1 | module.exports = { |
上边
第一种做法是显示指定哪些类库作为公共部分,
第二种做法实现的功能差不多,只是利用了 test 来做模块路径的匹配,
第三种做法是把所有在 node_modules 下的模块,即作为依赖安装的,都作为公共部分。
你可以针对项目情况,选择最合适的做法。
webpack 3.x代码分离:
webpack 3.x 以下的版本需要用到 webpack 自身提供的 CommonsChunkPlugin 插件。
1 | module.exports = { |
chunk 在这里是构建的主干,可以简单理解为【一个入口对应一个 chunk】。
以上插件配置在构建后会生成一个 commons.js 文件,该文件就是代码中的公共部分。上面的配置中 minChunks 字段为 3,【该字段的意思是当一个模块被 3 个以上的 chunk 依赖时,这个模块就会被划分到 commons chunk 中去】。单从这个配置的角度上讲,这种方式并没有 4.x 的 chunks: “all” 那么方便。
进一步
提取共享类库
1 | module.exports = { |
minChunks其实还可以是一个函数,如:
该函数在分析每一个依赖的时候会被调用,可以在函数中针对每一个模块做更加精细化的控制。
1 | // module: 传入当前依赖模块的信息 |
Demo:
1 | minChunks: (module, count) => { |
其余详见文档
进一步控制JS大小
模块按需加载:
要按需加载代码模块很简单,遵循 ES 标准的动态加载语法 dynamic-import 来编写代码即可,webpack 会自动处理使用该语法编写的模块:
1 | // import 作为一个方法使用,传入模块名即可,返回一个 promise 来获取模块暴露的对象 |
如果没有添加注释 webpackChunkName: “lodash” 以及 output.chunkFilename 配置,那么分离出来的文件名称会以简单数字的方式标识,不便于识别。
注意一下,如果你使用了 Babel 的话,还需要 Syntax Dynamic Import 这个 Babel 插件来处理 import() 这种语法。
由于动态加载代码模块的语法依赖于 promise,对于低版本的浏览器,需要添加 promise 的 polyfill 后才能使用。
动态加载代码时依赖于网络,其模块内容会异步返回,所以 import 方法是返回一个 promise 来获取动态加载的模块内容。
Tree shaking
可以移除 JavaScript 上下文中的未引用代码,删掉用不着的代码,能够有效减少 JS 代码文件的大小。
在 webpack 中,只有启动了 JS 代码压缩功能(即使用 uglify)时,会做 Tree shaking 的优化。webpack 4.x 需要指定 mode 为 production。
如果你在项目中使用了 Babel 的话,要把 Babel 解析模块语法的功能关掉,在 .babelrc 配置中增加 “modules”: false 这个配置,这样可以把 import/export 的这一部分模块语法交由 webpack 处理,否则没法使用 Tree shaking 的优化。
1 | { |
1 | // 下边启动了Tree shaking之后,构建出来的结果就会移除 square 的那一部分代码了。 |
打包压缩后还是可以发现,Person 这一块看起来没用到的代码出现在文件中。现在如果你在 Babel 配置中增加 “loose”: true 配置的话,Person 这一块代码就可以在构建时移除掉了。相关问题自行搜索。
sideEffects
webpack 4.x 才具备的特性
现在 lodash 的 ES 版本 的 package.json 文件中已经有 sideEffects: false 这个声明了,当某个模块的 package.json 文件中有了这个声明之后,webpack 会认为这个模块没有任何副作用,只是单纯用来对外暴露模块使用,那么在打包的时候就会做一些额外的处理。
1 | import { forEach, includes } from 'lodash-es' |
最终 webpack 不会把 lodash-es 所有的代码内容打包进来,只是打包了你用到的那两个方法,这便是 sideEffects 的作用。
提升 webpack 的构建速度
- 减少 resolve 的解析
- 减少 plugin 的消耗
- 换种方式处理图片
- 使用 DLLPlugin
- 积极更新 webpack 版本
减少 resolve 的解析
1 | modules: [ |
在编码时,如果是使用我们自己本地的代码模块,尽可能编写完整的路径,避免使用目录名,如:import ‘./lib/slider/index.js’
把 loader 应用的文件范围缩小
尽可能把 loader 应用的文件范围缩小,只在最少数必须的代码模块中去使用必要的 loader
例如 node_modules 目录下的其他依赖类库文件,基本就是直接编译好可用的代码,无须再经过 loader 处理了
如果没有配置 include,所有的外部依赖模块都经过 Babel 处理的话,构建速度也是会收很大影响的。
1 | rules: [ |
减少 plugin 的消耗
webpack 的 plugin 会在构建的过程中加入其它的工作步骤,如果可以的话,适当地移除掉一些没有必要的 plugin。
这里再提一下 webpack 4.x 的 mode,区分 mode 会让 webpack 的构建更加有针对性,更加高效。例如当 mode 为 development 时,webpack 会避免使用一些提高应用代码加载性能的配置项,如 UglifyJsPlugin,ExtractTextPlugin 等,这样可以更快地启动开发环境的服务,而当 mode 为 production 时,webpack 会避免使用一些便于 debug 的配置,来提升构建时的速度,例如极其消耗性能的 Source Maps 支持。
换种方式处理图片
之前我们用webpack 的 image-webpack-loader 来压缩图片
换一种思路,我们可以直接使用 imagemin 来做图片压缩,编写简单的命令即可。然后使用 pre-commit 这个类库来配置对应的命令,使其在 git commit 的时候触发,并且将要提交的文件替换为压缩后的文件。
这样提交到代码仓库的图片就已经是压缩好的了,以后在项目中再次使用到的这些图片就无需再进行压缩处理了,image-webpack-loader 也就没有必要了。
使用 DLLPlugin
DLLPlugin 是 webpack 官方提供的一个插件,也是用来分离代码的,和 optimization.splitChunks(3.x 版本的是 CommonsChunkPlugin)有异曲同工之妙,如果项目不涉及性能优化这一块,基本上使用 optimization.splitChunks 即可。
使用DLLPlugin需要额外的一个构建配置,用来打包公共的那一部分代码,举个例子,假设这个额外配置是 webpack.dll.config.js:
1 | module.exports = { |
然后在我们原来的webpack配置中添加一个webpack.DllReferencePlugin 配置
1 | module.exports = { |
在构建的时候,我们需要优先使用 webpack.dll.config.js 来打包,如 webpack -c webpack.dll.config.js –mode production,构建后生成公共代码模块的文件 vendor.js 和 manifest.json,然后再进行应用代码的构建。
你会发现构建结果的应用代码中不包含 lodash 的代码内容,这一部分代码内容会放在 vendor.js 这个文件中,DLLPlugin 构建出来的内容无需每次都重新构建,后续应用代码部分变更时,你不用再执行配置为 webpack.dll.config.js 这一部分的构建,沿用原本的构建结果即可,所以相比 optimization.splitChunks,使用 DLLPlugin 时,构建速度是会有显著提高的。
但是当你升级 lodash(即你的公共部分代码的内容变更)时,要重新去执行 webpack.dll.config.js 这一部分的构建,不然沿用的依旧是旧的构建结果,使用上并不如 optimization.splitChunks 来得方便。
还有一点需要注意的是,html-webpack-plugin 并不会自动处理 DLLPlugin 分离出来的那个公共代码文件,我们需要自己处理这一部分的内容,可以考虑使用 add-asset-html-webpack-plugin,详见文档
webpack 4.x 的构建性能
webpack 4.0 版本做了很多关于提升构建性能的工作
- AST 可以直接从 loader 直接传递给 webpack,避免额外的解析,对这一个优化细节有兴趣的可以查看这个 PR。
- 使用速度更快的 md4 作为默认的 hash 方法,对于大型项目来说,文件一多,需要 hash 处理的内容就多,webpack 的 hash 处理优化对整体的构建速度提升应该还是有一定的效果的。
- Node 语言层面的优化,如用 for of 替换 forEach,用 Map 和 Set 替换普通的对象字面量等等,这一部分就不展开讲了,有兴趣的同学可以去 webpack 的 PRs 寻找更多的内容。
- 默认开启 uglifyjs-webpack-plugin 的 cache 和 parallel,即缓存和并行处理,这样能大大提高 production mode 下压缩代码的速度。
除此之外,还有比较琐碎的一些内容,可以查阅:webpack release 4.0,留意 performance 关键词。
可以看出4.x 的构建性能对比 3.x 是有很显著的提高,而 webpack 官方后续计划加入多核运算,持久化缓存等特性来进一步提升性能(可能要等到 5.x 版本了),所以,及时更新 webpack 版本,也是提升构建性能的一个有效方式。
另外当我们面对因项目过大而导致的构建性能问题时,我们也可以换个角度,思考在 webpack 之上的另外一些解决方案,不要过分依赖于 webpack。
ps.
webpack4.x新增了 mode 参数(4.x 是必要的参数)
webpack5.x后增加对CSS、HTML模块类型的支持:
- CSS 模块类型的支持,可以用 CSS 文件作为入口
- HTML 模块类型的支持,可以用 HTML 文件作为入口