系统环境

macOS Sierra v10.12.3
Node.js v6.9.2
npm v4.1.1
yarn v0.18.1
webpack v1.14.0

相关链接

webpack 源码解读:命令行输入webpack的时候都发生了什么?
细说 webpack 之流程篇
webpack源码分析(一)— Tapable插件架构
Tapable中文文档
Webpack 源码(一)—— Tapable 和 事件流
Webpack 源码(二)—— 如何阅读源码

源码解读

1. webpack compiler object 何时被扩充?

1
2
3
4
5
6
7
8
9
10
11
// ./node_modules/webpack/lib/webpack.js(line#18)
// 通过 调用new WebpackOptionsDefaulter() 实例的 process方法

// 调用前 options:
‌{"entry":"./entry.js","output":{"filename":"./bundle.js"},"context":"<my project>/webpack@1"}

// 调用
new WebpackOptionsDefaulter().process(options);

// 调用后 options:
{"entry":"./entry.js","output":{"filename":"./bundle.js","libraryTarget":"var","path":"","sourceMapFilename":"[file].map[query]","hotUpdateChunkFilename":"[id].[hash].hot-update.js","hotUpdateMainFilename":"[hash].hot-update.json","crossOriginLoading":false,"hashFunction":"md5","hashDigest":"hex","hashDigestLength":20,"sourcePrefix":"\t","devtoolLineToLine":false},"context":"<my project>/webpack@1","debug":false,"devtool":false,"cache":true,"target":"web","node":{"console":false,"process":true,"global":true,"setImmediate":true,"__filename":"mock","__dirname":"mock"},"resolve":{"fastUnsafe":[],"alias":{},"packageAlias":"browser","modulesDirectories":["web_modules","node_modules"],"packageMains":["webpack","browser","web","browserify",["jam","main"],"main"],"extensions":["",".webpack.js",".web.js",".js",".json"]},"resolveLoader":{"fastUnsafe":[],"alias":{},"modulesDirectories":["web_loaders","web_modules","node_loaders","node_modules"],"packageMains":["webpackLoader","webLoader","loader","main"],"extensions":["",".webpack-loader.js",".web-loader.js",".loader.js",".js"],"moduleTemplates":["*-webpack-loader","*-web-loader","*-loader","*"]},"module":{"unknownContextRequest":".","unknownContextRecursive":true,"unknownContextRegExp":{},"unknownContextCritical":true,"exprContextRequest":".","exprContextRegExp":{},"exprContextRecursive":true,"exprContextCritical":true,"wrappedContextRegExp":{},"wrappedContextRecursive":true,"wrappedContextCritical":false},"optimize":{"occurenceOrderPreferEntry":true}}

2. Compiler 如何继承 Tapable?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// ./node_modules/webpack/bin/webpack.js(line#148)
var webpack = require("../lib/webpack.js");

// ./node_modules/webpack/lib/webpack.js(line#5)
// 这一步 require("./Compiler"),会执行了以下两行代码,即把 Compiler.prototype 对象指向了 Tapable.prototype 的复制对象。
var Compiler = require("./Compiler");

// ./node_modules/webpack/lib/Compiler.js(line#154-155)
// 这一步即完成了从 Tapable.prototype 原型对象到 Compiler.prototype 原型对象的复制。注意 Object.create(obj) 所创建出来的是一个新的复制对象。
Compiler.prototype = Object.create(Tapable.prototype);
Compiler.prototype.constructor = Compiler;

// ./node_modules/webpack/lib/webpack.js(line#20)
// 这一步实例化 Compiler,会执行以下一行代码,即把「Tapable 构造函数上的方法和属性」全部导出到 Compiler 构造函数所构造出来的对象上。这样在实例化 Compiler 后,compiler 对象上也就有了 Tapable 的方法和属性了。
compiler = new Compiler();

// ./node_modules/webpack/lib/Compiler.js(line#129-130)
function Compiler() {
Tapable.call(this);
}

详细可以通过以下 demo 来理解:

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
// 代码来自 [MDN](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Function/call)
function Product(name, price) {
this.name = name;
this.price = price;

if (price < 0) {
throw RangeError('Cannot create product ' +
this.name + ' with a negative price');
}
}
Product.prototype.showPrice = function () {
console.log(this.price);
};

function Food(name, price) {
Product.call(this, name, price);
this.category = 'food';
}

function Toy(name, price) {
Product.call(this, name, price);
this.category = 'toy';
}

Food.prototype = Object.create(Product.prototype);
Food.prototype.constructor = Food;

var cheese = new Food('feta', 5);
var fun = new Toy('robot', 40);

console.log(cheese);
console.log(fun);

inherit

3. Tapable 提供了哪些方法和属性?

经常会看到 compiler.applyPlugins 这样,compiler 实例调用原型链上 Tapable.prototype 所提供的方法。
注意,这里 Tapable 提供了一个 apply 方法,可能会被具体的实例上的 apply 方法所覆盖。

1
2
3
4
5
6
7
8
9
10
11
12
13
function Tapable() {
this._plugins = {};
}
Tapable.prototype.applyPlugins
Tapable.prototype.applyPluginsWaterfall
Tapable.prototype.applyPluginsBailResult
Tapable.prototype.applyPluginsAsyncSeries
Tapable.prototype.applyPluginsAsyncWaterfall
Tapable.prototype.applyPluginsParallel
Tapable.prototype.applyPluginsParallelBailResult
Tapable.prototype.restartApplyPlugins
Tapable.prototype.plugin
Tapable.prototype.apply

4. 都有哪些继承?

  • Compiler 继承 Tapable
  • MultiCompiler 继承 Tapable
  • Compilation 继承 Tapable
  • Parser 继承 Tapable
  • Resolver 继承 Tapable
  • WebpackOptionsApply 继承 OptionsApply
  • WebpackOptionsDefaulter 继承 OptionsDefaulter

5. webpack 对象何时暴露常用方法?

1
2
3
4
5
6
// ./node_modules/webpack/lib/webpack.js(line#50-54)
webpack.WebpackOptionsDefaulter = WebpackOptionsDefaulter;
webpack.WebpackOptionsApply = WebpackOptionsApply;
webpack.Compiler = Compiler;
webpack.MultiCompiler = MultiCompiler;
webpack.NodeEnvironmentPlugin = NodeEnvironmentPlugin;

6. webpack 是何时暴露插件?

可以看到 ./node_modules/webpack/lib/webpack.js 所导出的 exports 对象被扩充了。
当你从外部 require ./node_modules/webpack/lib/webpack.js 时,例如 ./node_modules/webpack/lib/webpack.js(line#148) 的 var webpack = require(“../lib/webpack.js”); 此时 webpack 这个变量就获取到了导出对象,而当你访问导出对象的 DefinePluginAggressiveMergingPluginLabeledModulesPlugin 等方法时,会触发 getter,并直接通过 require('./DefinePlugin')require('./optimize/AggressiveMergingPlugin')require('./dependencies/LabeledModulesPlugin') 等得到对应模块。

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
// ./node_modules/webpack/lib/webpack.js(line#56-66)
function exportPlugins(exports, path, plugins) {
plugins.forEach(function(name) {
Object.defineProperty(exports, name, {
configurable: false,
enumerable: true,
get: function() {
return require(path + "/" + name);
}
});
});
}

// ./node_modules/webpack/lib/webpack.js(line#68-133)
exportPlugins(exports, ".", [
"DefinePlugin",
"NormalModuleReplacementPlugin",
"ContextReplacementPlugin",
"IgnorePlugin",
"WatchIgnorePlugin",
"BannerPlugin",
"PrefetchPlugin",
"AutomaticPrefetchPlugin",
"ProvidePlugin",
"HotModuleReplacementPlugin",
"ResolverPlugin",
"SourceMapDevToolPlugin",
"EvalSourceMapDevToolPlugin",
"EvalDevToolModulePlugin",
"CachePlugin",
"ExtendedAPIPlugin",
"ExternalsPlugin",
"JsonpTemplatePlugin",
"LibraryTemplatePlugin",
"LoaderTargetPlugin",
"MemoryOutputFileSystem",
"ProgressPlugin",
"SetVarMainTemplatePlugin",
"UmdMainTemplatePlugin",
"NoErrorsPlugin",
"NewWatchingPlugin",
"OldWatchingPlugin",
"EnvironmentPlugin",
"DllPlugin",
"DllReferencePlugin",
"NamedModulesPlugin"
]);
exportPlugins(exports.optimize = {}, "./optimize", [
"AggressiveMergingPlugin",
"CommonsChunkPlugin",
"DedupePlugin",
"LimitChunkCountPlugin",
"MinChunkSizePlugin",
"OccurenceOrderPlugin",
"OccurrenceOrderPlugin",
"UglifyJsPlugin"
]);
exportPlugins(exports.dependencies = {}, "./dependencies", [
"LabeledModulesPlugin"
]);

7.何时 require webpack.config.js

1
2
// convert-argv.js
options = require(configPath);

8.为解析出来的 webpack 配置对象添加 context

1
2
3
4
5
6
7
8
// convert-argv.js
processOptions(options);

function processOptions(options) {
if(!options.context) {
options.context = process.cwd();
}
}