因为公司有vue的spa项目改ssr的需求,所以就顺便看了下webpack的源码,就顺便记录下来了。因为项目vue-cli使用的webpack3.12.0,所以本系列也是解析webpack3.12.0。但4和3也差不了多少,使用yarg做终端交互,tapable管理生命周期,纵向plugins处理,横向对于不同类文件使用plugins里的各种loader。
package.json
bin属性在npm下载包后在node_modules/.bin里生成webpack.cmd文件,这样我们就可以通过npm调用了,所以npm调用的入口文件是./bin/wbpack.js
main属性指示调用webpack模块的入口文件
{
...
"main": "lib/webpack.js",
"web": "lib/webpack.web.js",
"bin": "./bin/webpack.js",
...
}
./bin/webpack.js
// 告知使用node打开该文件,需放在首行
#!/usr/bin/env node
var path = require("path");
// 优先调用项目node_modules里的webpack
try {
var localWebpack = require.resolve(path.join(process.cwd(), "node_modules", "webpack", "bin", "webpack.js"));
if(__filename !== localWebpack) {
return require(localWebpack);
}
} catch(e) {}
// yarg一个好用的输入输出界面工具usage在help时输出使用提示,在调用.parse()或.argv后才生效
var yargs = require("yargs")
.usage("webpack " + require("../package.json").version + "\n" +
"Usage: https://webpack.js.org/api/cli/\n" +
"Usage without config file: webpack <entry> [<entry>] <output>\n" +
"Usage with config file: webpack");
// 对yarg的一些配置
require("./config-yargs")(yargs);
var DISPLAY_GROUP = "Stats options:";
var BASIC_GROUP = "Basic options:";
// yarg对传入参数的解释
yargs.options({...})
// yarg.parse(调用本进程传入的参数, (错误, 处理后的参数, yarg的输出)=>{...})
// yarg.parse()调用,yarg生效,进入回调
// webpack --color --verbose=true会被yarg处理后argv为{_: [], color: true, verbose:true}
yargs.parse(process.argv.slice(2), (err, argv, output) => {
// 如果有错误则输出错误
if(err && output) {
console.error(output);
process.exitCode = 1;
return;
}
// 在传入help或version参数时的输出
if(output) {
console.log(output);
return;
}
// 处理传入的参数(如--color、--config等)
if(argv.verbose) {
argv["display"] = "verbose";
}
var options = require("./convert-argv")(yargs, 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);
}
}
// 处理传入进程的参数经yarg和webpack内置配置处理后的options
function processOptions(options) {
// options有then且是函数则运行options.then并退出当前processOptions
if(typeof options.then === "function") {
options.then(processOptions).catch(function(err) {
console.error(err.stack || err);
process.exit(1); // eslint-disable-line
});
return;
}
// 这一大串都是对输出表现的处理
...
// 使用webpack ()函数生成预编译对象compiler
// 详情看下一标题
var webpack = require("../lib/webpack.js");
Error.stackTraceLimit = 30;
var lastHash = null;
var compiler;
try {
compiler = webpack(options);
} catch(err) {
if(err.name === "WebpackOptionsValidationError") {
if(argv.color)
console.error(
`\u001b[1m\u001b[31m${err.message}\u001b[39m\u001b[22m`
);
else
console.error(err.message);
// eslint-disable-next-line no-process-exit
process.exit(1);
}
throw err;
}
// 如果传入--progress则调用ProgressPlugin插件
// compiler.apply继承的tabpable的apply方法
// Tapable.prototype.apply = function apply() {
// for(var i = 0; i < arguments.length; i++) {
// arguments[i].apply(this);
// }
// };
// 会调用(new ProgressPlugin()).apply(compiler)
// ProgressPlugin在apply对预编译对象compiler做处理
if(argv.progress) {
var ProgressPlugin = require("../lib/ProgressPlugin");
compiler.apply(new ProgressPlugin({
profile: argv.profile
}));
}
// compiler.run的回调函数
function compilerCallback(err, stats) {
if(!options.watch || err) {
// Do not keep cache anymore
compiler.purgeInputFileSystem();
}
if(err) {
lastHash = null;
console.error(err.stack || err);
if(err.details) console.error(err.details);
process.exitCode = 1;
return;
}
if(outputOptions.json) {
process.stdout.write(JSON.stringify(stats.toJson(outputOptions), null, 2) + "\n");
} else if(stats.hash !== lastHash) {
lastHash = stats.hash;
var statsString = stats.toString(outputOptions);
if(statsString)
process.stdout.write(statsString + "\n");
}
if(!options.watch && stats.hasErrors()) {
process.exitCode = 2;
}
}
// 开始预编译compiler对象的处理
// compiler.watch和compiler.run都会进行编译打包
if(firstOptions.watch || options.watch) {
// watch模式下编译打包
var watchOptions = firstOptions.watchOptions || firstOptions.watch || options.watch || {};
if(watchOptions.stdin) {
process.stdin.on("end", function() {
process.exit(); // eslint-disable-line
});
process.stdin.resume();
}
compiler.watch(watchOptions, compilerCallback);
console.log("\nWebpack is watching the files…\n");
} else
// webpack运行compiler.run开始编译打包
compiler.run(compilerCallback);
}
processOptions(options);
});
./lib/webpack.js
"use strict";
const Compiler = require("./Compiler");
const MultiCompiler = require("./MultiCompiler");
const NodeEnvironmentPlugin = require("./node/NodeEnvironmentPlugin");
const WebpackOptionsApply = require("./WebpackOptionsApply");
const WebpackOptionsDefaulter = require("./WebpackOptionsDefaulter");
const validateSchema = require("./validateSchema");
const WebpackOptionsValidationError = require("./WebpackOptionsValidationError");
const webpackOptionsSchema = require("../schemas/webpackOptionsSchema.json");
// webpack处理函数
function webpack(options, callback) {
// webpack配置验证
const webpackOptionsValidationErrors = validateSchema(webpackOptionsSchema, options);
if(webpackOptionsValidationErrors.length) {
throw new WebpackOptionsValidationError(webpackOptionsValidationErrors);
}
let compiler;
if(Array.isArray(options)) {
// options是数组,则使用MultiCompiler模块处理
compiler = new MultiCompiler(options.map(options => webpack(options)));
} else if(typeof options === "object") {
// 对options初始化默认的webpack配置
new WebpackOptionsDefaulter().process(options);
compiler = new Compiler();
compiler.context = options.context;
compiler.options = options;
// 对compiler导入node环境的插件
// 主要对node的文件系统进一步封装并缓存打包导入的文件还有监听生命周期before-run
new NodeEnvironmentPlugin().apply(compiler);
if(options.plugins && Array.isArray(options.plugins)) {
compiler.apply.apply(compiler, options.plugins);
}
// compiler.applyPlugins继承自tapable,如果监听了就会在此触发生命周期environment和after-environment(监听写法compiler.plugin('environment'))
compiler.applyPlugins("environment");
compiler.applyPlugins("after-environment");
// 对webpack配置(平时写的webpack.config.js)做处理生成预编译的配置
compiler.options = new WebpackOptionsApply().process(options, compiler);
} else {
throw new Error("Invalid argument: options");
}
// 如果调用该函数时传入了callback则会马上运行compiler.watch或compiler.run进行编译打包
if(callback) {
if(typeof callback !== "function") throw new Error("Invalid argument: callback");
if(options.watch === true || (Array.isArray(options) && options.some(o => o.watch))) {
const watchOptions = Array.isArray(options) ? options.map(o => o.watchOptions || {}) : (options.watchOptions || {});
return compiler.watch(watchOptions, callback);
}
compiler.run(callback);
}
return compiler;
}
// 导出webpack的相关模块
exports = module.exports = webpack;
webpack.WebpackOptionsDefaulter = WebpackOptionsDefaulter;
webpack.WebpackOptionsApply = WebpackOptionsApply;
webpack.Compiler = Compiler;
webpack.MultiCompiler = MultiCompiler;
webpack.NodeEnvironmentPlugin = NodeEnvironmentPlugin;
webpack.validate = validateSchema.bind(this, webpackOptionsSchema);
webpack.validateSchema = validateSchema;
webpack.WebpackOptionsValidationError = WebpackOptionsValidationError;
function exportPlugins(obj, mappings) {
Object.keys(mappings).forEach(name => {
Object.defineProperty(obj, name, {
configurable: false,
enumerable: true,
get: mappings[name]
});
});
}
// 导出webpack.DefinePlugin等模块
exportPlugins(exports, {...});
// 导出webpack.optimize.UglifyJsPlugin等模块
exportPlugins(exports.optimize = {}, {...});