# webpack源码(二)

# 手写plugin

直接拷一下官方的例子

plugin/index.js

// A JavaScript class.
class MyExampleWebpackPlugin {
    // Define `apply` as its prototype method which is supplied with compiler as its argument
    apply(compiler) {
      // Specify the event hook to attach to
      compiler.hooks.emit.tapAsync(
        'MyExampleWebpackPlugin',
        (compilation, callback) => {
          console.log('This is an example plugin!');
          console.log(compilation);
          callback();
        }
      );
    }
  }

module.exports = MyExampleWebpackPlugin;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
  • 创建一个类,就是我们的插件名
  • 里面要有一个函数叫 applyapply 里面一串 compiler.hooks.emit.tapAsync 挺关键的
  • 把插件导出,在 webpack.config.js 中引入,执行打包的时候就会看到可以打印出东西

看一下webpack的源码

找到 node_modules / webpack / lib / Compiler.js

看到 Compiler 这个核心的东西是继承自 tapable 的,包括它的一些方法

const {
	Tapable,
	SyncHook,
	SyncBailHook,
	AsyncParallelHook,
	AsyncSeriesHook
} = require("tapable");
// ...

class Compiler extends Tapable {}
1
2
3
4
5
6
7
8
9
10

那么这个 tapable 是什么呢,我们一探究竟

创建一个demo.js 文件,安装一下 tapable

yarn add tapable --dev
1
const {
    SyncHook
} = require('tapable');

const run = new SyncHook(['complation']);

// 订阅者
run.tap('1', function (complation) {
    console.log('这是一个tap1');
})

run.tap('2', function (complation) {
    console.log('这是一个tap2');
})

run.tap('3', function (complation) {
    console.log('这是一个tap3');
})

run.call('webpack');
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
  • 先引用一下 SyncHook
  • 定义一下 run
  • 调用一下 run 上面的 tap,其实就是个订阅者
  • 发布完订阅之后,最后 call 调用一下,运行可以看到挨个输出了

当然 tapable 中还有很多钩子函数,下面就不举例了,直接贴出来

const {
    SyncHook,
    SyncBailHook,
    SyncWaterfallHook,
    SyncLoopHook,
    AsyncSeriesHook,
    AsyncSeriesBailHook,
    AsyncSeriesWaterfallHook,
    AsyncParallelBailHook,
    AsyncParallelHook
}  = require("tapable");
1
2
3
4
5
6
7
8
9
10
11
  1. SyncHook 同步串行 不关心返回值
  2. SyncBailHook 同步串行 如果返回值不为null 就省略之后的函数
  3. SyncWaterfallHook 同步串行 可以拿到之前函数的返回值
  4. SyncLoopHook 同步串行 返回true循环执行 直到返回undefined
  5. AsyncParallelHook 异步 不关心返回值
  6. AsyncParallelBailHook 异步 如果返回值不为null,就省略之后的函数
  7. AsyncSeriesHook 异步 不关心callback返回值
  8. AsyncSeriesBailHook 异步 如果返回不为null,就继续执行
  9. AsyncSeriesWaterfallHook 异步 上一个异步函数的值会传到下一异步函数

这样在写webpack插件的时候我们就可以通过 compiler 去调用这些钩子函数来达到不同的目的了

# webpack 编译流程

  1. Complier.run() 开始构建
  2. 创建 Complation 挂载资源文件 -> 生产Chunk
  3. 使用 Parser 从Chunk 里面解析依赖,使用 Module 和 Dependency 管理模块之间的相互依赖
  4. 使用 Template 基于 Compilation 的数据生成代码

# webpack5

在使用 webpack4 的时候我们使用异步引入的方式引入文件,打包出来的chunk会是 0 1 2 的命名,这样无法进行持久化缓存,解决办法就通过魔法注释 /import( /* webpackChunkName:"sync" */ './sync')/的方式去改chunk名,但是如果文件很多的话也很不好

在 webpack5 中进行了改进

首先安装一下webpack5

yarn add webpack@next --dev
1

也是创建几个测试用的文件,在index中异步引入两个文件

import('./async2').then((_)=>{
    console.log(_)
})
import('./async').then((_) => {
    console.log(_)
})
console.log('京程一灯');
1
2
3
4
5
6
7

通过 dev 的方式打包

webpack

直接用 路径名 + 文件名 命名

再看看 prod 方式的打包

webpack

打出来的文件是串数字,这是webpack5的新东西,叫chunkids

webpack 还有一些新特性,包括多线程、拆包等等

# webpack为什么慢

loader (string -> ast -> string) * n

使用 speed-measure-webpack-plugin 来监控打包过程中较大的部分

使用 happypack 开启多线程打包

const Happypack = require('happypack');
const os = require('os');
const happypackThreal = Happypack.ThreadPool({size: os.cpus().length});

module.exports = {
    rules:[
        {
            test: /\.js$/,
            loader: 'happypack/loader?id=babeljs'
        }
    ],
    plugins: [
        new Happypack({
            id: 'babeljs',
            loader: ['babel-loader?cacheDirectory=true'],
            threadPool: happypackThreal
        })
    ]
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

使用 cache-loader

使用 thread-loader