首页 > 其他分享 >详解webpack构建优化

详解webpack构建优化

时间:2022-10-10 17:25:46浏览次数:81  
标签:plugin module js webpack 详解 构建 loader

当项目越来越复杂时,会面临着构建速度慢和构建出来的文件体积大的问题。webapck构建优化对于大项目是必须要考虑的一件事,下面我们就从速度和体积两方面来探讨构建优化的策略。

分析工具

在优化之前,我们需要了解一些量化分析的工具,使用它们来帮助我们分析需要优化的点。

webpackbar

webpackbar可以在打包时实时显示打包进度。配置也很简单,在plugins数组中加入即可:

const WebpackBar = require('webpackbar')
module.exports = {
  plugins: [    ...    new WebpackBar()  ]
}

speed-measure-webpack-plugin

使用speed-measure-webpack-plugin可以看到每个loader和plugin的耗时情况。

和普通插件的使用略有不同,需要用它的wrap方法包裹整个webpack配置项。

const SpeedMeasurePlugin = require('speed-measure-webpack-plugin')
const smp = new SpeedMeasurePlugin()
module.exports = smp.wrap({
  entry: './src/main.js',
  ...
})

打包后,在命令行的输出信息如下,我们可以看出哪些loader和plugin耗时比较久,然后对其进行优化。

image,.png

webpack-bundle-analyzer

webpack-bundle-analyzer以可视化的方式让我们直观地看到打包的bundle中到底包含哪些模块内容,以及每一个模块的体积大小。我们可以根据这些信息去分析项目结构,调整打包配置,进行优化。

在plugins数组中加入该插件。构建完成后,默认会在http://127.0.0.1:8888/展示分析结果。

const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
module.exports = {
  plugins: [    ...    new BundleAnalyzerPlugin()  ]
}

image.png webpack-bundle-analyzer会计算出模块文件在三种情形下的大小:

  • stat:文件未经过任何转换的原始大小
  • parsed:文件经过转换后的输出大小(比如babel-loader转换ES6->ES5、UglifyJsPlugin压缩等等)
  • gzip:parsed后的文件,经过Gzip压缩的大小
    使用speed-measure-webpack-pluginwebpack-bundle-analyzer本身也会增加打包时间(webpack-bundle-analyzer特别耗时),所以建议这两个插件在开发分析时使用,而在生产环境去掉。

image.png

优化构建速度

多进程构建

运行在Node.js之上的 Webpack 是单线程的,就算有多个任务同时存在,它们也只能一个一个排队执行。当项目比较复杂时,构建就会比较慢。如今大多数CPU都是多核的,我们可以借助一些工具,充分释放 CPU 在多核并发方面的优势。参考webpack视频讲解:进入学习

比较常见的有happypackthread-loader

happypack

happypack能够将构建任务分解给多个子进程去并发执行,子进程处理完后再把结果发送给主进程。使用配置如下,就是把原有的loader的配置转移到happyPack中去处理。

const Happypack = require('happypack')
module.exports = {
  module:{
    rules:[
      {
        test: /\.js$/,
        use: 'happypack/loader?id=babel'   //问号后面的查询参数指定了处理这类文件的HappyPack实例的名字
      },
    ]
  },
  plugins:[
    new Happypack({
      id: 'babel',     //HappyPack实例名,对应上面rules中的“id=babel”
      use: ['babel-loader']     //原本要使用的loader
    })
  ]
}

thread-loader

happypack的作者已经没有这个项目进行维护了,在webpack4之后,可以使用thread-loader

thread-loader使用起来很简单,就是把它放置在其它loader之前,如下所示。放置在这个thread-loader之后的 loaders会运行在一个单独的worker池中。

module.exports = {
  module:{
    rules:[
      {
          test: /\.js$/,
          use: ['thread-loader','babel-loader']
      }
    ]
  },
}

image.png 如果是小项目,不建议开启多进程构建,因为开启进程是需要花费时间的,构建速度反而会变慢。

利用缓存

利用缓存可以提升二次构建速度(下面的对比图都是二次构建的速度)。使用缓存后,在node_modules中会有一个.cache目录,用于存放缓存的内容。

cache-loader

在一些性能开销较大的 loader 之前添加此cache-loader,以将结果缓存到磁盘中。

module.exports = {
  module: {
    rules: [
      {
        test: /\.js$/,
        use: ['cache-loader','babel-loader']
      }
    ]
  }
}

可以看出,使用cache-loader后,构建速度有非常明显的提升。

image.pngbabel-loader使用缓存,也可以不借助cache-loader,直接在babel-loader后面加上?cacheDirectory=true

module.exports = {
  module: {
    rules: [
      {
        test: /\.js$/,
        use: ['babel-loader?cacheDirectory=true']
      }
    ]
  }
}

hard-source-webpack-plugin

hard-source-webpack-plugin用于开启模块的缓存。

const HardSourceWebpackPlugin = require("hard-source-webpack-plugin")
module.exports = {
  plugins:[    new HardSourceWebpackPlugin()  ]
}

使用hard-source-webpack-plugin后,二次构建速度大概提升了90%。

image.png

include/exclude

通常来说,loader会处理符合匹配规则的所有文件。比如babel-loader,会遍历项目中用到的所有js文件,对每个文件的代码进行编译转换。而node_modules里的js文件基本上都是转译好了的,不需要再次处理,所以我们用 include/exclude 来帮我们避免这种不必要的转译。

module.exports = {
  module:{
    rules:[
      {
          test: /\.js$/,
          use: ['babel-loader'],
          exclude: /node_modules/
          //或者  include: [path.resolve(__dirname, 'src')]
      }
    ]
  },
}

include直接指定查找文件夹,比exclude效率更高,更能提升构建速度。 image.png

动态链接库

上面的babel-loader可以通过include/exclude,避免处理node_modules里的第三方库。

但如果将第三方库代码和业务代码都打包进一个bundle文件,那么处理这个bundle文件的插件,比如uglifyjs-webpack-plugin、terser-webpack-plugin等,就没办法不处理里面第三方库内容。

其实第三方库代码基本都是成熟的,不用作什么处理。因此,我们可以将项目的第三方库代码分离出来。

常见的处理方式有三种:

  1. Externals
  2. SplitChunks
  3. DllPlugin

Externals可以避免处理第三方库,但每一个第三方库都得在html文档中增加一个script标签来引入,一个页面过多的js文件下载会影响网页性能,而且有时我们只使用第三方库中的一小部分功能,用script标签全量引入不太合理。

SplitChunks在每一次构建时都会重新构建第三方库,不能有效提升构建速度。

这里推荐使用DllPlugin和DLLReferencePlugin(配合使用),它们是webpack的内置插件。DllPlugin会将不频繁更新的第三方库单独打包,当这些第三方库版本没有变化时,就不需要重新构建。

使用方法:

  1. 使用DllPlugin打包第三方库

  2. 使用DLLReferencePlugin引用manifest.json,去关联第1步中已经打好的包

  • 首先,新建一个webpack配置文件webpack.dll.js用于打包第三方库(第1步)
const path = require('path')
const webpack = require('webpack')

module.exports = {
  mode: 'production',
  entry: {
    three: ['three', 'dat.gui']   // 第三方库数组
  },
  output: {
    filename: '[name].dll.js',    //[name]就是在entry
    path: path.resolve(__dirname, 'dist/lib'),
    library: '[name]'
  },
  plugins: [
    new webpack.DllPlugin({
      name: '[name]',
      path: path.resolve(__dirname, 'dist/lib/[name].json') //manifest.json的存放位置
    })
  ]
}

打包好后,可以看到,在dist目录下增加了一个lib文件夹。 image.png

  • 然后,我们在webpack.base.js做一下修改,去关联第1步中已经打好的包(第2步)
module.exports = {
  plugins:[
    //修改CleanWebpackPlugin配置
    new CleanWebpackPlugin({
      cleanOnceBeforeBuildPatterns: [  
        '!lib/**'              //在每次清楚dist目录时,不清理lib文件夹的内容
      ]
    }),
    // dll相关配置
    new webpack.DllReferencePlugin({    
      // 将manifest字段配置成我们第1步中打包出来的json文件
      manifest: require('./dist/lib/three.json')  
    })
  ]
}

再次打包后可以看到,相比于一开始整个项目的体积 9.11MB,体积减小了90%,因为这是一个多页面打包(多页面打包配置)的应用,每个页面都引用了体积庞大的three.js核心文件,我们把体积最大的three.js核心文件从每个页面的bundle中抽离出来后,bundle的体积大大减小。

image.png

再来看看构建时间:相比于使用DllPlugin之前,时间减少了30% 。 image.png

不仅仅是第三方库,业务代码中的基础库也可以通过进行DllPlugin分离。

优化构建体积

代码分割

分离第三方库和业务代码中的基础库,可以避免单个bundle.js体积过大,加载时间过长。并且在多页面构建中,还能减少重复打包。

常见的操作是通过SplitChunks(之前的文章已经详细地写过了:SplitChunks)和 动态链接库(如上所示),这里都不再赘述。

动态import

动态import的作用主要减少首屏资源的体积,非首屏的资源在用到的时候再去请求,从而提高首屏的加载速度。一个常见的例子就是单页面应用的路由管理(比如vue-router

{
  path: '/list',
  name: 'List',
  component: () => import('../views/List.vue')  
},

不是直接import组件(import List from '../views/List.vue'),那样会把组件都打包进同一个bundle。而是动态import组件,凡是通过import()引用的模块都会打包到独立的bundle,使用到的时候再去加载。对于功能复杂,又不是首屏必须的资源都推荐使用动态import。

<span @click="loadModal">show弹窗</span>
/***
methods: {
  loadModal(){
     import('../modal/index.js')
  }
}
***/

treeShaking

使用ES6的import/export语法,并且使用下面的方式导入导出你的代码,而不要使用export default。

// util.js 导出
export const a = 1
export const b = 2
export function afunc(){}
或
export { a, b, afunc }

// index.js 导入
import { a, b } from './util.js'
console.log(a,b)

那么在mode:production生产环境,就会自动开启tree-shaking,移除没有使用到的代码,上面例子中的afunc函数就不会被打包到bundle中。

代码压缩

常用的js代码压缩插件有:uglifyjs-webpack-pluginterser-webpack-plugin

在webpack4中,生产环境默认开启代码压缩。我们也可以自己配置去覆盖默认配置,来完成更定制化的需求。

v4.26.0版本之前,webpack内置的压缩插件是uglifyjs-webpack-plugin,从v4.26.0版本开始,换成了terser-webpack-plugin。我们这里也以terser-webpack-plugin为例,和普通插件使用不同,在optimization.minimizer中配置压缩插件

const TerserPlugin = require('terser-webpack-plugin');
module.exports = {
  optimization: {
    minimizer: [  
      new TerserPlugin({
        parallel: true,  //开启并行压缩,可以加快构建速度
        sourceMap: true, //如果生产环境使用source-maps,则必须设置为true
      })
    ]
  }
}

image.png

雪碧图

雪碧图将多张小图标拼接成一张大图,在HTTP1.x环境下,雪碧图可以减少HTTP请求,加速网页的显示速度。

用于合成雪碧图的图标体积要小,较大的图片不建议拼接成雪碧图;同时要是网站静态图标,不是通过ajax请求动态获取的图标。所以通常是作为网站logo、icon之类的图片。

开发时,可以是UI提供雪碧图,但是每新增一个图标,就要重新制作一次,重新计算偏移量,比较麻烦。通过webpack插件合成雪碧图,就可以在开发时直接使用单个小图标,在打包时,自动合成雪碧图,并自动自动修改css中的background-position的值。

下面,我们借助postcss-sprites来自动合成雪碧图。

首先,在webpack.base.js中配置postcss-loader

//webpack.base.js
module.exports = {
  module: {
    rules: [
      {
        test: /\.css$/,
        use: ['vue-style-loader','css-loader', 'postcss-loader']  //配置postcss-loader
      },
      {
        test: /\.less$/,
        use: [
          'vue-style-loader','css-loader', 'postcss-loader', 'less-loader']  //配置postcss-loader
      }
    ]
  }
};

然后在项目根目录下新建.postcssrc.js,配置postcss-sprites

module.exports = {
  "plugins": [
    require('postcss-sprites')({
      // 默认会合并css中用到的所有静态图片
      // 使用filterBy指定需要合并的图片,比如这里这里只合并images/icon文件夹下的图片
      filterBy: function (image) {
        if (image.url.indexOf('/images/icon/') > -1) {
            return Promise.resolve();
        }
        return Promise.reject();
      }
    })
  ]
}

默认会把图片合并到名为sprite.png的雪碧图中。

在css中直接指定小图标当背景:

.star{
  display: inline-block;
  height: 100px;
  width: 100px;
  &.l1{
    background: url('../icon/star.png') no-repeat;
  }
  &.l2{
    background: url('../icon/star2.png') no-repeat;
  }
  &.l3{
    background: url('../icon/star3.png') no-repeat;
  }
}

打包完成后可以看到,自动修改了background-imagebackground-position

image.png image.png image.png

gzip

开启gzip压缩,可以减小文件体积。在浏览器支持gzip的情况下,可以加快资源加载速度。服务端和客户端都可以完成gzip压缩,服务端响应请求时压缩,客户端应用构建时压缩。但压缩文件这个过程本身是需要耗费时间和CPU资源的,如果存在大量的压缩需求,会加大服务器的负担。

所以可以在构建打包时候就生成gzip压缩文件,作为静态资源放在服务器上,接收到请求后直接把压缩文件返回。

使用webpack生成gzip文件需要借助compression-webpack-plugin,使用配置如下:

const CompressionWebpackPlugin = require("compression-webpack-plugin")
module.exports = {
  plugins: [
     new CompressionWebpackPlugin({
       test: /\.(js|css)$/,         //匹配要压缩的文件
       algorithm: "gzip"
     })
  ]
}

打包完成后除了生成打包文件外,还会额外生成 .gz后缀的压缩文件。可以看出,gzip压缩文件的体积比未压缩文件的体积小很多。

image.png

标签:plugin,module,js,webpack,详解,构建,loader
From: https://www.cnblogs.com/gogo2027/p/16776444.html

相关文章

  • 详解ROMA Connect API 流控实现技术
    摘要:本文将详细描述APIGateway流控实现,揭开高性能秒级流控的技术细节。1、概述ROMA平台的核心系统ROMAConnect源自华为流程IT的集成平台,在华为内部有超过15年的企业业......
  • TCP/IP 协议详解
    1.  TCP/IP协议模型  OSI参考模型分为七层:物理层、数据链路层、网络层、传输层、会话层、表示层、应用层。其实际是一个指导作用的协议,并没有实际的实现。主要还是看......
  • 利用MVC设计模式构建GUI(PyQt5版)
    今天介绍一个PyQt5中利用MVC设计模式构建GUI的例子,这个案例来源于《MATLAB面向对象编程——从入门到设计模式(第2版)》第7章内容,关于存取款的GUI工具设计,详情请参考127~160页......
  • linux top 查看CPU命令 top输出详解
    [root@localhost~]$top//动态查看进程使用资源的情况,每三秒刷新一次[root@localhost~]$top-c//动态查看进程使用资源的情况,但会详细地显示进程的命......
  • Go_Channel详解
    一channel介绍单纯地将函数并发执行是没有意义的。函数与函数间需要交换数据才能体现并发执行函数的意义。虽然可以使用共享内存进行数据交换,但是共享内存在不同的gorou......
  • Go_Goroutine详解
    Goroutine详解goroutine的概念类似于线程,但goroutine是由Go的运行时(runtime)调度和管理的。Go程序会智能地将goroutine中的任务合理地分配给每个CPU。Go语言之所以被称......
  • 第17章: 基于K8S构建Jenkins CICD平台
          基于K8S构建JenkinsCICD平台​ 作者刘畅时间2021-08-12  环境说明:操作系统-CentOS7.5主机名称IP地址备注k8s_nfs[2核2G]172.16.1.60nfs服务k8s_harbor[2核......
  • 利用 Kubernetes 内置 PodTemplate 管理 Jenkins 构建节点
    Jenkins可以很好地与Kubernetes集成,不管是控制器(controller)还是构建节点(agent),都能以Pod的形式运行在Kubernetes上。 熟悉Jenkins的用户,都知道Jenkins支持多种类......
  • 一文详解 | 低代码发展的 “背后推手”
    近年来,随着数字经济的发展,越来越多企业迈入了数字化转型的行列,低代码平台因为其低门槛、高效率的特性,被许多企业认为是数字化转型的利器,因而得到了长足发展。低代码市场快......
  • 详解数仓的锁相关参数及视图
    摘要:GaussDB(DWS)中锁等待可以设置等待超时相关参数,一旦等锁的时间超过参数配置值会抛错。本文分享自华为云社区《​​GaussDB(DWS)锁相关参数及视图详解​​》,作者:yd_22......