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
方法,这个方法将会对我们传进去的options
和compiler
编译对象进行逐个编译,下面我们一起看下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
中根据传入的options
和compiler
逐个编译并应用不同插件,并且返回文件