《二十五》Webpack 源码解读

Webpack 代码基于 webpack5.88.2

代码解读:

  1. webpack/lib/webpack.js 文件中:可以看到导出的 webpack 其实就是一个函数。主要就是根据传入的配置选项生成 compiler 编译器,如果有传入 callback 的话,会根据配置选项调用 compiler 编译器的 watch() 或者 run() 方法,否则的话返回 compiler 编译器。
    请添加图片描述

    // webpack/lib/webpack.js
    // ...
    const webpack = (options, callback) => {
    	const create = () => {
    		if (!asArray(options).every(webpackOptionsSchemaCheck)) {
    			getValidateSchema()(webpackOptionsSchema, options);
    			util.deprecate(
    				() => {},
    				"webpack bug: Pre-compiled schema reports error while real schema is happy. This has performance drawbacks.",
    				"DEP_WEBPACK_PRE_COMPILED_SCHEMA_INVALID"
    			)();
    		}
    		// 定义 compiler 编译器
    		let compiler;
    		let watch = false;
    		let watchOptions;
    	
    		// 如果传入的 options 配置是一个数组的话,会创建多个 compiler 编译器
    		if (Array.isArray(options)) {
    			compiler = createMultiCompiler(
    				options,
    				options,
    			);
    			watch = options.some(options => options.watch);
    			watchOptions = options.map(options => options.watchOptions || {});
    		} else {
    			// 否则的话,会创建一个 compiler 编译器
    			const webpackOptions = options;
    			compiler = createCompiler(webpackOptions);
    			watch = webpackOptions.watch;
    			watchOptions = webpackOptions.watchOptions || {};
    		}
    
    		// 返回 compiler 编译器及 watch 的数据
    		return { compiler, watch, watchOptions };
    	};
    
    	// 如果有传 callback
    	if (callback) {
    		try {
    			const { compiler, watch, watchOptions } = create();
    			// 如果 watch 为 true,执行编译器的 watch() 方法开始编译并监听
    			if (watch) {
    				compiler.watch(watchOptions, callback);
    			} else {
    				// 否则的话,执行编译的 run() 方法开始编译
    				compiler.run((err, stats) => {
    					compiler.close(err2 => {
    						callback(err || err2, stats);
    					});
    				});
    			}
    			return compiler;
    		} catch (err) {
    			process.nextTick(() => callback(err));
    			return null;
    		}
    	} else {
    		// 如果没有传 callback,返回 compiler 编译器
    		const { compiler, watch } = create();
    		if (watch) {
    			util.deprecate(
    				() => {},
    				"A 'callback' argument needs to be provided to the 'webpack(options, callback)' function when the 'watch' option is set. There is no way to handle the 'watch' option without a callback.",
    				"DEP_WEBPACK_WATCH_WITHOUT_CALLBACK"
    			)();
    		}
    		return compiler;
    	}
    }
    
    module.exports = webpack;
    
  2. webpack/lib/webpack.js 文件中:通过 createCompiler() 函数生成了 compiler 编译器。

    const createCompiler = rawOptions => {
    	const options = getNormalizedWebpackOptions(rawOptions);
    	applyWebpackOptionsBaseDefaults(options);
    	// 1. 通过 new Compiler 类创建出 compiler 对象
    	const compiler = new Compiler(
    		options.context,
    		options
    	);
    	new NodeEnvironmentPlugin({
    		infrastructureLogging: options.infrastructureLogging
    	}).apply(compiler);
    
    	// 2. 注册所有的插件(也就是配置选项中传入的 plugins)
    	if (Array.isArray(options.plugins)) {
    		for (const plugin of options.plugins) {
    			// 如果 plugin 是一个函数,那么通过 call 调用 plugin 并且传入 compiler
    			if (typeof plugin === "function") {
    				plugin.call(compiler, compiler);
    			} else if (plugin) {
    				// 否则的话,调用其 apply 方法并且传入 compiler 。其实 Webpack 中大部分 plugin 都不是函数,而是一个类,其中都是实现一个叫做 apply 的方法
    				plugin.apply(compiler);
    			}
    		}
    	}
    	applyWebpackOptionsDefaults(options);
    
    	// 3. 调用钩子 environment 和 afterEnvironment 的 call()
    	compiler.hooks.environment.call();
    	compiler.hooks.afterEnvironment.call();
    
    	// 4. process() 方法用于处理配置选项中除了 plugins 外的其他属性
    	new WebpackOptionsApply().process(options, compiler);
    	compiler.hooks.initialize.call();
    	return compiler;
    };
    
  3. webpack/lib/WebpackOptionsApply.js 文件中: process() 方法用于处理配置选项中除了 plugins 外的其他属性,会将传入的属性转成 Webpack 的 Plugin 注入到 Webpack 的生命周期中。
    在这里插入图片描述

    process(options, compiler) {
    	compiler.outputPath = options.output.path;
    	compiler.recordsInputPath = options.recordsInputPath || null;
    	compiler.recordsOutputPath = options.recordsOutputPath || null;
    	compiler.name = options.name;
    
    	// 根据各种配置情况,导入各种插件,并且通过 new Plugin.apply() 调用
    	if (options.externals) {
    		const ExternalsPlugin = require("./ExternalsPlugin");
    		new ExternalsPlugin(options.externalsType, options.externals).apply(
    			compiler
    		);
    	}
    	if (options.externalsPresets.node) {
    		const NodeTargetPlugin = require("./node/NodeTargetPlugin");
    		new NodeTargetPlugin().apply(compiler);
    	}
    	if (options.externalsPresets.electronMain) {
    		const ElectronTargetPlugin = require("./electron/ElectronTargetPlugin");
    		new ElectronTargetPlugin("main").apply(compiler);
    	}
    	// ...
    }
    
  4. webpack/lib/Compiler.js 文件中:可以看到 Compiler 类的 constructor() 方法中,有一大堆的 hook。
    任何 Plugin 都会调用其 apply() 方法,在 apply() 方法中就会通过 compiler 编译器的 hooks 属性找到其中的某个 hook 并注册事件,在 Webpack 编译的生命周期内一旦调用了 hook 就会执行该 hook 上注册的事件的回调函数。

    因此 plugin 会贯穿在 Webpack 的整个生命周期内。

    请添加图片描述

  5. webpack/lib/Compiler.js 文件中:Webpack 打包编译最终都是拿到 compiler 编译器执行的 run() 方法。

    Webpack 源代码中 Compiler 对象和 Compilation 对象的区别:

    1. Compiler 对象:在 Webpack 构建之处就会创建的一个对象,在 Webpack 的整个生命周期都会存在(before - run - beforeCompiler - compile - make - finishMake - aftercompiler - done)。只要是 Webpack 的编译,都会创建一个 Compiler 对象。
    2. Compilation 对象:准备编译模块时才会创建 Compilation 对象,主要是在 compile - make 阶段存在使用的对象。
      当修改了源代码重新编译模块时,Compiler 对象可以继续使用,但是会创建一个新的 Compilation 对象;当修改了 Webpack 配置重新执行 webpack 命令进行打包时,就会创建一个新的 Compiler 对象。

    请添加图片描述

    compile(callback) {
    	// 初始化 Compilation 的参数
    	const params = this.newCompilationParams();
    
    	// hooks 的流程:beforeCompile -> compile -> make -> finishMake -> afterCompile
    	this.hooks.beforeCompile.callAsync(params, err => {
    		if (err) return callback(err);
    
    		this.hooks.compile.call(params);
    
    		// 创建 Compilation 对象。此处可以看到,Compilation 对象是在 compile hook 之后,make hook 之前创建的
    		const compilation = this.newCompilation(params);
    
    		const logger = compilation.getLogger("webpack.Compiler");
    
    		logger.time("make hook");
    		// make 是最终的编译过程,但是它只是一个 hook 的调用,具体做的事要去找注册在这个 hook 上的回调的函数
    		// 以 Entry 配置选项为例,当调用 this.hooks.make.callAsync() 时,compiler.hooks.make.tapAsync("EntryPlugin", (compilation, callback) => {compilation.addEntry(context, dep,options, err => {callback(err);});}); 中的回调函数就会被执行
    		this.hooks.make.callAsync(compilation, err => {
    			logger.timeEnd("make hook");
    			if (err) return callback(err);
    
    			logger.time("finish make hook");
    			this.hooks.finishMake.callAsync(compilation, err => {
    				logger.timeEnd("finish make hook");
    				if (err) return callback(err);
    
    				process.nextTick(() => {
    					logger.time("finish compilation");
    					compilation.finish(err => {
    						logger.timeEnd("finish compilation");
    						if (err) return callback(err);
    
    						logger.time("seal compilation");
    						compilation.seal(err => {
    							logger.timeEnd("seal compilation");
    							if (err) return callback(err);
    
    							logger.time("afterCompile hook");
    							this.hooks.afterCompile.callAsync(compilation, err => {
    								logger.timeEnd("afterCompile hook");
    								if (err) return callback(err);
    
    								return callback(null, compilation);
    							});
    						});
    					});
    				});
    			});
    		});
    	});
    }
    
  6. webpack/lib/NormalModule.js 文件中: _doBuild() 函数中的 runLoaders() 处理 Loader。
    请添加图片描述

  7. /webpack/lib/Compilation.js 文件中:seal() 函数输出编译打包后的文件。
    请添加图片描述

图示说明:在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值