webpack4打包优化(HappyPack、thread-loader)

8,778

一、速度分析

安装插件speed-measure-webpack-plugin

npm install --save-dev speed-measure-webpack-plugin

引入插件、创建插件对象

const SpeedMeasurePlugin = require('speed-measure-webpack-plugin'); //引入插件
const smp = new SpeedMeasurePlugin(); //创建插件对象

使用插件的wrap()方法将配置包起来

module.exports = smp.wrap({
  entry: {
    index: './src/index.js',
    search: './src/search.js',
  }, 
  output: {
    path: path.join(__dirname, 'dist'), //__dirname(当前模块的目录名) + dist
    filename: '[name]_[chunkhash:8].js', //打包后输出的文件名,添加文件指纹 chunkhash
  },
plugpins: [],
.....
});

打包完成后控制台会输出各个loader的打包耗时,可根据耗时进一步优化打包速度 image.png

二、体积分析

体积分析可以分析哪些问题?

  1. 依赖的第三方模块文件大小
  2. 业务里面的组件代码大小

打包后可以很清晰直观的看出各个模块的体积占比

安装插件webpack-bundle-analyzer

npm install --save-dev webpack-bundle-analyzer

引入插件、创建插件对象

const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer');

添加plugpins配置

 plugins: [
    new BundleAnalyzerPlugin()
  ],

打包完成后浏览器会打开http://127.0.0.1:8888/显示打包后的体积分析

image.png

三、打包速度优化

webpack构建过程中直接影响构建效率的,一个是文件的编译,另一个是文件的分类打包。相较之下文件的编译更为耗时,而且在Node环境下文件只能一个一个去处理,因此这块的优化需要解决。那么要怎样优化打包速度呢?

1. 使用高版本的webpack和node.js

webpack4新版本的优化使用v8引擎,v8带来的优化包括

  • for of 替代 forEach
  • Map和Set 替代Object
  • includes 替代 indexOf()
  • 默认使用更快的md4 hash算法 替代 md5算法,md4较md5速度更快
  • webpack AST 可以直接从loader传递给AST,从而减少解析时间
  • 使用字符串方法替代正则表达式

更高版本的node.js对原生js api和js数据结构做出进一步的优化

2. 多进程/多实例构建(资源并行解析)

在webpack构建过程中,我们需要使用Loader对js,css,图片,字体等文件做转换操作,并且转换的文件数据量也是非常大的,且这些转换操作不能并发处理文件,而是需要一个个文件进行处理,我们需要的是将这部分任务分解到多个子进程中去并行处理,子进程处理完成后把结果发送到主进程中,从而减少总的构建时间。

可选方案

  • thread-loader(官方推出)
  • parallel-webpack
  • HappyPack
HappyPack

注:由于HappyPack作者对js的兴趣逐步丢失,所以之后维护将变少,webpack4及之后推荐使用thread-loader

原理:每次webpack解析一个模块,HappyPack会将它及它的依赖分配给worker进程中; HappyPack会将模块进行一个划分,比如我们有多个模块,这些模块交给HappyPack,首先在webpack compiler(钩子)的run方法之后,进程就会到达HappyPack,HappyPack会做一些初始化,初始化之后会创建一个线程池,线程池会将构建任务里面的模块进行一个分配,比如会将某个模块以及它的一些依赖分配给其中的一个HappyPack线程,以此类推,那么一个HappyPack的一个线程池会包括多个线程,这时候线程池的这些线程会各自去处理其中的模块以及它的依赖,处理完成之后会有一个通信的过程,会将处理好的资源传输给HappyPack的一个主进程,完成整个的一个构建过程。

HappyPack

将src目录下复制出多个相同页面

src.png

在没引入HappyPack之前执行打包 build.png

安装 npm install --save-dev happypack 注:如果在webpack4使用需要HappyPack5.0的版本 引入之后将rules对js的编译改为happypack/loader

rules: [
      {
        test: /.js$/, //对所有js后缀的文件进行编译
        use: [
          // 'babel-loader'
          'happypack/loader',
        ],
      },
]

在插件中加入happypack-loader

  plugins: [
    new HappyPack({
      // 3) re-add the loaders you replaced above in #1:
      loaders: ['babel-loader'],
    }),
]

happypack.png

很明显可以看出使用happypack之后打包速度加快很多

thread-loader

原理:与HappyPack类似,每次webpack解析一个模块,thread-loader会将它及它的依赖分配给worker进程中; 安装

npm install --save-dev thread-loader

在rule中添加thread-loader,thread-loader可以进行一些配置,例如workers(进程数)

rules: [
      {
        test: /.js$/, //对所有js后缀的文件进行编译
        include: path.resolve('src'), //表示在src目录下的.js文件都要进行一下使用的loader
        use: [
          'babel-loader',
          {
            loader: 'thread-loader',
            options: {
              workers: 3,
            },
          },
          // 'happypack/loader',
        ],
      },
]

thread-loader.png

使用thread-loader之后打包速度也有明显提升

3. 多进程/多实例进行代码压缩(并行压缩)

在代码构建完成之后输出之前有个代码压缩阶段,这个阶段也可以进行并行压缩来达到优化构建速度的目的;

可选方案

  • webpack-parallel-uglify-plugin
  • uglifyjs-webpack-plugin
  • terser-webpack-plugin**(webpack4.0推荐使用,支持压缩es6代码)**
npm install terser-webpack-plugin --save-dev
const TerserPlugin = require('terser-webpack-plugin');

optimization中添加TerserPlugin插件,开启parallel

  optimization: {
    minimize: true,
    minimizer: [
      new TerserPlugin({
        //代码压缩插件
        parallel: 4, //开启并行压缩
      }),
    ],
  },

4. 通过分包提升打包速度

可以使用html-webpack-externals-plugin分离基础包,分离之后以CDN的方式引入所需要的资源文件,缺点就是一个基础库必须指定一个CDN,实际项目开发中可能会引用到多个基础库,还有一些业务包,这样会打出很多个script标签

new HtmlWebpackExternalsPlugin({
  externals: [
    {
      module: 'react',
      entry: 'https://unpkg.com/react@16/umd/react.development.js',
      global: 'React',
    },
  ],
})

进一步分包,采用预编译资源模块

采用webpack官方内置的插件DLLPlugin进行分包,DdllReferenceRlugin对manifest.json引用 DLLPlugin可以将项目中涉及到的例如react、reactdom、redux等组件和框架库打包成一个文件,同时生成manifest.json文件 manifest.json是对分离出来的包进行一个描述,实际项目就可以引用manifest.json,引用之后就会关联DLLPlugin分离出来的包,这个文件是用来让 DLLReferencePlugin 映射到相关的依赖上去

  1. 首先使用DLLPlugin进行分包 创建一个单独的构建配置文件,webpack.dll.js,在该配置文件中指定出需要分离的包 在package.json中添加dll的编译语句
  "scripts": {
    "dll": "webpack --config webpack.dll.js"
  }

webpack.dll.js

const webpack = require('webpack');
const path = require('path');

module.exports = {
  mode: 'development',
  entry: {
    //对应output 中的library
    library: ['react', 'react-dom'],
  },
  output: {
    filename: '[name]_[chunkhash].dll.js', //分离出来的文件名称,一个占位符+hash.dll.js  [name]对应的是entry的library
    path: path.join(__dirname, 'build/library'), //输出到当前目录下的build目录
    library: '[name]', //打包后暴露出的库的名字
  },
  plugins: [
    new webpack.DllPlugin({
      name: '[name]_[hash]', //打包后library.json中的name
      path: path.join(__dirname, 'build/library/[name].json'), //打包后生成[name].json的路径
    }),
  ],
};

npm run dll之后在build目录下会生成两个文件

image.png

也就是前面提到的manifest.json

构建好之后使用DllReferencePlugin引用manifest.json

plugins: [
    new webpack.DllReferencePlugin({
      manifest: require('./build/library/library.json'),
    }),
]

5. 通过缓存提升二次打包速度

  • babel-loader 开启缓存
  • terser-webpack-plugin 开启缓存
  • 使用cache-loader或者 hard-source-webpack-plugin
    new HappyPack({
      loaders: ['babel-loader?cacheDirectory=true'],
    })

设置babel-loader的cacheDirectory=true开启缓存

  optimization: {
    minimize: true,
    minimizer: [
      new TerserPlugin({
        //代码压缩插件
        parallel: 4, //开启并行压缩
        cache: true,
      }),
    ],
  },

设置terser-webpack-plugin插件的cache: true开启缓存

使用hard-source-webpack-plugin

npm install --save-dev hard-source-webpack-plugin
  plugins: [
    new HardSourceWebpackPlugin()  
]

第一次运行开始写入缓存文件 image.png

image.png

开启缓存之后明显提升了打包速度

6. 缩小构建目标

尽可能的少构建模块,比如babel-loader不解析 node_modules

  • 优化resolve.modules配置(减少模块搜索层级)
  • 优化resolve.mainFields配置
  • 优化resolve.extensions配置

四、打包体积优化

主要对打包后图片、js、css文件的资源体积优化

1. 图片压缩

使用Node库的imagemin,配置image-webpack-loader对图片优化,改插件构建时会识别图片资源,对图片资源进行优化 imagemin优点分析

  • imagemin有很多定制选项
  • 可以引入更多第三方优化插件,例如pngquant
  • 可以引入多种图片格式

imagemin的压缩原理

  • pngquant:是一款PNG的压缩器,通过将图像转换为具有alpha通道(通常比24/32位PNG文件小60%-80%)的更高效的8位PNG格式,可显著减小文件大小; 阿尔法通道(Alpha Channel)是指一张图片的透明和半透明度
  • pngcrush:其主要目的是通过尝试不同的压缩级别和PNG过滤方法来降低PNG IDAT数据流的大小;
  • optipng:其涉及灵感来自于pngcrush。optipng可将图像文件重新压缩位更小的尺寸,而不会丢失任何信息;
  • tingpng:也是将24位png文件转化为更小具有索引的8位图片,同时所有非必要的metadata也会被剥离掉;
npm install image-webpack-loader --save-dev
rules: [
      {
        test: /.(png|jpg|gif|jpeg)$/,
        use: [
          {
            loader: 'file-loader',
            options: {
              name: '[name]_[hash:8].[ext]',
            },
          },
          {
            loader: 'image-webpack-loader',
            options: {
              mozjpeg: {
                progressive: true,
                quality: 65,
              },
              // optipng.enabled: false will disable optipng
              optipng: {
                enabled: false,
              },
              pngquant: {
                quality: [0.65, 0.9],
                speed: 4,
              },
              gifsicle: {
                interlaced: false,
              },
              // the webp option will enable WEBP
              webp: {
                quality: 75,
              },
            },
          },
        ],
      }
]

2. 擦除无用到的css

可以同通过插件遍历代码,识别已经用到的css class 安装插件 npm i purgecss-webpack-plugin -D

const PurgecssPlugin = require('purgecss-webpack-plugin');
const PATHS = {
  src: path.join(__dirname, 'src'),
};
plugins: [
new PurgecssPlugin({
      paths: glob.sync(`${PATHS.src}/**/*`, { nodir: true }),
    }),
]

3. 动态Polyfill

什么是Polyfill?

babel默认只转换新的JavaScript语法(syntax),如箭头函数等,而不转换新的API,比如Iterator、Generator、Set、Maps、Proxy、Reflect、Symbol、Promise等全局对象,以及一些定义在全局对象上的方法(比如Object.assign)都不会转码;因此我们需要polyfill; 链接:www.jianshu.com/p/482285279…

官方解释

  • 它是一项服务,接受对一组浏览器功能的请求,并且仅返回请求浏览器所需的polyfill。
  • 全世界有许多不同的浏览器和版本的浏览器在使用,每种浏览器都具有与其他浏览器完全不同的功能集。这会使浏览器开发成为一项艰巨的任务。流行浏览器的最新版本可以完成许多旧浏览器无法完成的任务-但是您可能仍必须支持旧浏览器。通过尝试使用polyfills重新创建缺少的功能,Polyfill.io使支持不同的浏览器变得更简单:您可以在支持或不支持的浏览器中使用最新和最强大的功能。

通过caniuse查询可知,promise有96.17%的兼容性 image.png

由于Polyfill是非必须的,对一些不支持es6新语法的浏览器才需要加载polyfill,为了百分之3.几的用户让所有用户去加载Polyfill是很没有必要的;

我们可以通过polyfill-service,只给用户返回需要的polyfill 每次用户打开一个页面,浏览器端会请求polyfill-service,polyfill-service会识别用户User Agent,下发不同的polyfill 如何使用动态Polyfill service 通过polyfill.io官方提供的服务,自建polyfill服务polyfill.io/v3/url-buil…

或者通过引入cdn<script src="https://cdn.polyfill.io/v2/polyfill.min.js"></script>来加载polyfill-service 可以通过加载polyfill.io/v3/polyfill…网址来查看不同浏览器User Agent的情况;

webpack4对打包构建速度优化和体积优化的内容到此结束,该文章通过学习程柳锋老师的《玩转webpack》课程实践总结得出,欢迎讨论和指正,以上。