webpack3.12.0源码解析(一)

因为公司有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 = {}, {...});

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值