Fancy Front End Fancy Front End
  • 开始上手
  • 基础
  • 调度器(Scheduler)
  • 更新器(Updater)
  • 渲染器(Render)
  • 更新周期
  • hooks 原理
  • 总结
  • 📙 React源码漂流记
  • 开始上手
  • 基础
  • reactivity
  • runtime-core
  • runtime-dom
  • Awesome Web
  • Awesome NodeJS
话题
  • 导航
  • Q&A
  • 幻灯片
  • 关于
  • 分类
  • 标签
  • 归档
博客 (opens new window)
GitHub (opens new window)

Jonsam NG

让有意义的事变得有意思,让有意思的事变得有意义
  • 开始上手
  • 基础
  • 调度器(Scheduler)
  • 更新器(Updater)
  • 渲染器(Render)
  • 更新周期
  • hooks 原理
  • 总结
  • 📙 React源码漂流记
  • 开始上手
  • 基础
  • reactivity
  • runtime-core
  • runtime-dom
  • Awesome Web
  • Awesome NodeJS
话题
  • 导航
  • Q&A
  • 幻灯片
  • 关于
  • 分类
  • 标签
  • 归档
博客 (opens new window)
GitHub (opens new window)
  • 开始上手
  • Plan 计划
  • typescript-utility

  • single-spa源码

  • qiankun源码

  • webpack

    • 开始阅读
    • tapable源码

    • init阶段

      • 本章概要
      • init 阶段:compiler
        • 目录
        • webpack
        • createCompiler
        • compiler.run
        • compiler.compile
        • compilation.addEntry
        • compiler.Hook
        • 参考
      • init 阶段:options
    • make阶段

    • 总结

  • axios

  • solid

  • vite源码

  • jquery源码

  • snabbdom

  • am-editor

  • html2canvas

  • express

  • acorn源码

  • immutable.js源码

  • web
  • webpack
  • init阶段
jonsam
2022-04-21
目录

init 阶段:compiler

本节将探讨 webpack 构建流程中 init 阶段的如下工作:

  • options 初始化
  • compiler 初始化
  • 编译环境初始化
  • compiler.run

# 目录

  • 目录
  • webpack
  • createCompiler
  • compiler.run
  • compiler.compile
    • newCompilationParams
    • newCompilation
  • compilation.addEntry
  • compiler.Hook
  • 参考

# webpack

// lib/webpack.js
const webpack = (options, callback) => {
  const create = () => {
      // 校验 options
   if (!asArray(options).every(webpackOptionsSchemaCheck)) {
    getValidateSchema()(webpackOptionsSchema, options);
   }
   let compiler;
   let watch = false;
   let watchOptions;
   //  options 是数组,创建 MultiCompiler
   if (Array.isArray(options)) {
    compiler = createMultiCompiler(
     options,
    );
    // 收集 watch 和 watchOptions
    watch = options.some(options => options.watch);
    watchOptions = options.map(options => options.watchOptions || {});
   } else {
    const webpackOptions = (options);
    // 创建 compiler
    compiler = createCompiler(webpackOptions);
    watch = webpackOptions.watch;
    watchOptions = webpackOptions.watchOptions || {};
   }
   return { compiler, watch, watchOptions };
  };
  if (callback) {
   try {
    const { compiler, watch, watchOptions } = create();
    if (watch) {
      // 配置了 watch,开启 Watching
     compiler.watch(watchOptions, callback);
    } else {
     // 启动 compiler
     compiler.run((err, stats) => {
      // compile 完毕,关闭 compiler
      compiler.close(err2 => {
       callback(err || err2, stats);
      });
     });
    }
    return compiler;
   } catch (err) {
    process.nextTick(() => callback(err));
    return null;
   }
  } else {
   const { compiler, watch } = create();
   //  ......
   return compiler;
  }
};
1
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
42
43
44
45
46
47
48
49
50
51
52
53

下面是精简代码:

const webpack = (options, callback) => {
 // 校验 options
 if (!asArray(options).every(webpackOptionsSchemaCheck)) {
  getValidateSchema()(webpackOptionsSchema, options);
 }

 // 创建 compiler
 const compiler = createCompiler(options);

 // 启动 compiler
 compiler.run((err, stats) => {
  // compile 完毕,关闭 compiler
  compiler.close((err2) => {
   callback(err || err2, stats);
  });
 });

 return compiler;
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

总结一下核心的作用:

  • 校验 options
  • 根据 options 生成并启动 compiler

# createCompiler

// lib/webpack.js
const createCompiler = rawOptions => {
  // options 规整化
 const options = getNormalizedWebpackOptions(rawOptions);
  // merge webpack 基础默认的 options:context、infrastructureLogging
 applyWebpackOptionsBaseDefaults(options);
  // 新建 Compiler
 const compiler = new Compiler(options.context, options);
  // ========== 注册插件 ========== //
  // 注册 NodeEnvironmentPlugin:beforeRun => 初始化 Node 环境 
 new NodeEnvironmentPlugin({
  infrastructureLogging: options.infrastructureLogging
 }).apply(compiler);
  // 注册 options.plugins
 if (Array.isArray(options.plugins)) {
  for (const plugin of options.plugins) {
   if (typeof plugin === "function") {
    plugin.call(compiler, compiler);
   } else {
    plugin.apply(compiler);
   }
  }
 }
  // merge webpack 默认 options: context、target、devtool等
 applyWebpackOptionsDefaults(options);
  // 触发 Hook.environment => call
 compiler.hooks.environment.call();
  // 触发 Hook.afterEnvironment => call
 compiler.hooks.afterEnvironment.call();
  // WebpackOptionsApply 根据 options 注册内置插件
 new WebpackOptionsApply().process(options, compiler);
  // 触发 Hook.initialize => call
 compiler.hooks.initialize.call();
  // 返回 compiler
 return compiler;
};
1
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

总结核心作用如下:

  • options 规整化、merge 默认的 options
  • 创建 compiler 并返回
  • 注册插件 NodeEnvironmentPlugin、options.plugins、基于 options 的内置插件
  • 依次触发 Hook.environment、Hook.afterEnvironment、Hook.initialize

# compiler.run

run(callback) {
  // 如果 compiler 已经在运行则报错
  if (this.running) {
    return callback(new ConcurrentCompilationError());
  }

  // 仅在出现 err 时执行
  const finalCallback = (err, stats) => {
    // 将 compiler 标记为 idle
    this.idle = true;
    // 触发 CacheHook.beginIdle => call
    this.cache.beginIdle();
    this.idle = true;
    // 标记 compiler running 为 false
    this.running = false;
    if (err) {
      // 触发 Hook.failed => call
      this.hooks.failed.call(err);
    }
    if (callback !== undefined) callback(err, stats);
    // 触发 Hook.afterDone => call
    this.hooks.afterDone.call(stats);
  };

  // 标记 running 为 true
  this.running = true;

  const run = () => {
    // 触发 Hook.beforeRun: callAsync 
    this.hooks.beforeRun.callAsync(this, err => {
      if (err) return finalCallback(err);
      // 触发 Hook.run: callAsync
      this.hooks.run.callAsync(this, err => {
        if (err) return finalCallback(err);
        // 尝试从 records 恢复,反序列化
        this.readRecords(err => {
          if (err) return finalCallback(err);
          // 开始 compile
          this.compile(onCompiled);
        });
      });
    });
  };
  // 初始为 false
  if (this.idle) {
    // 触发 CacheHook.endIdle
    this.cache.endIdle(err => {
      if (err) return finalCallback(err);
      
      this.idle = false;
      run();
    });
  } else {
    run();
  }
}
1
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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56

总结一下核心作用:

  • 如果 compiler 为 idle 状态,触发 CacheHook.endIdle,并且执行 run。否则直接执行 run。
  • compiler.run 依次触发 Hook.beforeRun、Hook.run。尝试从序列化的 records(compiler 的状态) 中恢复,并且调用 compile,compile 完成后执行 onCompiled。标记 compiler 为 running。
  • 发生错误时执行 finalCallback,调用上层 callback,将 compile 标记为 idle,触发 CacheHook.beginIdle、Hook.failed 和 Hook.afterDone。去除 compiler 的 running 标记。

# compiler.compile

// lib/Compiler.js
compile(callback) {
    // 使用缓存的 compilationParams
  const params = this.newCompilationParams();
    // 触发 Hook.beforeCompile => callAsync
  this.hooks.beforeCompile.callAsync(params, err => {
   if (err) return callback(err);
      // 触发 Hook.compile => call
   this.hooks.compile.call(params);
      // 使用缓存的 compilation 对象
   const compilation = this.newCompilation(params);

   // 触发 Hook.make => callAsync
   this.hooks.make.callAsync(compilation, err => {
    if (err) return callback(err);
    
    // 触发 Hook.finishMake => callAsync
    this.hooks.finishMake.callAsync(compilation, err => {
     if (err) return callback(err);

     process.nextTick(() => {
      // compilation.finish
      compilation.finish(err => {
       if (err) return callback(err);

       // compilation.seal
       compilation.seal(err => {
        if (err) return callback(err);

        // 触发 Hook.afterCompile => callAsync
        this.hooks.afterCompile.callAsync(compilation, err => {
         if (err) return callback(err);

         return callback(null, compilation);
        });
       });
      });
     });
    });
   });
  });
}
1
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
42

compile 的代码高度抽象化,具体的业务逻辑都封装到相应的插件中了。

总结一下核心作用:

  • 依次触发 Hook.beforeCompile、Hook.compile、Hook.make、Hook.finishMake、Hook.afterCompile。
  • compilation:创建 compilation 对象、触发 Hook.make、Hook.finishMake、执行 compilation.finish、compilation.seal、触发 Hook.afterCompile。

# newCompilationParams

这个函数或创建 normalModuleFactory 和 contextModuleFactory。

newCompilationParams() {
const params = {
  // new NormalModuleFactory()
  normalModuleFactory: this.createNormalModuleFactory(),
  // new ContextModuleFactory()
  contextModuleFactory: this.createContextModuleFactory()
};
return params;
}
1
2
3
4
5
6
7
8
9

# newCompilation

// lib/Compiler.js
createCompilation(params) {
  // 注意:这里并不会频繁实例化类,而是在 _cleanupLastCompilation 中将 _lastCompilation 清理之后复用
 this._cleanupLastCompilation();
 return (this._lastCompilation = new Compilation(this, params));
}
newCompilation(params) {
  // 创建 compilation 实例
 const compilation = this.createCompilation(params);
 compilation.name = this.name;
 compilation.records = this.records;
  // 触发 Hook.thisCompilation => call
 this.hooks.thisCompilation.call(compilation, params);
  // 触发 Hook.compilation => call
 this.hooks.compilation.call(compilation, params);
 return compilation;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

核心作用如下:

  • 创建或者复用 compilation。
  • 依次触发 Hook.thisCompilation、Hook.compilation。

# compilation.addEntry

在 createCompiler 中我们了解到 new WebpackOptionsApply().process(options, compiler) 会根据 options 来注册各种不同的插件。

// lib/WebpackOptionsApply.js
new EntryOptionPlugin().apply(compiler);
// 触发 Hook.entryOption => call
compiler.hooks.entryOption.call(options.context, options.entry);
1
2
3
4

我们来探讨下这里注册的 EntryOptionPlugin 插件:

// lib/EntryOptionPlugin.js
class EntryOptionPlugin {
 // ......
 apply(compiler) {
  // 注册 Callback: Hook.entryOption, Hook.entryOption 触发时,将执行 applyEntryOption
  // Hook.entryOption 在此插件注册完毕后就会被触发
  compiler.hooks.entryOption.tap("EntryOptionPlugin", (context, entry) => {
   EntryOptionPlugin.applyEntryOption(compiler, context, entry);
   return true;
  });
 }

 static applyEntryOption(compiler, context, entry) {
  // entry 是函数,注册 DynamicEntryPlugin
  if (typeof entry === "function") {
   const DynamicEntryPlugin = require("./DynamicEntryPlugin");
   new DynamicEntryPlugin(context, entry).apply(compiler);
  } else {
   // entry 是 object,注册 EntryPlugin
   const EntryPlugin = require("./EntryPlugin");
   // 循环 entries 将 entry 生成 options
   //  entry: {
   //   b2: {
   //     dependOn: 'a2',
   //     import: './src/app.js',
   //   },
   // },
   for (const name of Object.keys(entry)) {
    const desc = entry[name];
    const options = EntryOptionPlugin.entryDescriptionToOptions(
     compiler,
     name,
     desc
    );
    // 对于 entry 中每一个入口路径,注册插件 EntryPlugin
    for (const entry of desc.import) {
     new EntryPlugin(context, entry, options).apply(compiler);
    }
   }
  }
 }
}
1
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
42

总结一下:

  • createCompiler 中注册 WebpackOptionsApply 插件时,会注册 EntryOptionPlugin 插件,在随后的 Hook.entryOption 调用时,为每个 Entry 的 import 都注册了插件 EntryPlugin 。
  • 对每个 Entry.import 所注册的 EntryPlugin 将会初始化打包入口。

接着来探讨下 EntryPlugin 插件:

class EntryPlugin {
 apply(compiler) {
  // 注册 Callback:Hook.compilation
  // Hook.compilation 在  compiler.compile 函数中触发
  compiler.hooks.compilation.tap(
   "EntryPlugin",
   (compilation, { normalModuleFactory }) => {
    // dependencyFactories 记录某种 Dependency 应用哪种 ModuleFactory
    // 记录 EntryDependency 使用 normalModuleFactory
    compilation.dependencyFactories.set(
     EntryDependency,
     normalModuleFactory
    );
   }
  );

  const { entry, options, context } = this;
  // 静态方法创建 EntryDependency
  const dep = EntryPlugin.createDependency(entry, options);
  // 注册 Callback: Hook.make => tapAsync
  // Hook.make 在  compiler.compile 函数中触发
  compiler.hooks.make.tapAsync("EntryPlugin", (compilation, callback) => {
   // compilation.addEntry 添加 Entry,目的是创建 EntryDependency
   compilation.addEntry(context, dep, options, (err) => {
    callback(err);
   });
  });
 }

 static createDependency(entry, options) {
  // 创建 EntryDependency 对象
  const dep = new EntryDependency(entry);
  // TODO webpack 6 remove string option
  dep.loc = { name: typeof options === "object" ? options.name : options };
  return dep;
 }
}
1
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

总结一下:

  • 在 Hook.compilation 触发时记录 EntryDependency 使用 normalModuleFactory,创建 EntryDependency,并在 Hook.make 触发时执行 compilation.addEntry 。

# compiler.Hook

compiler 有如下的 Hook。

this.hooks = Object.freeze({
  /** @type {SyncHook<[]>} */
  initialize: new SyncHook([]),

  /** @type {SyncBailHook<[Compilation], boolean>} */
  shouldEmit: new SyncBailHook(["compilation"]),
  /** @type {AsyncSeriesHook<[Stats]>} */
  done: new AsyncSeriesHook(["stats"]),
  /** @type {SyncHook<[Stats]>} */
  afterDone: new SyncHook(["stats"]),
  /** @type {AsyncSeriesHook<[]>} */
  additionalPass: new AsyncSeriesHook([]),
  /** @type {AsyncSeriesHook<[Compiler]>} */
  beforeRun: new AsyncSeriesHook(["compiler"]),
  /** @type {AsyncSeriesHook<[Compiler]>} */
  run: new AsyncSeriesHook(["compiler"]),
  /** @type {AsyncSeriesHook<[Compilation]>} */
  emit: new AsyncSeriesHook(["compilation"]),
  /** @type {AsyncSeriesHook<[string, AssetEmittedInfo]>} */
  assetEmitted: new AsyncSeriesHook(["file", "info"]),
  /** @type {AsyncSeriesHook<[Compilation]>} */
  afterEmit: new AsyncSeriesHook(["compilation"]),

  /** @type {SyncHook<[Compilation, CompilationParams]>} */
  thisCompilation: new SyncHook(["compilation", "params"]),
  /** @type {SyncHook<[Compilation, CompilationParams]>} */
  compilation: new SyncHook(["compilation", "params"]),
  /** @type {SyncHook<[NormalModuleFactory]>} */
  normalModuleFactory: new SyncHook(["normalModuleFactory"]),
  /** @type {SyncHook<[ContextModuleFactory]>}  */
  contextModuleFactory: new SyncHook(["contextModuleFactory"]),

  /** @type {AsyncSeriesHook<[CompilationParams]>} */
  beforeCompile: new AsyncSeriesHook(["params"]),
  /** @type {SyncHook<[CompilationParams]>} */
  compile: new SyncHook(["params"]),
  /** @type {AsyncParallelHook<[Compilation]>} */
  make: new AsyncParallelHook(["compilation"]),
  /** @type {AsyncParallelHook<[Compilation]>} */
  finishMake: new AsyncSeriesHook(["compilation"]),
  /** @type {AsyncSeriesHook<[Compilation]>} */
  afterCompile: new AsyncSeriesHook(["compilation"]),

  /** @type {AsyncSeriesHook<[]>} */
  readRecords: new AsyncSeriesHook([]),
  /** @type {AsyncSeriesHook<[]>} */
  emitRecords: new AsyncSeriesHook([]),

  /** @type {AsyncSeriesHook<[Compiler]>} */
  watchRun: new AsyncSeriesHook(["compiler"]),
  /** @type {SyncHook<[Error]>} */
  failed: new SyncHook(["error"]),
  /** @type {SyncHook<[string | null, number]>} */
  invalid: new SyncHook(["filename", "changeTime"]),
  /** @type {SyncHook<[]>} */
  watchClose: new SyncHook([]),
  /** @type {AsyncSeriesHook<[]>} */
  shutdown: new AsyncSeriesHook([]),

  /** @type {SyncBailHook<[string, string, any[]], true>} */
  infrastructureLog: new SyncBailHook(["origin", "type", "args"]),

  /** @type {SyncHook<[]>} */
  environment: new SyncHook([]),
  /** @type {SyncHook<[]>} */
  afterEnvironment: new SyncHook([]),
  /** @type {SyncHook<[Compiler]>} */
  afterPlugins: new SyncHook(["compiler"]),
  /** @type {SyncHook<[Compiler]>} */
  afterResolvers: new SyncHook(["compiler"]),
  /** @type {SyncBailHook<[string, Entry], boolean>} */
  entryOption: new SyncBailHook(["context", "entry"])
});
1
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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73

注意:Object.freeze 只冻结顶层,Callbacks 并不会被冻结。

# 参考

  • suguru03/neo-async: Neo-Async is thought to be used as a drop-in replacement for Async, it almost fully covers its functionality and runs faster (opens new window)
  • compiler 钩子 | webpack 中文文档 (opens new window)
编辑 (opens new window)
上次更新: 2022/09/06, 14:25:16
本章概要
init 阶段:options

← 本章概要 init 阶段:options→

最近更新
01
渲染原理之组件结构与 JSX 编译
09-07
02
计划跟踪
09-06
03
开始上手
09-06
更多文章>
Theme by Vdoing | Copyright © 2022-2022 Fancy Front End | Made by Jonsam by ❤
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式