webpack 源码分析(四)

回顾内容,我们之前讲完了了NodeEnvironmentPlugin。先回答一道题,你了解哪些webpack插件,他们是干什么的?

  1. 我了解NodeEnvironmentPlugin这个插件
  2. 它是webpack内置函数,而且是webpack第一个执行的插件,它的主要作用是用来操作webpack输入输出,以及给webpack提供日志,集成了inputFileSystem,infrastructureLogger
  3. 它相较于fs or graceful-fs,它增加了缓存策略,多次调用文件,会把方法合并,只调用一个文件,500ms内,如果再次调用该文件,直接走缓存。
  4. 监听watchFileSystem,加入了轮训的机制,原来没用fs.watch(webpack5给换回来了),跨平台支持(磨平macos和windows的区别)

接着接着分析代码

	if (Array.isArray(options.plugins)) {
		for (const plugin of options.plugins) {
			if (typeof plugin === "function") {
				plugin.call(compiler, compiler);
			} else {
				plugin.apply(compiler);
			}
		}
	}

来了来了,我们配置的plugin来了,这里发现,插件支持两种形式,一种是对象,一种是函数,如果是类需要增加apply方法,如果是函数直接执行。

到这里,我们已经学废了,如何写一个webpack插件

class Plugin {

	constructor(options) {
		this.options = options;
	}

	apply(compiler) {
		compiler.hooks.beforeRun.tap("NodeEnvironmentPlugin", compiler => {
		console.log(" hello 我是wepack插件")
		});
	}
}

这就是最简单的一个plugin

以后面试的时候,面试官问你,loader先加载还是plugin,记得回答plugin先执行。

applyWebpackOptionsDefaults(options);

接下来走到了applyWebpackOptionsDefaults,走到这里我最开始看的也有点懵,为啥不直接和getNormalizedWebpackOptions结合呢,emmm,我现在感觉是因为代码太多了,拆开了更好理解,前面的normalize,很明显就是格式化代码,把不同传参统一成一套传参,这个apply很明显是修改webpack默认传参的。

代码如下

const applyWebpackOptionsDefaults = options => {
	// 再次给context赋值,我到现在也不知道有什么意义,明明前面已经给过了
	F(options, "context", () => process.cwd());
	// 给webpack制定默认环境
	F(options, "target", () => {
		return getDefaultTarget(/** @type {string} */ (options.context));
	});
	const { mode, name, target } = options;

	let targetProperties =
		target === false
			? /** @type {false} */ (false)
			: typeof target === "string"
			? getTargetProperties(target, /** @type {Context} */ (options.context))
			: getTargetsProperties(
					/** @type {string[]} */ (target),
					/** @type {Context} */ (options.context)
			  );

	const development = mode === "development";
	const production = mode === "production" || !mode;
	// 如果 entry:{main:{}} ----> entry:{main:{import:[./src]}}
	if (typeof options.entry !== "function") {
		for (const key of Object.keys(options.entry)) {
			F(
				options.entry[key],
				"import",
				() => /** @type {[string]} */ (["./src"])
			);
		}
	}
	// devtool 如果没有赋值,在这里完成赋值
	F(options, "devtool", () => (development ? "eval" : false));
	D(options, "watch", false);
	D(options, "profile", false);
	D(options, "parallelism", 100);
	D(options, "recordsInputPath", false);
	D(options, "recordsOutputPath", false);
	// 赋值experiments
	applyExperimentsDefaults(options.experiments, {
		production,
		development,
		targetProperties
	});

	const futureDefaults =
		/** @type {NonNullable<ExperimentsNormalized["futureDefaults"]>} */
		(options.experiments.futureDefaults);

	F(options, "cache", () =>
		development ? { type: /** @type {"memory"} */ ("memory") } : false
	);
	applyCacheDefaults(options.cache, {
		name: name || "default",
		mode: mode || "production",
		development,
		cacheUnaffected: options.experiments.cacheUnaffected
	});
	const cache = !!options.cache;

	applySnapshotDefaults(options.snapshot, {
		production,
		futureDefaults
	});

	applyModuleDefaults(options.module, {
		cache,
		syncWebAssembly:
			/** @type {NonNullable<ExperimentsNormalized["syncWebAssembly"]>} */
			(options.experiments.syncWebAssembly),
		asyncWebAssembly:
			/** @type {NonNullable<ExperimentsNormalized["asyncWebAssembly"]>} */
			(options.experiments.asyncWebAssembly),
		css:
			/** @type {NonNullable<ExperimentsNormalized["css"]>} */
			(options.experiments.css),
		futureDefaults,
		isNode: targetProperties && targetProperties.node === true
	});

	applyOutputDefaults(options.output, {
		context: /** @type {Context} */ (options.context),
		targetProperties,
		isAffectedByBrowserslist:
			target === undefined ||
			(typeof target === "string" && target.startsWith("browserslist")) ||
			(Array.isArray(target) &&
				target.some(target => target.startsWith("browserslist"))),
		outputModule:
			/** @type {NonNullable<ExperimentsNormalized["outputModule"]>} */
			(options.experiments.outputModule),
		development,
		entry: options.entry,
		module: options.module,
		futureDefaults
	});

	applyExternalsPresetsDefaults(options.externalsPresets, {
		targetProperties,
		buildHttp: !!options.experiments.buildHttp
	});

	applyLoaderDefaults(
		/** @type {NonNullable<WebpackOptions["loader"]>} */ (options.loader),
		{ targetProperties }
	);

	F(options, "externalsType", () => {
		const validExternalTypes = require("../../schemas/WebpackOptions.json")
			.definitions.ExternalsType.enum;
		return options.output.library &&
			validExternalTypes.includes(options.output.library.type)
			? /** @type {ExternalsType} */ (options.output.library.type)
			: options.output.module
			? "module"
			: "var";
	});

	applyNodeDefaults(options.node, {
		futureDefaults:
			/** @type {NonNullable<WebpackOptions["experiments"]["futureDefaults"]>} */
			(options.experiments.futureDefaults),
		targetProperties
	});

	F(options, "performance", () =>
		production &&
		targetProperties &&
		(targetProperties.browser || targetProperties.browser === null)
			? {}
			: false
	);
	applyPerformanceDefaults(
		/** @type {NonNullable<WebpackOptions["performance"]>} */
		(options.performance),
		{
			production
		}
	);

	applyOptimizationDefaults(options.optimization, {
		development,
		production,
		css:
			/** @type {NonNullable<ExperimentsNormalized["css"]>} */
			(options.experiments.css),
		records: !!(options.recordsInputPath || options.recordsOutputPath)
	});

	options.resolve = cleverMerge(
		getResolveDefaults({
			cache,
			context: /** @type {Context} */ (options.context),
			targetProperties,
			mode: /** @type {Mode} */ (options.mode)
		}),
		options.resolve
	);

	options.resolveLoader = cleverMerge(
		getResolveLoaderDefaults({ cache }),
		options.resolveLoader
	);
};

这里就是给我们没有传递的配置文件传递默认的初始值。

这章先到这里

回顾一下

  1. 什么是webpack插件,怎么写webpack插件
  2. applyWebpackOptionsDefaults是干什么的

#前端##web前端#
前端技术专栏分享 文章被收录于专栏

1. 包含常规前端面试题解析,和源码分析 2. 从0-1深入浅出理解前端相关技术栈

全部评论

相关推荐

评论
点赞
收藏
分享
牛客网
牛客企业服务