HappyPack
能让Webpack在同一时刻处理多个任务,发挥多核CPU电脑的功能以提升构建速度。HappyPack
将任务分解给多个子进程去并发执行,子进程处理完后再将结果发送给主进程。
需要注意的是:由于javascript是单线程模型,所以要想发挥多核CPU的功能,就只能通过多进程实现,而无法通过多线程实现。
HappyPack
是让webpack对loader的执行过程,从单一进程形式扩展为多进程模式,也就是将任务分解给多个子进程去并发的执行,子进程处理完后再把结果发送给主进程。从而加速代码构建与DLL
动态链接库结合来使用更佳。
happypack
是webpack
的一个插件,目的是通过多进程模型,来加速代码构建。
配置属性:
- mpDir: 存放打包缓存文件的位置;
- cache: 是否开启缓存,目前缓存如果开启,(注: 会以数量级的差异来缩短构建时间,很方便日常开发);
- cachePath: 存放缓存文件映射配置的位置;
- verbose: 是否输出过程日志;
- loaders: 因为配置中文件的处理 loader 都指向了 happypack 提供的 loadr ,这里配置的对应文件实际需要运行的 loader。
由于有大量文件需要解析和处理,所以构建是文件读写和计算密集型的操作,特别是当文件数量变多后,Webpack
构建慢的问题会显得更为严重。运行在Node.js
之上的Webpack
是单线程模型的,也就是说Webpack
需要一个一个地处理任务,不能同时处理多个任务。
文件读写和计算操作是无法避免的,那能不能让Webpack
在同一时刻处理多个任务,发挥多核CPU
电脑的功能,以提升构建速度呢?
HappyPack
就能让Webpack
做到这一点,它将任务分解给多个子进程去并发执行,子进程处理完后再将结果发送给主进程。
需要注意:由于
JavaScript
是单线程模型,所以要想发挥多核CPU
的功能,就只能通过多进程实现,而无法通过多线程实现。
使用HappyPack
对于分解任务和管理线程的事情,HappyPack都会帮我们做好,我们所需要做的只是接入HappyPack。接入HappyPack的相关代码如下:
const path = require('path');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
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`,
}),
],
};
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
在以上代码中有以下两项重要的修改:
- 在
Loader
配置中,对所有文件的处理都交给了happypack/loader
,使用紧跟其后的querystring?id=babel
去告诉happypack/loader
选择哪个HappyPack
实例处理文件; - 在
Plugin
配置中新增了两个HappyPack
实例,分别用于告诉happypack/loader
去如何处理.js和.css
文件。选项中的id
属性的值和上面querystring
中的?id=babel
对应,选项中的loaders
属性和Loader
配置中的一样。
在实例化HappyPack
插件时,除了可以传入id和loaders
两个参数,HappyPack
还支持如下参数:
- threads: 代表开启几个子进程去处理这一类型的文件,默认是3个,必须是整数;
- verbose: 是否允许
HappyPack
输出日志,默认是true
; - threadPool: 代表共享进程池,即多个
HappyPack
实例都使用同一个共享进程池中的子进程去处理任务,以防止资源占用过多,相关代码如下:
const HappyPack = require('happypack');
// 构造出共享进程池,在进程池中包含5个子进程
const happyThreadPool = HappyPack.ThreadPool({ size: 5 });
module.exports = {
plugins: [
new HappyPack({
// 用唯一的标识符 id 来代表当前的HappyPack用来处理一类特定的文件
id: 'babel',
// 如何处理 .js 文件,用法和 Loader 配置中一样
loaders: ['babel-loader?cacheDirectory'],
// 使用共享进程池中的子进程去处理任务
threadPool: happyThreadPool,
}),
new HappyPack({
id: 'css',
// 如何处理 .css 文件,用法和 Loader 配置中一样
loaders: ['css-loader'],
// 使用共享进程池中的子进程去处理任务
threadPool: happyThreadPool,
}),
new ExtractTextPlugin({
filename: `[name].css`,
}),
],
};
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
接入HappyPack
后,需要给项目安装新的依赖:
npm i -D happypack
安装成功后重新执行构建,就会看到由HappyPack
输出的以下日志:
说明
HappyPack
配置生效了,并且可以得知HappyPack
分别启动了3个进程去并行处理任务。
HappyPack 原理
在整个Webpack
构建流程中,最耗时的流程可能就是Loader
对文件的转换操作了,因为要转换的文件数据量巨大,而且这些转换操作都只能一个一个地处理。HappyPack
的核心原理就是:将这部分任务分解到多个进程中去并行处理,从而减少了总的构建时间。
从前面的使用中可以看出,所有需要通过Loader
处理的文件都先交给了 happypack/loader
去处理,在收集到了这些文件的处理权后,HappyPack
就可以统一分配了。
每通过new HappyPack()
实例化一个HappyPack
,其实就是告诉HappyPack
核心调度器如何通过一系列Loader
去转换一类文件,并且可以指定如何为这类转换操作分配子进程。
核心调度器的逻辑代码在主进程中,也就是运行着Webpack
的进程中,核心调度器会将一个个任务分配给当前空闲的子进程,子进程处理完毕后将结果发送给核心调度器,它们之间的数据交换是通过进程间的通信API
实现的。
核心调度器收到来自子进程处理完毕的结果后,会通知Webpack该文件已处理完毕。