rwson

rwson

一个前端开发

webpack源码阅读

在前端工程化越来越普及的今天,我们几乎每个项目都需要用到构建工具,从一开始的grunt,到gulp,再到现在的webpack。 我们在使用webpack时,可以通过配置一些命令行参数来让webpack完成一些编译打包的任务,那么当我们执行webpack这个命令的时候,webpack究竟做了哪些事情? 我们一起来读读webpack的相关源码。

注:本文阅读的版本为webpack1.15.0,从入口开始分析再拿到我们的命令之后执行的的流程,所以有些个人认为不重要的可能会省略。

通过package.json中的bin的指向可以知道首先会走到./bin/webpack.js这个文件:

#!/usr/bin/env node

//	引入nodejs path模块
var path = require("path");

// require.resolve获取/bin/webpack.js的绝对路径(从项目根目录下的node_modules里面找)
try {
	var localWebpack = require.resolve(path.join(process.cwd(), "node_modules", "webpack", "bin", "webpack.js"));
	if(__filename !== localWebpack) {
		return require(localWebpack);
	}
} catch(e) {}

var optimist = require("optimist")
	.usage("webpack " + require("../package.json").version + "\n" +
		"Usage: https://webpack.github.io/docs/cli.html");

require("./config-optimist")(optimist);

//	对在命令行传入的参数进行解析
//	--colors、--json、 ...
optimist
	.boolean("json").alias("json", "j").describe("json")
	.boolean("colors").alias("colors", "c").describe("colors")
	//	其他参数解析, 略;

//	获取解析后的参数并转换格式
var argv = optimist.argv;
var options = require("./convert-argv")(optimist, argv);

//	如果有符合argv里面的参数
//	就执行相关的回调函数
function ifArg(name, fn, init) {
	if(Array.isArray(argv[name])) {
		if(init) init();
		argv[name].forEach(fn);
	} else if(typeof argv[name] !== "undefined") {
		if(init) init();
		fn(argv[name], -1);
	}
}

/**
 *  处理输出相关(output)的配置参数,并执行编译函数
 *  @param   {[type]}  options  [description]
 *  @return  {[type]}           [description]
 */
function processOptions(options) {
	//	如果options下面的then是个函数(options是个Promise示例)
	//	把processOptions作为参数传到options.then中
	//	并且中断下面代码的执行
	if(typeof options.then === "function") {
		options.then(processOptions).catch(function(err) {
			console.error(err.stack || err);
			process.exit();
		});
		return;
	}

	//	如果传入的options是一个数组, 取数组的第一项
	var firstOptions = Array.isArray(options) ? options[0] : options;

	//	输出的配置
	var outputOptions = Object.create(options.stats || firstOptions.stats || {});

	//	如果outputOptions中本身没有context
	//	就把配置项中的context给它
	if(typeof outputOptions.context === "undefined")
		outputOptions.context = firstOptions.context;

	//	各种命令行参数处理
	ifArg("json", function(bool) {
		if(bool)
			outputOptions.json = bool;
	});
	//	还有很多ifArg,略

	//	引入webpack入口文件
	var webpack = require("../lib/webpack.js");

	//	错误堆栈追踪上限
	Error.stackTraceLimit = 30;
	var lastHash = null;

	//	执行编译
	var compiler = webpack(options);

	function compilerCallback(err, stats) {
      	//	命令行参数中没有带有--watch
		if(!options.watch) {
			compiler.purgeInputFileSystem();
		}
      
      	//	出错
		if(err) {
			lastHash = null;
			console.error(err.stack || err);
			if(err.details) console.error(err.details);
			if(!options.watch) {
				process.on("exit", function() {
					process.exit(1);
				});
			}
			return;
		}
		if(outputOptions.json) {
			process.stdout.write(JSON.stringify(stats.toJson(outputOptions), null, 2) + "\n");
		} else if(stats.hash !== lastHash) {
			lastHash = stats.hash;
			process.stdout.write(stats.toString(outputOptions) + "\n");
		}
	}
  
	//	命令行参数中带有--watch
	if(options.watch) {
		var primaryOptions = !Array.isArray(options) ? options : options[0];
		var watchOptions = primaryOptions.watchOptions || primaryOptions.watch || {};
		if(watchOptions.stdin) {
			process.stdin.on('end', function() {
				process.exit(0); // eslint-disable-line
			});
			process.stdin.resume();
		}
      	
      	//	调用compiler下面的watch来监听文件变化
		compiler.watch(watchOptions, compilerCallback);
	} else
		compiler.run(compilerCallback);

}

//	执行processOptions并且传入解析出来的参数
processOptions(options);

在上面定义的processOptions方法中可以看到引入了../lib/webpack.js这个文件,下面我们一起看看../lib/webpack.js这个文件。

../lib/webpack.js中,webpack作为入口函数,我们一起看下它的实现:

/**
 *  webpack入口
 *  @param   {Object}    options   webpack.config.js中module.exports出来的对象
 *  @param   {Function}  callback  回调函数
 *  @return  {Object}              webpack Compiler Object/multi compiler object Collection
 */
function webpack(options, callback) {
	var compiler;
	//	options是一个数组时, 实例化一个MultiCompiler, 去map这个数组, 针对每个配置项返回一个webpack实例, 最后用compiler接收
	if(Array.isArray(options)) {
		compiler = new MultiCompiler(options.map(function(options) {
			return webpack(options);
		}));
	} else if(typeof options === "object") {
		
		// 实例化一个WebpackOptionsDefaulter来处理默认配置项
		new WebpackOptionsDefaulter().process(options);

		//	实例化一个Compiler, Compiler继承自Tapable(https://doc.webpack-china.org/api/plugins/tapable/)
		//	Compiler实例化后会继承到apply、plugin等调用和绑定插件的方法
		compiler = new Compiler();

		//	配置项
		compiler.options = options;

		//	对传入的options进行逐个编译,并且把options作为返回值重赋值到compiler.options
		compiler.options = new WebpackOptionsApply().process(options, compiler);

		//	应用node环境插件(给compiler设置resolvers、watchFileSystem、outputFileSystem)
		new NodeEnvironmentPlugin().apply(compiler);

		//	触发environment和after-environment事件
		compiler.applyPlugins("environment");
		compiler.applyPlugins("after-environment");
	} else {
		//	options既不是数组也不是对象时, 抛出异常
		throw new Error("Invalid argument: options");
	}

	//	存在回调函数
	if(callback) {
		if(typeof callback !== "function") throw new Error("Invalid argument: callback");

		//	命令行里面带watch, 或者options是个数组(并且数组中有一项的watch为true)
		if(options.watch === true || (Array.isArray(options) &&
				options.some(function(o) {
					return o.watch;
				}))) {

			//	根据参数类型拿到watchOptions
			var watchOptions = (!Array.isArray(options) ? options : options[0]).watchOptions || {};

			//	watchDelay属性应该被options.watchOptions.aggregateTimeout代替
			var watchDelay = (!Array.isArray(options) ? options : options[0]).watchDelay;
			if(watchDelay) {
				console.warn("options.watchDelay is deprecated: Use 'options.watchOptions.aggregateTimeout' instead");
				watchOptions.aggregateTimeout = watchDelay;
			}

			//	实例化并返回一个watch对象
			return compiler.watch(watchOptions, callback);
		}
		compiler.run(callback);
	}
	return compiler;
}

/**
 *  在对象下用Object.defineProperty的方法添加插件
 *  @param   {Object}  exports  被添加的对象
 *  @param   {String}  path     在get中返回require
 *  @param   {Array}   plugins  插件数组
 */
function exportPlugins(exports, path, plugins) {
	plugins.forEach(function(name) {
		Object.defineProperty(exports, name, {
			configurable: false,
			enumerable: true,
			get: function() {
				return require(path + "/" + name);
			}
		});
	});
}

在这个文件最后,通过上面的exportPlugins webpack下面挂载了一些插件做属性

//	暴露插件到webpack下面
//	new webpack.DefinePlugin()
//	...
exportPlugins(exports, ".", [
	"DefinePlugin",
	//	...
]);
exportPlugins(exports.optimize = {}, "./optimize", [
	"AggressiveMergingPlugin",
	"CommonsChunkPlugin",
	"DedupePlugin",
	"LimitChunkCountPlugin",
	"MinChunkSizePlugin",
	"OccurenceOrderPlugin",
	"OccurrenceOrderPlugin",
	"UglifyJsPlugin"
]);
exportPlugins(exports.dependencies = {}, "./dependencies", [
	"LabeledModulesPlugin"
]);

刚才在对webpack进行标注的时候,实例化了WebpackOptionsApply 对象并且调用了process 方法,这个方法将会对我们传进去的optionscompiler编译对象进行逐个编译,下面我们一起看下WebpackOptionsApply这个模块的实现:

function WebpackOptionsApply() {
	//	OptionsApply构造函数的继承
	OptionsApply.call(this);
}
module.exports = WebpackOptionsApply;

//	继承OptionsApply的原型
WebpackOptionsApply.prototype = Object.create(OptionsApply.prototype);

/**
 *  对我们传进去的options和compiler编译对象进行逐个编译
 *  @param   {[type]}  options   [description]
 *  @param   {[type]}  compiler  [description]
 *  @return  {[type]}            [description]
 */
WebpackOptionsApply.prototype.process = function(options, compiler) {
	//	context处理
	compiler.context = options.context;

	//	plugins的处理, 调用compiler.apply.apply
	if(options.plugins && Array.isArray(options.plugins)) {
		compiler.apply.apply(compiler, options.plugins);
	}

	//	缓存输入输出的路径
	compiler.outputPath = options.output.path;
	compiler.recordsInputPath = options.recordsInputPath || options.recordsPath;
	compiler.recordsOutputPath = options.recordsOutputPath || options.recordsPath;
	compiler.name = options.name;

	//	如果options.target是个字符串,根据options.target判断具体应用哪些插件(compiler.apply)
	if(typeof options.target === "string") {
		//	这边本来有个switch...case来根据options.target的具体值应用
	} else if(options.target !== false) {
		options.target(compiler);
	} else {
		throw new Error("Unsupported target '" + options.target + "'.");
	}

	//	output.library相关配置
	if(options.output.library || options.output.libraryTarget !== "var") {
		var LibraryTemplatePlugin = require("./LibraryTemplatePlugin");
		compiler.apply(new LibraryTemplatePlugin(options.output.library, options.output.libraryTarget, options.output.umdNamedDefine));
	}

	//	处理externals属性,告诉webpack不要打包这些模块,而是在运行时从环境中请求他们
	if(options.externals) {
		var ExternalsPlugin = require("./ExternalsPlugin");
		compiler.apply(new ExternalsPlugin(options.output.libraryTarget, options.externals));
	}

	//	处理hot属性,devServer相关
	if(options.hot) {
		compiler.apply(new MovedToPluginWarningPlugin("hot", "HotModuleReplacementPlugin"));
		var HotModuleReplacementPlugin = require("./HotModuleReplacementPlugin");
		compiler.apply(new HotModuleReplacementPlugin(options.output));
	}

	//	devtool和sourceMap相关配置
	if(options.devtool && (options.devtool.indexOf("sourcemap") >= 0 || options.devtool.indexOf("source-map") >= 0)) {
		var hidden = options.devtool.indexOf("hidden") >= 0;
		var inline = options.devtool.indexOf("inline") >= 0;
		var evalWrapped = options.devtool.indexOf("eval") >= 0;
		var cheap = options.devtool.indexOf("cheap") >= 0;
		var moduleMaps = options.devtool.indexOf("module") >= 0;
		var noSources = options.devtool.indexOf("nosources") >= 0;
		var legacy = options.devtool.indexOf("@") >= 0;
		var modern = options.devtool.indexOf("#") >= 0;
		var comment = legacy && modern ? "\n/*\n//@ sourceMappingURL=[url]\n//# sourceMappingURL=[url]\n*/" :
			legacy ? "\n/*\n//@ sourceMappingURL=[url]\n*/" :
			modern ? "\n//# sourceMappingURL=[url]" :
			null;
		var Plugin = evalWrapped ? EvalSourceMapDevToolPlugin : SourceMapDevToolPlugin;
		compiler.apply(new Plugin({
			filename: inline ? null : options.output.sourceMapFilename,
			moduleFilenameTemplate: options.output.devtoolModuleFilenameTemplate,
			fallbackModuleFilenameTemplate: options.output.devtoolFallbackModuleFilenameTemplate,
			append: hidden ? false : comment,
			module: moduleMaps ? true : cheap ? false : true,
			columns: cheap ? false : true,
			lineToLine: options.output.devtoolLineToLine,
			noSources: noSources,
		}));
	} else if(options.devtool && options.devtool.indexOf("eval") >= 0) {
		var legacy = options.devtool.indexOf("@") >= 0;
		var modern = options.devtool.indexOf("#") >= 0;
		var comment = legacy && modern ? "//@ sourceURL=[url]\n//# sourceURL=[url]" :
			legacy ? "//@ sourceURL=[url]" :
			modern ? "//# sourceURL=[url]" :
			null;
		compiler.apply(new EvalDevToolModulePlugin(comment, options.output.devtoolModuleFilenameTemplate));
	}

	compiler.apply(new EntryOptionPlugin());
	compiler.applyPluginsBailResult("entry-option", options.context, options.entry);

	if(options.prefetch) {
		compiler.apply(new MovedToPluginWarningPlugin("prefetch", "PrefetchPlugin"));
		var PrefetchPlugin = require("./PrefetchPlugin");
		options.prefetch.map(function(request) {
			compiler.apply(new PrefetchPlugin(options.context, request));
		});
	}
	compiler.apply(
		new CompatibilityPlugin(),
		new LoaderPlugin(),						//	loader插件
		new NodeStuffPlugin(options.node),		//	nodejs环境相关的插件
		new RequireJsStuffPlugin(),				//	RequireJs的插件
		new APIPlugin(),						//	变量名的替换,编译后的文件里随处可见的__webpack_require__变量名就是在此处理
		new ConstPlugin(),						//	转换一些if, 三目表达式等等
		new RequireIncludePlugin(),				//	require.include函数的转换
		new RequireEnsurePlugin(),				//	require.ensure函数的转换
		new RequireContextPlugin(options.resolve.modulesDirectories, options.resolve.extensions),
												//	
		new AMDPlugin(options.module, options.amd || {}),
												//	AMD规范插件
		new CommonJsPlugin(options.module)		//	commonJs规范插件
	);

	compiler.apply(
		new RemoveParentModulesPlugin(),		//	移除父module的插件
		new RemoveEmptyChunksPlugin(),			//	移除空模块的插件
		new MergeDuplicateChunksPlugin(),		//	移除重复模块的插件
		new FlagIncludedChunksPlugin()			//	output中对于名称的配置([name].[hash].[ext])
	);

	compiler.apply(new TemplatedPathPlugin());

	compiler.apply(new RecordIdsPlugin());		//	记录chunkId的插件

	compiler.apply(new WarnCaseSensitiveModulesPlugin());
												//	大小写敏感的插件

	/**
	 * webpack.optimize属性下的几个方法
	 * ./webpack.js
	 * exportPlugins(exports.optimize = {}, "./optimize", [
	 * 		"AggressiveMergingPlugin"
	 * 		//	...
	 * ]);
	 */
	if(options.optimize && options.optimize.occurenceOrder) {
		compiler.apply(new MovedToPluginWarningPlugin("optimize.occurenceOrder", "optimize.OccurrenceOrderPlugin"));
		var OccurrenceOrderPlugin = require("./optimize/OccurrenceOrderPlugin");
		compiler.apply(new OccurrenceOrderPlugin(options.optimize.occurenceOrderPreferEntry));
	}

	/**
	 * 处理minChunkSize, 性能优化相关
	 * 一些小模块会被打包成单独的模块而造成不必要的HTTP请求, 指定minChunkSize会合并这些小模块, 减少HTTP请求
	 */
	if(options.optimize && options.optimize.minChunkSize) {
		compiler.apply(new MovedToPluginWarningPlugin("optimize.minChunkSize", "optimize.MinChunkSizePlugin"));
		var MinChunkSizePlugin = require("./optimize/MinChunkSizePlugin");
		compiler.apply(new MinChunkSizePlugin(options.optimize));
	}

	//	和maxChunksSize相反
	if(options.optimize && options.optimize.maxChunks) {
		compiler.apply(new MovedToPluginWarningPlugin("optimize.maxChunks", "optimize.LimitChunkCountPlugin"));
		var LimitChunkCountPlugin = require("./optimize/LimitChunkCountPlugin");
		compiler.apply(new LimitChunkCountPlugin(options.optimize));
	}

	if(options.optimize.minimize) {
		compiler.apply(new MovedToPluginWarningPlugin("optimize.minimize", "optimize.UglifyJsPlugin"));
		var UglifyJsPlugin = require("./optimize/UglifyJsPlugin");
		if(options.optimize.minimize === true)
			compiler.apply(new UglifyJsPlugin());
		else
			compiler.apply(new UglifyJsPlugin(options.optimize.minimize));
	}

	//	缓存,该属性在watch的模式下默认开启缓存
	if(options.cache === undefined ? options.watch : options.cache) {
		var CachePlugin = require("./CachePlugin");
		compiler.apply(new CachePlugin(typeof options.cache === "object" ? options.cache : null));
	}

	/**
	 * 处理provide属性,如果有则调用ProvidePlugin插件,这个插件可以让一个module赋值为一个变量,从而能在每个module中以变量名访问它
	 * new webpack.ProvidePlugin({
	 * 		identifier: "module1"
	 * });
	 * new webpack.ProvidePlugin({
	 * 		identifier: ["module1", "property1"]
	 * });
	 */
	if(typeof options.provide === "object") {
		compiler.apply(new MovedToPluginWarningPlugin("provide", "ProvidePlugin"));
		var ProvidePlugin = require("./ProvidePlugin");
		compiler.apply(new ProvidePlugin(options.provide));
	}

	/**
	 * 处理define属性,如果有这个属性则调用DefinePlugin插件,这个插件可以定义全局的常量
	 * //	define
	 * new webpack.DefinePlugin({
	 * 		PRODUCTION: JSON.stringify(true),
	 * 		BROWSER_SUPPORTS_HTML5: true
	 * });
	 *
	 * //	use
	 * if(!PRODUCTION) {
	 * 		console.log("develop mode");
	 * }
	 *
	 * if (!BROWSER_SUPPORTS_HTML5) {
	 * 		require("html5shiv");
	 * } 
	 */
	if(options.define) {
		compiler.apply(new MovedToPluginWarningPlugin("define", "DefinePlugin"));
		var defineObject = {};
		if(typeof options.define === "object") {
			Object.keys(options.define).forEach(function(key) {
				defineObject[key] = options.define[key];
			});
		}
		compiler.apply(new DefinePlugin(defineObject));
	}

	//	defineDebug不为false,声明一个全局的DEBUG
	if(options.defineDebug !== false)
		compiler.apply(new DefinePlugin({
			DEBUG: !!options.debug
		}));

	//	触发after-plugins和应用一些其他插件, 最后触发after-resolvers
	compiler.applyPlugins("after-plugins", compiler);
	compiler.resolvers.normal.apply(
		new UnsafeCachePlugin(options.resolve.unsafeCache)
		//	...
	);
	//	...
	compiler.applyPlugins("after-resolvers", compiler);

	//	返回options
	return options;
};

看完这一段之后,个人对webpack的了解更深入了一些,熟悉了好几个平常不怎么用到,但是感觉还是很有用的东西,例如externals define 属性。

到这里,webpack的整体流程算是完了,在webpack中,有各式各样的插件,下面我们一起了解下大家所熟知的UglifyJsPlugin来理解下插件体系和加深对webpack流程的理解。

首先我们了解下开发webpack插件的一些事项: 在中文里面提到:

  • 一个JavaScript命名函数
  • 在它的原型上定义一个apply方法
  • 指定挂载的webpack事件钩子
  • 处理webpack内部实例的特定数据
  • 功能完成后调用webpack提供的回调

基础结构如下:

// 命名函数
function MyExampleWebpackPlugin() {
	//	...
};

// 在它的 prototype 上定义一个 apply 方法。
MyExampleWebpackPlugin.prototype.apply = function(compiler) {
  // 指定挂载的webpack事件钩子
  compiler.plugin('webpacksEventHook', function(compilation /* 处理webpack内部实例的特定数据*/, callback) {
    console.log("This is an example plugin!!!");

    // 功能完成后调用webpack提供的回调。
    callback();
  });
};

了解了插件的基本架构,下面一起看下UglifyJsPlugin的实现,UglifyJsPlugin位于lib/optimize/UglifyJsPlugin.js

//	一个JavaScript命名函数
function UglifyJsPlugin(options) {
	if(typeof options !== "object") options = {};
	if(typeof options.compressor !== "undefined") {
		options.compress = options.compressor;
	}
	this.options = options;
}
module.exports = UglifyJsPlugin;

//	在它的原型上定义一个apply方法
UglifyJsPlugin.prototype.apply = function(compiler) {
	var options = this.options;

	//	如果只选指定项中有关于文件后缀的就用指定项中的,否则用默认的(.js结尾)
	options.test = options.test || /\.js($|\?)/i;

	var requestShortener = new RequestShortener(compiler.context);
	compiler.plugin("compilation", function(compilation) {
		if(options.sourceMap !== false) {
			compilation.plugin("build-module", function(module) {
				// to get detailed location info about errors
				module.useSourceMap = true;
			});
		}

		/**
		 * 指定挂载的webpack事件钩子(optimize-chunk-assets)
		 * @param  {Object}  chunks     被压缩的模块信息
		 * @param  {Object}  callback 	webpack提供的回调
		 */
		compilation.plugin("optimize-chunk-assets", function(chunks, callback) {
			var files = [];

			//	取出模块中的文件
			chunks.forEach(function(chunk) {
				chunk.files.forEach(function(file) {
					files.push(file);
				});
			});

			//	compilation.additionalChunkAssets这个队列在lib/HotModuleReplacementPlugin.js这个文件中被加入了新元素
			//	也一起合并到files中去
			compilation.additionalChunkAssets.forEach(function(file) {
				files.push(file);
			});

			//	提取出符合options中指定的文件后缀的文件
			files = files.filter(ModuleFilenameHelpers.matchObject.bind(undefined, options));
			files.forEach(function(file) {
				var oldWarnFunction = uglify.AST_Node.warn_function;
				var warnings = [];
				try {
					var asset = compilation.assets[file];
					if(asset.__UglifyJsPlugin) {
						compilation.assets[file] = asset.__UglifyJsPlugin;
						return;
					}

					//	sourceMap相关
					if(options.sourceMap !== false) {
						if(asset.sourceAndMap) {
							var sourceAndMap = asset.sourceAndMap();
							var inputSourceMap = sourceAndMap.map;
							var input = sourceAndMap.source;
						} else {
							var inputSourceMap = asset.map();
							var input = asset.source();
						}
						var sourceMap = new SourceMapConsumer(inputSourceMap);
						uglify.AST_Node.warn_function = function(warning) { // eslint-disable-line camelcase
							var match = /\[.+:([0-9]+),([0-9]+)\]/.exec(warning);
							var line = +match[1];
							var column = +match[2];
							var original = sourceMap.originalPositionFor({
								line: line,
								column: column
							});
							if(!original || !original.source || original.source === file) return;
							warnings.push(warning.replace(/\[.+:([0-9]+),([0-9]+)\]/, "") +
								"[" + requestShortener.shorten(original.source) + ":" + original.line + "," + original.column + "]");
						};
					} else {
						var input = asset.source();
						uglify.AST_Node.warn_function = function(warning) { // eslint-disable-line camelcase
							warnings.push(warning);
						};
					}
					uglify.base54.reset();
					var ast = uglify.parse(input, {
						filename: file
					});

					//	压缩代码并应用转换
					if(options.compress !== false) {
						ast.figure_out_scope();
						var compress = uglify.Compressor(options.compress);
						ast = ast.transform(compress);
					}

					//	在顶级作用域打乱变量名称(默认不开启)
					if(options.mangle !== false) {
						ast.figure_out_scope(options.mangle || {});
						ast.compute_char_frequency(options.mangle || {});
						ast.mangle_names(options.mangle || {});
						if(options.mangle && options.mangle.props) {
							uglify.mangle_properties(ast, options.mangle.props);
						}
					}
					var output = {};

					//	注释
					output.comments = Object.prototype.hasOwnProperty.call(options, "comments") ? options.comments : /^\**!|@preserve|@license/;

					//	代码美化
					output.beautify = options.beautify;

					//	把options.output下的每项提取到output
					for(var k in options.output) {
						output[k] = options.output[k];
					}
					if(options.sourceMap !== false) {
						var map = uglify.SourceMap({ // eslint-disable-line new-cap
							file: file,
							root: ""
						});
						output.source_map = map; // eslint-disable-line camelcase
					}
					var stream = uglify.OutputStream(output); // eslint-disable-line new-cap
					ast.print(stream);
					if(map) map = map + "";
					stream = stream + "";
					asset.__UglifyJsPlugin = compilation.assets[file] = (map ?
						new SourceMapSource(stream, file, JSON.parse(map), input, inputSourceMap) :
						new RawSource(stream));
					if(warnings.length > 0) {
						compilation.warnings.push(new Error(file + " from UglifyJs\n" + warnings.join("\n")));
					}
				} catch(err) {
					//	捕获到压缩出错信息的操作
					if(err.line) {
						var original = sourceMap && sourceMap.originalPositionFor({
							line: err.line,
							column: err.col
						});
						if(original && original.source) {
							compilation.errors.push(new Error(file + " from UglifyJs\n" + err.message + " [" + requestShortener.shorten(original.source) + ":" + original.line + "," + original.column + "]"));
						} else {
							compilation.errors.push(new Error(file + " from UglifyJs\n" + err.message + " [" + file + ":" + err.line + "," + err.col + "]"));
						}
					} else if(err.msg) {
						compilation.errors.push(new Error(file + " from UglifyJs\n" + err.msg));
					} else
						compilation.errors.push(new Error(file + " from UglifyJs\n" + err.stack));
				} finally {
					uglify.AST_Node.warn_function = oldWarnFunction; // eslint-disable-line camelcase
				}
			});

			//	执行回调函数
			callback();
		});

		// 指定挂载的webpack事件钩子(normal-module-loader)
		compilation.plugin("normal-module-loader", function(context) {
			context.minimize = true;
		});
	});
};

上面就是我对UglifyJsPlugin的标注,从这个插件的源码分析,我们可以基本看到 webpack 编译时的读写过程大致是怎么样的:实例化插件 –> 读取源文件 –> 编译并输出

总结

我们在命令行里输入了webpack [--xxx]之后,webpack内部大概帮我们做了下面几件事情:

  • 首先会走到bin/webpack.js,解析命令行参数以及开始执行编译
  • bin/webpack.js中调用webpack入口函数, 走到lib/webpack.js
  • webpack中实例化一个Compiler对象(继承Tapable ,实现插件的注册),调用目录下的lib/WebpackOptionsApply.js模块
  • lib/WebpackOptionsApply.js中根据传入的optionscompiler逐个编译并应用不同插件,并且返回文件
参考

[webpack]源码解读:命令行输入webpack的时候都发生了什么?

webpack源码分析(一)— Tapable插件架构

webpack 源码解析