webpack-cli命令-从零开始webpack源码思考2

2022-08-02,,,,

1.webpack-cli

上一节知道,webpack命令会执行webpack-cli包的bin。那就接着看下webpack-cli包。

package.json

 "bin": {
    "webpack-cli": "./bin/cli.js"
  },
  "main": "./bin/cli.js",

2../bin/cli.js  主流

不用编译的命令数组。

IIFE,立即调用函数表达式。

 

// wrap in IIFE to be able to use return

	const importLocal = require("import-local");
	// Prefer the local installation of webpack-cli
	if (importLocal(__filename)) {
		return;
	}

 本地如果安装了webpack-cli,就用本地安装版本,不用全局的。

require("v8-compile-cache");

	const ErrorHelpers = require("./utils/errorHelpers");

使用v8缓存的代码,从而加快实例化时间, "代码缓存"是由V8解析和编译完成的工作。

引入错误处理函数。

	const NON_COMPILATION_CMD = process.argv.find(arg => {
		if (arg === "serve") {
			global.process.argv = global.process.argv.filter(a => a !== "serve");
			process.argv = global.process.argv;
		}
		return NON_COMPILATION_ARGS.find(a => a === arg);
	});

	if (NON_COMPILATION_CMD) {
		return require("./utils/prompt-command")(NON_COMPILATION_CMD, ...process.argv);
	}

遍历查找命令行参数,命令行参数有serve,就过滤掉,然后再从不需要编译的命令数组中找到当前命令行参数也有的一个命令。

不需要编译的命令数组,["init", "migrate", "serve", "generate-loader", "generate-plugin", "info"];

对于不需要编译的命令,直接/utils/prompt-command函数,执行。

const yargs = require("yargs").usage(`webpack-cli ${require("../package.json").version}

Usage: webpack-cli [options]
       webpack-cli [options] --entry <entry> --output <output>
       webpack-cli [options] <entries...> --output <output>
       webpack-cli <command> [options]

For more information, see https://webpack.js.org/api/cli/.`);

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

使用yargs,处理命令行交互,配置用法,再配置帮助、版本、命令相关。

// yargs will terminate the process early when the user uses help or version.
	// This causes large help outputs to be cut short (https://github.com/nodejs/node/wiki/API-changes-between-v0.10-and-v4#process).
	// To prevent this we use the yargs.parse API and exit the process normally
	yargs.parse(process.argv.slice(2), (err, argv, output) => {
		Error.stackTraceLimit = 30;
		// arguments validation failed
		if (err && output) {
			console.error(output);
			process.exitCode = 1;
			return;
		}

		// help or version info
		if (output) {
			console.log(output);
			return;
		}

		if (argv.verbose) {
			argv["display"] = "verbose";
		}

根据当前命令行参数,格式化参数和处理输出。注意参数取slice(2),

        let options;
		try {
			options = require("./utils/convert-argv")(argv);
		} catch (err) {
			if (err.code === "MODULE_NOT_FOUND") {
				const moduleName = err.message.split("'")[1];
				let instructions = "";
				let errorMessage = "";

				if (moduleName === "webpack") {
					errorMessage = `\n${moduleName} not installed`;
					instructions = `Install webpack to start bundling: \u001b[32m\n  $ npm install --save-dev ${moduleName}\n`;

					if (process.env.npm_execpath !== undefined && process.env.npm_execpath.includes("yarn")) {
						instructions = `Install webpack to start bundling: \u001b[32m\n $ yarn add ${moduleName} --dev\n`;
					}
					Error.stackTraceLimit = 1;
					console.error(`${errorMessage}\n\n${instructions}`);
					process.exitCode = 1;
					return;
				}
			}

			if (err.name !== "ValidationError") {
				throw err;
			}

			const stack = ErrorHelpers.cleanUpWebpackOptions(err.stack, err.message);
			const message = err.message + "\n" + stack;

			if (argv.color) {
				console.error(`\u001b[1m\u001b[31m${message}\u001b[39m\u001b[22m`);
			} else {
				console.error(message);
			}

			process.exitCode = 1;
			return;
		}

根据命令行参数,获取并解析配置文件配置信息options,并结合命令行参数再次处理配置信息options,校验配置项合法性。

捕获异常,webpack模块找不到,没安装的话提示下。

非校验错误,直接抛出错误。

校验错误等,简洁化处理保留必要错误信息。

结束。返回。

/**
		 * When --silent flag is present, an object with a no-op write method is
		 * used in place of process.stout
		 */
		const stdout = argv.silent ? { write: () => {} } : process.stdout;

 参数开启静默模式,就用空输出函数。不是标准输出。

	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);
			}
		}

这个函数好,从命令行取参数值,并且执行传入的函数,函数参数时从命令行取的参数值。兼容数组,遍历执行。

function processOptions(options) {
			// process Promise
			if (typeof options.then === "function") {
				options.then(processOptions).catch(function(err) {
					console.error(err.stack || err);
					// eslint-disable-next-line no-process-exit
					process.exit(1);
				});
				return;
			}

			const firstOptions = [].concat(options)[0];
			const statsPresetToOptions = require("webpack").Stats.presetToOptions;

			let outputOptions = options.stats;
			if (typeof outputOptions === "boolean" || typeof outputOptions === "string") {
				outputOptions = statsPresetToOptions(outputOptions);
			} else if (!outputOptions) {
				outputOptions = {};
			}

			ifArg("display", function(preset) {
				outputOptions = statsPresetToOptions(preset);
			});

			outputOptions = Object.create(outputOptions);
			if (Array.isArray(options) && !outputOptions.children) {
				outputOptions.children = options.map(o => o.stats);
			}
			if (typeof outputOptions.context === "undefined") outputOptions.context = firstOptions.context;

			ifArg("env", function(value) {
				if (outputOptions.env) {
					outputOptions._env = value;
				}
			});

			ifArg("json", function(bool) {
				if (bool) {
					outputOptions.json = bool;
					outputOptions.modules = bool;
				}
			});

			if (typeof outputOptions.colors === "undefined") outputOptions.colors = require("supports-color").stdout;

			ifArg("sort-modules-by", function(value) {
				outputOptions.modulesSort = value;
			});

			ifArg("sort-chunks-by", function(value) {
				outputOptions.chunksSort = value;
			});

			ifArg("sort-assets-by", function(value) {
				outputOptions.assetsSort = value;
			});

			ifArg("display-exclude", function(value) {
				outputOptions.exclude = value;
			});

			if (!outputOptions.json) {
				if (typeof outputOptions.cached === "undefined") outputOptions.cached = false;
				if (typeof outputOptions.cachedAssets === "undefined") outputOptions.cachedAssets = false;

				ifArg("display-chunks", function(bool) {
					if (bool) {
						outputOptions.modules = false;
						outputOptions.chunks = true;
						outputOptions.chunkModules = true;
					}
				});

				ifArg("display-entrypoints", function(bool) {
					outputOptions.entrypoints = bool;
				});

				ifArg("display-reasons", function(bool) {
					if (bool) outputOptions.reasons = true;
				});

				ifArg("display-depth", function(bool) {
					if (bool) outputOptions.depth = true;
				});

				ifArg("display-used-exports", function(bool) {
					if (bool) outputOptions.usedExports = true;
				});

				ifArg("display-provided-exports", function(bool) {
					if (bool) outputOptions.providedExports = true;
				});

				ifArg("display-optimization-bailout", function(bool) {
					if (bool) outputOptions.optimizationBailout = bool;
				});

				ifArg("display-error-details", function(bool) {
					if (bool) outputOptions.errorDetails = true;
				});

				ifArg("display-origins", function(bool) {
					if (bool) outputOptions.chunkOrigins = true;
				});

				ifArg("display-max-modules", function(value) {
					outputOptions.maxModules = +value;
				});

				ifArg("display-cached", function(bool) {
					if (bool) outputOptions.cached = true;
				});

				ifArg("display-cached-assets", function(bool) {
					if (bool) outputOptions.cachedAssets = true;
				});

				if (!outputOptions.exclude) outputOptions.exclude = ["node_modules", "bower_components", "components"];

				if (argv["display-modules"]) {
					outputOptions.maxModules = Infinity;
					outputOptions.exclude = undefined;
					outputOptions.modules = true;
				}
			}

			ifArg("hide-modules", function(bool) {
				if (bool) {
					outputOptions.modules = false;
					outputOptions.chunkModules = false;
				}
			});

			ifArg("info-verbosity", function(value) {
				outputOptions.infoVerbosity = value;
			});

			ifArg("build-delimiter", function(value) {
				outputOptions.buildDelimiter = value;
			});

这么长,都在逐个判个判断命令行是否有那个参数,有则执行回调,就是取值,然后再配置给outputOptions。

supports-color,拿到支持颜色的输出流。

const webpack = require("webpack");

			let lastHash = null;
			let 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;
			}

获取webpack,使用options,编译产生compiler对象。

如果出现,配置校验错误,就用颜色字体或者不带颜色提示。

	if (argv.progress) {
				const ProgressPlugin = require("webpack").ProgressPlugin;
				new ProgressPlugin({
					profile: argv.profile
				}).apply(compiler);
			}

如果,有监听进度参数,初始化并把ProgressPlugin插件的逻辑,挂给compiler。

if (outputOptions.infoVerbosity === "verbose") {
				if (argv.w) {
					compiler.hooks.watchRun.tap("WebpackInfo", compilation => {
						const compilationName = compilation.name ? compilation.name : "";
						console.error("\nCompilation " + compilationName + " starting…\n");
					});
				} else {
					compiler.hooks.beforeRun.tap("WebpackInfo", compilation => {
						const compilationName = compilation.name ? compilation.name : "";
						console.error("\nCompilation " + compilationName + " starting…\n");
					});
				}
				compiler.hooks.done.tap("WebpackInfo", compilation => {
					const compilationName = compilation.name ? compilation.name : "";
					console.error("\nCompilation " + compilationName + " finished\n");
				});
			}

如果信息输出模式是要冗长的,根据命令行参数w,也就是watch是否开启,选择用compiler.hooks.watchRun还是compiler.hooks.beforeRun事件监听器,监听日志信息。输出。

最后,用done监听器,监听日志信息。

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) {
					stdout.write(JSON.stringify(stats.toJson(outputOptions), null, 2) + "\n");
				} else if (stats.hash !== lastHash) {
					lastHash = stats.hash;
					if (stats.compilation && stats.compilation.errors.length !== 0) {
						const errors = stats.compilation.errors;
						if (errors[0].name === "EntryModuleNotFoundError") {
							console.error("\n\u001b[1m\u001b[31mInsufficient number of arguments or no entry found.");
							console.error(
								"\u001b[1m\u001b[31mAlternatively, run 'webpack(-cli) --help' for usage info.\u001b[39m\u001b[22m\n"
							);
						}
					}
					const statsString = stats.toString(outputOptions);
					const delimiter = outputOptions.buildDelimiter ? `${outputOptions.buildDelimiter}\n` : "";
					if (statsString) stdout.write(`${statsString}\n${delimiter}`);
				}
				if (!options.watch && stats.hasErrors()) {
					process.exitCode = 2;
				}
			}

编译回调函数,如果不是监听或者错误,purgeInputFileSystem,清理缓存。

异常处理提示;

hash不一致处理提示;

if (firstOptions.watch || options.watch) {
				const watchOptions =
					firstOptions.watchOptions || options.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);
				if (outputOptions.infoVerbosity !== "none") console.error("\nwebpack is watching the files…\n");
			} else {
				compiler.run((err, stats) => {
					if (compiler.close) {
						compiler.close(err2 => {
							compilerCallback(err || err2, stats);
						});
					} else {
						compilerCallback(err, stats);
					}
				});
			}
		}

compiler.watch或者compiler.run,开始进行编译构建。

watch时,处理标准输出监听end时,退出进程。

标准输入流默认是暂停 (pause) 的,所以必须要调用 process.stdin.resume() 来恢复 (resume) 接收。

processOptions(options);

执行上面的函数啊。

3.分支 config-yargs.js

const optionsSchema = require("../config/optionsSchema.json");

const { GROUPS } = require("../utils/constants");

const {
	CONFIG_GROUP,
	BASIC_GROUP,
	MODULE_GROUP,
	OUTPUT_GROUP,
	ADVANCED_GROUP,
	RESOLVE_GROUP,
	OPTIMIZE_GROUP,
	DISPLAY_GROUP
} = GROUPS;

const nestedProperties = ["anyOf", "oneOf", "allOf"];

可用配置schema适配列表。

配置分组信息。

需要嵌套获取的属性

const resolveSchema = schema => {
	let current = schema;
	if (schema && typeof schema === "object" && "$ref" in schema) {
		const path = schema.$ref.split("/");
		for (const element of path) {
			if (element === "#") {
				current = optionsSchema;
			} else {
				current = current[element];
			}
		}
	}
	return current;
};

const findPropertyInSchema = (schema, property, subProperty) => {
	if (!schema) return null;
	if (subProperty) {
		if (schema[property] && typeof schema[property] === "object" && subProperty in schema[property]) {
			return resolveSchema(schema[property][subProperty]);
		}
	} else {
		if (property in schema) return resolveSchema(schema[property]);
	}
	for (const name of nestedProperties) {
		if (schema[name]) {
			for (const item of schema[name]) {
				const resolvedItem = resolveSchema(item);
				const result = findPropertyInSchema(resolvedItem, property, subProperty);
				if (result) return result;
			}
		}
	}
	return undefined;
};

const getSchemaInfo = (path, property, subProperty) => {
	const pathSegments = path.split(".");
	let current = optionsSchema;
	for (const segment of pathSegments) {
		if (segment === "*") {
			current = findPropertyInSchema(current, "additionalProperties") || findPropertyInSchema(current, "items");
		} else {
			current = findPropertyInSchema(current, "properties", segment);
		}
		if (!current) return undefined;
	}
	return findPropertyInSchema(current, property, subProperty);
};

从shema中,根据路径,循环递归获取属性值。 其实就是循环解析schema对象,根据,a.b.c这样的路径,依次解析a,b,c,递归用的好。

module.exports = function(yargs) {
	yargs
		.help("help")
		.alias("help", "h")
		.version()
		.alias("version", "v")
		.options({
			config: {
				type: "string",
				describe: "Path to the config file",
				group: CONFIG_GROUP,
				defaultDescription: "webpack.config.js or webpackfile.js",
				requiresArg: true
			},
			"config-register": {
				type: "array",
				alias: "r",
				describe: "Preload one or more modules before loading the webpack configuration",
				group: CONFIG_GROUP,
				defaultDescription: "module id or path",
				requiresArg: true
			},
			"config-name": {
				type: "string",
				describe: "Name of the config to use",
				group: CONFIG_GROUP,
				requiresArg: true
			},
			env: {
				describe: "Environment passed to the config, when it is a function",
				group: CONFIG_GROUP
			},
			mode: {
				type: getSchemaInfo("mode", "type"),
				choices: getSchemaInfo("mode", "enum"),
				describe: getSchemaInfo("mode", "description"),
				group: CONFIG_GROUP,
				requiresArg: true
			},
			context: {
				type: getSchemaInfo("context", "type"),
				describe: getSchemaInfo("context", "description"),
				group: BASIC_GROUP,
				defaultDescription: "The current directory",
				requiresArg: true
			},
			entry: {
				type: "string",
				describe: getSchemaInfo("entry", "description"),
				group: BASIC_GROUP,
				requiresArg: true
			},
			"no-cache": {
				type: "boolean",
				describe: "Disables cached builds",
				group: BASIC_GROUP
			},
			"module-bind": {
				type: "string",
				describe: "Bind an extension to a loader",
				group: MODULE_GROUP,
				requiresArg: true
			},
			"module-bind-post": {
				type: "string",
				describe: "Bind an extension to a post loader",
				group: MODULE_GROUP,
				requiresArg: true
			},
			"module-bind-pre": {
				type: "string",
				describe: "Bind an extension to a pre loader",
				group: MODULE_GROUP,
				requiresArg: true
			},
			output: {
				alias: "o",
				describe: "The output path and file for compilation assets",
				group: OUTPUT_GROUP,
				requiresArg: true
			},
			"output-path": {
				type: "string",
				describe: getSchemaInfo("output.path", "description"),
				group: OUTPUT_GROUP,
				defaultDescription: "The current directory",
				requiresArg: true
			},
			"output-filename": {
				type: "string",
				describe: getSchemaInfo("output.filename", "description"),
				group: OUTPUT_GROUP,
				defaultDescription: "[name].js",
				requiresArg: true
			},
			"output-chunk-filename": {
				type: "string",
				describe: getSchemaInfo("output.chunkFilename", "description"),
				group: OUTPUT_GROUP,
				defaultDescription: "filename with [id] instead of [name] or [id] prefixed",
				requiresArg: true
			},
			"output-source-map-filename": {
				type: "string",
				describe: getSchemaInfo("output.sourceMapFilename", "description"),
				group: OUTPUT_GROUP,
				requiresArg: true
			},
			"output-public-path": {
				type: "string",
				describe: getSchemaInfo("output.publicPath", "description"),
				group: OUTPUT_GROUP,
				requiresArg: true
			},
			"output-jsonp-function": {
				type: "string",
				describe: getSchemaInfo("output.jsonpFunction", "description"),
				group: OUTPUT_GROUP,
				requiresArg: true
			},
			"output-pathinfo": {
				type: "boolean",
				describe: getSchemaInfo("output.pathinfo", "description"),
				group: OUTPUT_GROUP
			},
			"output-library": {
				type: "array",
				describe: "Expose the exports of the entry point as library",
				group: OUTPUT_GROUP,
				requiresArg: true
			},
			"output-library-target": {
				type: "string",
				describe: getSchemaInfo("output.libraryTarget", "description"),
				choices: getSchemaInfo("output.libraryTarget", "enum"),
				group: OUTPUT_GROUP,
				requiresArg: true
			},
			"records-input-path": {
				type: "string",
				describe: getSchemaInfo("recordsInputPath", "description"),
				group: ADVANCED_GROUP,
				requiresArg: true
			},
			"records-output-path": {
				type: "string",
				describe: getSchemaInfo("recordsOutputPath", "description"),
				group: ADVANCED_GROUP,
				requiresArg: true
			},
			"records-path": {
				type: "string",
				describe: getSchemaInfo("recordsPath", "description"),
				group: ADVANCED_GROUP,
				requiresArg: true
			},
			define: {
				type: "string",
				describe: "Define any free var in the bundle",
				group: ADVANCED_GROUP,
				requiresArg: true
			},
			target: {
				type: "string",
				describe: getSchemaInfo("target", "description"),
				group: ADVANCED_GROUP,
				requiresArg: true
			},
			cache: {
				type: "boolean",
				describe: getSchemaInfo("cache", "description"),
				default: null,
				group: ADVANCED_GROUP,
				defaultDescription: "It's enabled by default when watching"
			},
			watch: {
				type: "boolean",
				alias: "w",
				describe: getSchemaInfo("watch", "description"),
				group: BASIC_GROUP
			},
			"watch-stdin": {
				type: "boolean",
				alias: "stdin",
				describe: getSchemaInfo("watchOptions.stdin", "description"),
				group: ADVANCED_GROUP
			},
			"watch-aggregate-timeout": {
				describe: getSchemaInfo("watchOptions.aggregateTimeout", "description"),
				type: getSchemaInfo("watchOptions.aggregateTimeout", "type"),
				group: ADVANCED_GROUP,
				requiresArg: true
			},
			"watch-poll": {
				type: "string",
				describe: getSchemaInfo("watchOptions.poll", "description"),
				group: ADVANCED_GROUP
			},
			hot: {
				type: "boolean",
				describe: "Enables Hot Module Replacement",
				group: ADVANCED_GROUP
			},
			debug: {
				type: "boolean",
				describe: "Switch loaders to debug mode",
				group: BASIC_GROUP
			},
			devtool: {
				type: "string",
				describe: getSchemaInfo("devtool", "description"),
				group: BASIC_GROUP,
				requiresArg: true
			},
			"resolve-alias": {
				type: "string",
				describe: getSchemaInfo("resolve.alias", "description"),
				group: RESOLVE_GROUP,
				requiresArg: true
			},
			"resolve-extensions": {
				type: "array",
				describe: getSchemaInfo("resolve.alias", "description"),
				group: RESOLVE_GROUP,
				requiresArg: true
			},
			"resolve-loader-alias": {
				type: "string",
				describe: "Setup a loader alias for resolving",
				group: RESOLVE_GROUP,
				requiresArg: true
			},
			"optimize-max-chunks": {
				describe: "Try to keep the chunk count below a limit",
				group: OPTIMIZE_GROUP,
				requiresArg: true
			},
			"optimize-min-chunk-size": {
				describe: getSchemaInfo("optimization.splitChunks.minSize", "description"),
				group: OPTIMIZE_GROUP,
				requiresArg: true
			},
			"optimize-minimize": {
				type: "boolean",
				describe: getSchemaInfo("optimization.minimize", "description"),
				group: OPTIMIZE_GROUP
			},
			prefetch: {
				type: "string",
				describe: "Prefetch this request (Example: --prefetch ./file.js)",
				group: ADVANCED_GROUP,
				requiresArg: true
			},
			provide: {
				type: "string",
				describe: "Provide these modules as free vars in all modules (Example: --provide jQuery=jquery)",
				group: ADVANCED_GROUP,
				requiresArg: true
			},
			"labeled-modules": {
				type: "boolean",
				describe: "Enables labeled modules",
				group: ADVANCED_GROUP
			},
			plugin: {
				type: "string",
				describe: "Load this plugin",
				group: ADVANCED_GROUP,
				requiresArg: true
			},
			bail: {
				type: getSchemaInfo("bail", "type"),
				describe: getSchemaInfo("bail", "description"),
				group: ADVANCED_GROUP,
				default: null
			},
			profile: {
				type: "boolean",
				describe: getSchemaInfo("profile", "description"),
				group: ADVANCED_GROUP,
				default: null
			},
			d: {
				type: "boolean",
				describe: "shortcut for --debug --devtool eval-cheap-module-source-map --output-pathinfo",
				group: BASIC_GROUP
			},
			p: {
				type: "boolean",
				// eslint-disable-next-line quotes
				describe: 'shortcut for --optimize-minimize --define process.env.NODE_ENV="production"',
				group: BASIC_GROUP
			},
			silent: {
				type: "boolean",
				describe: "Prevent output from being displayed in stdout"
			},
			json: {
				type: "boolean",
				alias: "j",
				describe: "Prints the result as JSON."
			},
			progress: {
				type: "boolean",
				describe: "Print compilation progress in percentage",
				group: BASIC_GROUP
			},
			color: {
				type: "boolean",
				alias: "colors",
				default: function supportsColor() {
					return require("supports-color").stdout;
				},
				group: DISPLAY_GROUP,
				describe: "Force colors on the console"
			},
			"no-color": {
				type: "boolean",
				alias: "no-colors",
				group: DISPLAY_GROUP,
				describe: "Force no colors on the console"
			},
			"sort-modules-by": {
				type: "string",
				group: DISPLAY_GROUP,
				describe: "Sorts the modules list by property in module"
			},
			"sort-chunks-by": {
				type: "string",
				group: DISPLAY_GROUP,
				describe: "Sorts the chunks list by property in chunk"
			},
			"sort-assets-by": {
				type: "string",
				group: DISPLAY_GROUP,
				describe: "Sorts the assets list by property in asset"
			},
			"hide-modules": {
				type: "boolean",
				group: DISPLAY_GROUP,
				describe: "Hides info about modules"
			},
			"display-exclude": {
				type: "string",
				group: DISPLAY_GROUP,
				describe: "Exclude modules in the output"
			},
			"display-modules": {
				type: "boolean",
				group: DISPLAY_GROUP,
				describe: "Display even excluded modules in the output"
			},
			"display-max-modules": {
				type: "number",
				group: DISPLAY_GROUP,
				describe: "Sets the maximum number of visible modules in output"
			},
			"display-chunks": {
				type: "boolean",
				group: DISPLAY_GROUP,
				describe: "Display chunks in the output"
			},
			"display-entrypoints": {
				type: "boolean",
				group: DISPLAY_GROUP,
				describe: "Display entry points in the output"
			},
			"display-origins": {
				type: "boolean",
				group: DISPLAY_GROUP,
				describe: "Display origins of chunks in the output"
			},
			"display-cached": {
				type: "boolean",
				group: DISPLAY_GROUP,
				describe: "Display also cached modules in the output"
			},
			"display-cached-assets": {
				type: "boolean",
				group: DISPLAY_GROUP,
				describe: "Display also cached assets in the output"
			},
			"display-reasons": {
				type: "boolean",
				group: DISPLAY_GROUP,
				describe: "Display reasons about module inclusion in the output"
			},
			"display-depth": {
				type: "boolean",
				group: DISPLAY_GROUP,
				describe: "Display distance from entry point for each module"
			},
			"display-used-exports": {
				type: "boolean",
				group: DISPLAY_GROUP,
				describe: "Display information about used exports in modules (Tree Shaking)"
			},
			"display-provided-exports": {
				type: "boolean",
				group: DISPLAY_GROUP,
				describe: "Display information about exports provided from modules"
			},
			"display-optimization-bailout": {
				type: "boolean",
				group: DISPLAY_GROUP,
				describe: "Display information about why optimization bailed out for modules"
			},
			"display-error-details": {
				type: "boolean",
				group: DISPLAY_GROUP,
				describe: "Display details about errors"
			},
			display: {
				type: "string",
				choices: ["", "verbose", "detailed", "normal", "minimal", "errors-only", "none"],
				group: DISPLAY_GROUP,
				describe: "Select display preset"
			},
			verbose: {
				type: "boolean",
				group: DISPLAY_GROUP,
				describe: "Show more details"
			},
			"info-verbosity": {
				type: "string",
				default: "info",
				choices: ["none", "info", "verbose"],
				group: DISPLAY_GROUP,
				describe: "Controls the output of lifecycle messaging e.g. Started watching files..."
			},
			"build-delimiter": {
				type: "string",
				group: DISPLAY_GROUP,
				describe: "Display custom text after build output"
			}
		});
};

根据yargs,配置命令的信息,包括、分组、描述、类型、默认值。 在help时可看到。相当于api文档啊

4、utils/prompt-command 分支

module.exports = function promptForInstallation(packages, ...args) {
	const nameOfPackage = "@webpack-cli/" + packages;
	let packageIsInstalled = false;
	let pathForCmd;
	try {
		const path = require("path");
		const fs = require("fs");
		pathForCmd = path.resolve(process.cwd(), "node_modules", "@webpack-cli", packages);
		if (!fs.existsSync(pathForCmd)) {
			const globalModules = require("global-modules");
			pathForCmd = globalModules + "/@webpack-cli/" + packages;
			require.resolve(pathForCmd);
		} else {
			require.resolve(pathForCmd);
		}
		packageIsInstalled = true;
	} catch (err) {
		packageIsInstalled = false;
	}

获取包路径,局部没有,取全局。 安装了,设置安装标志为true;

global-modules,去全局modules路径。

 

if (!packageIsInstalled) {
		const path = require("path");
		const fs = require("fs");
		const readLine = require("readline");
		const isYarn = fs.existsSync(path.resolve(process.cwd(), "yarn.lock"));

		const packageManager = isYarn ? "yarn" : "npm";
		const options = ["install", "-D", nameOfPackage];

		if (isYarn) {
			options[0] = "add";
		}

		if (packages === "init") {
			if (isYarn) {
				options.splice(1, 1); // remove '-D'
				options.splice(0, 0, "global");
			} else {
				options[1] = "-g";
			}
		}

		const commandToBeRun = `${packageManager} ${options.join(" ")}`;

		const question = `Would you like to install ${packages}? (That will run ${commandToBeRun}) (yes/NO) : `;

		console.error(`The command moved into a separate package: ${nameOfPackage}`);
		const questionInterface = readLine.createInterface({
			input: process.stdin,
			output: process.stdout
		});
		questionInterface.question(question, answer => {
			questionInterface.close();
			switch (answer.toLowerCase()) {
				case "y":
				case "yes":
				case "1": {
					runCommand(packageManager, options)
						.then(_ => {
							if (packages === "init") {
								npmGlobalRoot()
									.then(root => {
										const pathtoInit = path.resolve(root.trim(), "@webpack-cli", "init");
										return pathtoInit;
									})
									.then(pathForInit => {
										return require(pathForInit).default(...args);
									})
									.catch(error => {
										console.error(error);
										process.exitCode = 1;
									});
								return;
							}

							pathForCmd = path.resolve(process.cwd(), "node_modules", "@webpack-cli", packages);
							return runWhenInstalled(packages, pathForCmd, ...args);
						})
						.catch(error => {
							console.error(error);
							process.exitCode = 1;
						});
					break;
				}
				default: {
					console.error(`${nameOfPackage} needs to be installed in order to run the command.`);
					process.exitCode = 1;
					break;
				}
			}
		});
	}

 没有安装这个命令,我们就去安装。

分yarn add 还是npm -D;

如果是init,则全局安装;

readLine.createInterface,创建问题交互接口;

监听答案,执行回调,如果,y、yes、1,则安装,然后执行。如果是init,则全局寻找 ,执行。

否则,提示,必须先安装这个包,才能运行命令。

 

return runWhenInstalled(packages, pathForCmd, ...args);

已经安装过,就直接执行啊。

const runWhenInstalled = (packages, pathForCmd, ...args) => {
	const currentPackage = require(pathForCmd);
	const func = currentPackage.default;
	if (typeof func !== "function") {
		throw new Error(`@webpack-cli/${packages} failed to export a default function`);
	}
	return func(...args);
};

执行安装过的包,按路径,找到包,执行默认函数,参数为命令行参数。

const npmGlobalRoot = () => {
	const cp = require("child_process");
	return new Promise((resolve, reject) => {
		const command = cp.spawn("npm", ["root", "-g"]);
		command.on("error", error => reject(error));
		command.stdout.on("data", data => resolve(data.toString()));
		command.stderr.on("data", data => reject(data));
	});
};

开启子进程,执行,npm root -g,获取全局node_modules路径,返回promise;


/**
 * @param {string} command process to run
 * @param {string[]} args commandline arguments
 * @returns {Promise<void>} promise
 */
const runCommand = (command, args) => {
	const cp = require("child_process");
	return new Promise((resolve, reject) => {
		const executedCommand = cp.spawn(command, args, {
			stdio: "inherit",
			shell: true
		});

		executedCommand.on("error", error => {
			reject(error);
		});

		executedCommand.on("exit", code => {
			if (code === 0) {
				resolve();
			} else {
				reject();
			}
		});
	});
};

开启子进程cp,执行安装命令。promise。

 

本文地址:https://blog.csdn.net/liixnhai/article/details/107352968

《webpack-cli命令-从零开始webpack源码思考2.doc》

下载本文的Word格式文档,以方便收藏与打印。