webpack 工程化实践总结之性能优化

一、代码体积优化

  1. JavaScript 压缩
    mode=production 下,Webpack 会自动压缩代码,我们可以自定义自己的压缩工具,这里推荐 terser-webpack-plugin,它是 Webpack 官方维护的插件,使用 terser 来压缩 JavaScript 代码。UglifyJS 在压缩 ES5 方面做的很优秀,但是随着 ES6 语法的普及,UglifyJSES6 代码压缩上做的不够好,所以有了uglify-es 项目,但是之后 uglify-es 项目不在维护了,terser 是从 uglify-es 项目拉的一个分支,来继续维护。terser-webpack-plugin 具有跟 Uglifyjs-webpack-plugin 相同的参数。
  2. 总结:
  • 合理划分代码职责,适当使用按需加载方案;
  • 善用 webpack-bundle-analyzer 插件,帮助分析 Webpack 打包后的模块依赖关系;
  • 设置合理的 SplitChunks 分组;
  • 对于一些 UI 组件库,例如 AntDesignElementUI 等,可以使用 bable-plugin-import 这类工具进行优化;
  • 使用 lodash、momentjs 这类库,不要一股脑引入,要按需引入,momentjs 可以用 date-fns 库来代替;
  • 合理使用 hash 占位符,防止 hash 重复出现,导致文件名变化从而 HTTP 缓存过期;
  • 合理使用 polyfill,防止多余的代码;
  • 使用 ES6 语法,尽量不使用具有副作用的代码,以加强 Tree-Shaking 的效果;
  • 使用 WebpackScope Hoisting(作用域提升)功能,而 Scope Hoistingwebpack 通过 ES6 语法的静态分析,分析出模块之间的依赖关系,尽可能地把模块放到同一个函数中
  1. CSS 压缩
    cssnano 是基于 postcss 的一款功能强大的插件包,它集成了 30 多个插件,只需要执行一个命令,就可以对我们的 CSS 做多方面不同类型的优化,比如:
  • 删除空格和最后一个分号;
  • 删除注释;
  • 优化字体权重;
  • 丢弃重复的样式规则;
  • 压缩选择器;
  • 减少手写属性;
  • 合并规则;
  1. 图片资源优化:
    通常我们的代码体积会比图片体积小很多,有的时候整个页面的代码都不如一张头图大。好在图片资源不会阻塞浏览器渲染,但是不合理的图片大小也会消耗一定的代码。可以使用:url-loader、svg-url-loader 和 image-webpack-loader 来优化图片,还可以使用雪碧图来优化图片资源,如下所示:
  • url-loader 可以按照配置将小于一定体积的静态文件内联进我们的应用。当我们指定了 limit 这个 options 选项,它会将文件编码成比无配置更小的 Base64 的数据 url 并将该 url 返回,这样可以将图片内联进 JavaScript 代码中,并节省一次 HTTP 请求。
  • svg-url-loader 的工作原理类似于 url-loader,除了它利用 URL encoding 而不是 Base64 对文件编码,对于 SVG 图片来说,svg-url-loader 的这种方式这是有效的,因为 SVG 文件本质上是纯文本文件,这种 URL encoding 编码规模效应更加明显。
  • 如果我们的项目中小图片特别多,例如有很多 icon 类的图标,这时候则推荐使用雪碧图(CSS Sprite)来合并这些小图到一张大图中,然后使用 background-position 来设置图片的位置,通过这样的方式可以节省多次小图片的请求。
  • 对于大图片来说,可以使用 image-webpack-loader 来压缩图片,image-webpack-loader 它支持 JPG、PNG、GIF 和 SVG格式的图片,因此我们在碰到所有这些类型的图片都会使用它。

二、增强缓存命中率

  1. 浏览器缓存策略和 Webpack 缓存相关配置,使用浏览器的持久化缓存方案分两步走,第一步是我们一般会将静态资源(JavaScript 、CSS、图片字体文件等)这些不经常变动的文件寻找更合适的服务器存放,比如放到 CDN 服务器上,并且配置单独的域名,比如百度常用的 CDN 域名是:x.bdimg.comx.bdstatic.com 域名,这样做的好处是:
  • 保证动静分离,将动态页面和静态资源分开部署有利于服务的更好维护;
  • CDN 可以更加接近用户的终端,提供加速服务,详细可以查看 CDN 的原理;
  • 静态资源不会具有动态逻辑,单独的域名可以减少页面请求中的 Cookie 等不必要字段,减少 HTTP带宽。
  • 将静态资源存放到单独的服务器之后,需要做的是配置合理的 HTTP 缓存相关协议,比如我们使用Cache-Control 告诉浏览器,当前文件的 max-age: Cache-Control: max-age=31536000
    上面设置了文件的缓存时间是一年(31536000=360024365)
  1. 完成第一步之后,第二步就是要针对发生了变更的静态资源进行重命名,这样静态的文件虽然使用了 CDN 和强缓存,但是只要内容变化,那么文件的路径(网址)发生了变化,浏览器还是会重新请求下载的:

  2. 所以缓存相关的 Webpack 打包优化方案,合理利用 HTTP 请求头的缓存相关字段,然后配合 Webpackchunkhashcontenthash 可以做到根据文件内容和依赖关系变化而增强浏览器缓存,另外根据代码的变更频率合理的拆分代码也能够起到缓存的最大作用,Webpack 中拆分代码用到的是动态加载方式和 optimization.splitChunks

三、功能拆分代码

  1. Webpack 代码拆分方式:
    Webpack 中,总共提供了三种方式来实现代码拆分(Code Splitting)
  • entry 配置:通过多个 entry文件来实现;
  • 动态加载(按需加载):通过写代码时主动使用 import() 或者 require.ensure 来动态加载;
  • 抽取公共代码:使用 splitChunks 配置来抽取公共代码。
  1. 设置更高权重的 cacheGroup ;使用 test 函数针对类型为 jscss 分别设置各自的cacheGroup。另外我们还可以使用 test 函数实现更细化的匹配,例如:忽略一部分文件等。

四、速度优化

  1. Webpack 的项目文件多了之后,构建过程会越来越慢,这时候就需要做一些构建速度方面的优化手段了。影响 Webpack 构建速度的有两个「大户」:一个是 loaderplugin 方面的构建过程,一个就是压缩,把这两个东西优化起来,可以减少很多发布的时间。

  2. 压缩速度优化:
    相对于构建过程而言,压缩相对我们来说只有生产环境打包才会做,而且压缩我们除了添加 cache 和多线程支持之外,可以优化的空间较小。我们在使用 terser-webpack-plugin 的时候可以通过下面的配置开启多线程和缓存:

const TerserPlugin = require('terser-webpack-plugin');

module.exports = {
     
    optimization: {
     
        minimizer: [
            new TerserPlugin({
     
                cache: true, // 开启缓存
                parallel: true // 多线程
            })
        ]
    }
};
  1. 从构建速度角度来如何提升打包速度,分别从构建过程和压缩两个维度来介绍提升速度的方法,其中压缩角度可以优化的点比较少,而在构建过程中可以从减少查找过程、多线程、提前编译和缓存多个角度来优化,其中重点介绍了使用减少查找过程的几种配置方式,使用 thread-loaderHappyPack 来开启多线程,使用 DLLPlugin 来预先编译和使用 babel-loader 的缓存等方法。实际项目中并不是非要做构建速度的优化,如果项目简单完全没有必要,要具体问题具体分析。不管怎样,保持 Webpack 版本最新是一个既简单又效果不错的方式!

五、Tree-Shaking

  1. Tree-Shaking 是一个前端术语,本意为摇树的意思,在前端术语中通常用于描述移除 JavaScript上下文中没用的代码,这样可以有效地缩减打包体积。关于 Tree-ShakingWebpack 官方文档有一段很形象的描述:你可以将应用程序想象成一棵树。绿色表示实际用到的源码和 library,是树上活的树叶。灰色表示无用的代码,是秋天树上枯萎的树叶。为了除去死去的树叶,你必须摇动这棵树,使它们落下。

  2. Tree-Shaking 实现原理:
    Tree-Shaking 的本质是消除无用的 JavaScript 代码。无用代码消除(Dead Code Elimination)广泛存在于传统的编程语言编译器中,编译器可以判断出某些代码根本不影响输出,然后消除这些代码,这个称之为 DCE(Dead Code Elimination)Tree-ShakingDCE 的一种新的实现,Javascript 同传统的编程语言不同的是,JavaScript 绝大多数情况是在浏览器中执行,需要通过网络进行加载,然后解析 JavaScript 文件再执行。

  3. Webpack Tree-Shaking 代码实战:
    Webpack 中,Tree-Shaking 是需要配合 mode=production 来使用的,这是因为 WebpackTree-Shaking 实际分了两步来实现:

  • Webpack 自己来分析 ES6 Modules 的引入和使用情况,去除不使用的 import 引入;
  • 借助工具(如 uglifyjs-webpack-pluginterser-webpack-plugin)进行删除,这些工具只在mode=production 中会被使用。
  1. 配置sideEffects:
    Webpack 的项目中,可以在 package.json 中使用 sideEffects 来告诉 webpack 哪些文件中的代码具有副作用,从而对没有副作用的文件代码可以放心的使用 Tree-Shaking 进行优化。如果自己的项目是个类库或者工具库,需要发布给其他项目使用,并且项目是使用 ES6 Modules 编写的,没有副作用,那么可以在该项目 package.json 设置 sideEffects:false 来告诉使用该项目的 webpack可以放心的对该项目进行 Tree-Shaking,而不必考虑副作用。

  2. Tree-Shaking 对前端项目来说可谓意义重大,是一个极致优化的理想世界,是前端进化的又一个终极理想。但是理想是美好的,现实是骨感的,真正发挥 Tree-Shaking 的强大作用,还需要我们在日常的代码中保持良好的开发习惯:

  • 要使用 Tree-Shaking 必然要保证引用的模块都是 ES6规范的,很多工具库或者类库都提供了 ES6语法的库,例如 lodashES6 版本是 lodash-es
  • 按需引入模块,避免「一把梭」,例如我们要使用 lodashisNumber ,可以使用 import isNumber from 'lodash-es/isNumber';,而不是 import {isNumber} from 'lodash-es';
  • 减少代码中的副作用代码。另外一些组件库,例如 AntDesignElementUI 这些组件库,本身自己开发了 Babel 的插件,通过插件的方式来按需引入模块,避免一股脑的引入全部组件。

你可能感兴趣的:(Gulp,Webpack,webpack,工程化实践总结,性能优化)