【实操】使用thread-load替换happyPack实现loader多线程

4,255 阅读4分钟

webpack 4时使用的happyPack,因为项目需要升级依赖,再升级babel-core依赖至@babel/core,编译运行项目时,一直报错

cannot found babel-core module

定位问题存在在于调用了babel-core相关的webpack,找到了happyPack配置。

1、浅谈happyPack

happyPack实现多线程编译

webpack在node中是单线程的,但是在使用webpack编译项目时,loader、plugin需要处理的文件数量很大,webpack单线程处理导致效率低、速度慢,可以使用happyPack来实现多线程工作。happyPack将不同的loader、plugin工作分配到不同的子线程,在子线程结束完后再推入到主线程中,多线程同时编译同时减少编译时间。

基本使用

HappyPack同时提供pluginloader以完成其工作,因此您必须同时使用两者来启用它。

const path = require('path');
const HappyPack = require('happypack');

module.exports = {
  module: {
    rules: [
      {
        test: /.js$/,
        // 把对 .js 文件的处理转交给 id 为 babel 的 HappyPack 实例
        use: ['happypack/loader?id=babel'],
        // 排除 node_modules 目录下的文件,node_modules 目录下的文件都是采用的 ES5 语法,没必要再通过 Babel 去转换
        exclude: path.resolve(__dirname, 'node_modules'),
      },
      {
        // 把对 .css 文件的处理转交给 id 为 css 的 HappyPack 实例
        test: /.css$/,
        use: ExtractTextPlugin.extract({
          use: ['happypack/loader?id=css'],
        }),
      },
    ]
  },
  plugins: [
    new HappyPack({
      // 用唯一的标识符 id 来代表当前的 HappyPack 是用来处理一类特定的文件
      id: 'babel',
      // 如何处理 .js 文件,用法和 Loader 配置中一样
      loaders: ['babel-loader?cacheDirectory'],
      // ... 其它配置项
    }),
    new HappyPack({
      id: 'css',
      // 如何处理 .css 文件,用法和 Loader 配置中一样
      loaders: ['css-loader'],
    }),
    new ExtractTextPlugin({
      filename: `[name].css`,
    }),
  ],
};
  • loader配置中,全部交给happyPck/loader处理,其后的 id=babel告诉happyPck/loader选择哪个happyPack实例处理文件
  • plugin中,options中设置的idloader中使用的id对应。
实例化 happyPack 时的其他options
  • threads代表开启几个子进程去处理这一类型的文件,默认是3个,类型必须是整数。

  • verbose是否允许HappyPack输出日志,默认是true

  • threadPool代表共享进程池,即多个HappyPack实例都使用同一个共享进程池中的子进程去处理任务,以防止资源占用过多,相关代码如下:

const HappyPack = require('happypack')
const os = require('os') 
//os nodejs os模块提供了一些基本的系统操作函数
const happyThreadPool = HappyPack.ThreadPool({ size: os.cpus().length })
module.exports = {
    ...
        plugins: [
            new HappyPack({
                id: 'js',
                threadPool: happyThreadPool,
                loaders: ['babel-loader?cacheDirectory=true']
            }),
        ]
}

基本原理

image.png 在整个 Webpack 构建流程中,最耗时的流程可能就是Loader对文件的转换操作了,因为要转换的文件数据巨多,而且这些转换操作都只能一个个挨着处理。 HappyPack的核心原理就是把这部分任务分解到多个进程去并行处理,从而减少了总的构建时间。

从前面的使用中可以看出所有需要通过Loader处理的文件都先交给了happypack/loader去处理,收集到了这些文件的处理权后HappyPack就好统一分配了。

每通过new HappyPack()实例化一个HappyPack其实就是告诉HappyPack核心调度器如何通过一系列Loader去转换一类文件,并且可以指定如何给这类转换操作分配子进程。

核心调度器的逻辑代码在主进程中,也就是运行着Webpack的进程中,核心调度器会把一个个任务分配给当前空闲的子进程,子进程处理完毕后把结果发送给核心调度器,它们之间的数据交换是通过进程间通信API实现的。

核心调度器收到来自子进程处理完毕的结果后会通知Webpack该文件处理完毕

2、thread-load替代happyPack

因为happyPack作者目前很少从事JavaScript工作,所以不维护了,推荐使用thread-loader

使用thread-load可以实现将不同编译工作放入不同的工作池中运行。

简述

thread-load放在其他loader的前面即可,那么在它其后的loader就会单独运行在一个工作池中。

在工作池中运行的loader是有限制的:

  • Loaders 不能产生新的文件
  • Loaders 不能使用自定义的loader API(也就是说,通过插件)
  • Loaders 无法获取 webpack 的选项设置。

每个worker都是一个单独的node.js进程,其开销约为600ms。同时跨进程的数据交换也会被限制。

[thread-loader]中建议:请仅在耗时的 loader 上使用

使用方法

webpack.config.js

module.exports = {
  module: {
    rules: [
      {
        test: /\.js$/,
        include: path.resolve('src'),
        use: [
          'thread-loader',
          // your expensive loader (e.g babel-loader)
        ],
      },
    ],
  },
};

选项options配置:

use: [
  {
    loader: 'thread-loader',
    // 具有相同options的loader将共享工作池
    options: {
      // worker的数量,默认为(cpus的数量- 1)或
      // 当require('os').cpus()未定义时,返回1
      workers: 2,

      // 一个worker并发处理的工作数,默认为20
      workerParallelJobs: 50,

      // 额外的node . js参数
      workerNodeArgs: ['--max-old-space-size=1024'],

      // 允许复活一个死亡的工作池\
      // 重生减慢整个编译\
      // 和应该设置为false的开发
      poolRespawn: false,

      // 闲置时定时删除 worker 进程
      // 默认为500(毫秒)
      // 以设置为无穷大, 这样在监视模式(--watch)下可以保持 worker 持续存在
      poolTimeout: 2000,

      // 工作池分配给worker的工作数量。默认为200.
      // 降低这个数值会降低总体的效率,但是会提升工作分布更均一
      poolParallelJobs: 50,

      // 工作池名称。可以修改名称来创建其余选项都一样的池(pool)
      name: 'my-pool',
    },
  },
  // 其他你需要的loader,例如 babel-loader
];