Подробное объяснение веб-пакета

внешний интерфейс JavaScript Promise Webpack

Webpack — это самый популярный инструмент для упаковки модулей в современной фронтенд-разработке, которому требуется только простая настройка для завершения загрузки и упаковки модулей. Так как же упростить сборку кода путем настройки некоторых плагинов?

конфигурация веб-пакета

const path = require('path');
module.exports = {
  entry: "./app/entry", // string | object | array
  // Webpack打包的入口
  output: {  // 定义webpack如何输出的选项
    path: path.resolve(__dirname, "dist"), // string
    // 所有输出文件的目标路径
    filename: "[chunkhash].js", // string
    // 「入口(entry chunk)」文件命名模版
    publicPath: "/assets/", // string
    // 构建文件的输出目录
    /* 其它高级配置 */
  },
  module: {  // 模块相关配置
    rules: [ // 配置模块loaders,解析规则
      {
        test: /\.jsx?$/,  // RegExp | string
        include: [ // 和test一样,必须匹配选项
          path.resolve(__dirname, "app")
        ],
        exclude: [ // 必不匹配选项(优先级高于test和include)
          path.resolve(__dirname, "app/demo-files")
        ],
        loader: "babel-loader", // 模块上下文解析
        options: { // loader的可选项
          presets: ["es2015"]
        },
      },
  },
  resolve: { //  解析模块的可选项
    modules: [ // 模块的查找目录
      "node_modules",
      path.resolve(__dirname, "app")
    ],
    extensions: [".js", ".json", ".jsx", ".css"], // 用到的文件的扩展
    alias: { // 模块别名列表
      "module": "new-module"
	  },
  },
  devtool: "source-map", // enum
  // 为浏览器开发者工具添加元数据增强调试
  plugins: [ // 附加插件列表
    // ...
  ],
}

Из вышеизложенного мы видим, что есть несколько основных концепций, которые необходимо понимать при настройке веб-пакета.Entry,Output,Loaders,Plugins,Chunk

  • Entry: указывает модуль входа, который начинает собирать webpack, и начинает собирать и вычислять прямо или косвенно зависимые модули или библиотеки от этого модуля.
  • Вывод: сообщает веб-пакету, как называть выходные файлы и выходные каталоги.
  • Загрузчики: поскольку веб-пакет может обрабатывать только javascript, нам нужно преобразовать некоторые файлы, отличные от js, в модули, которые может обрабатывать веб-пакет, например файлы sass.
  • Плагины:LoadersОбрабатывайте различные типы файлов в модули, которые может обрабатывать веб-пакет,pluginsОбладает сильной способностью. Плагины варьируются от оптимизации упаковки и сжатия до переопределения переменных в среде. Но и самый сложный. Например, сжатие и оптимизация файлов js.UglifyJsPluginплагин
  • Чанк: продукт разделения кода, мы можем упаковать некоторый код в один фрагмент, например, некоторые общие модули, чтобы удалить дублирование и лучше использовать кеш. Или загрузите некоторые функциональные модули по запросу, чтобы оптимизировать время загрузки. В webpack3 и до того, как мы используемCommonsChunkPluginРазделите некоторый общий код на фрагмент и загрузите его отдельно. в вебпаке4CommonsChunkPluginустарел, используетсяSplitChunksPlugin

Подробное объяснение веб-пакета

После прочтения этого, возможно, у вас есть общее представление о веб-пакете, как работает веб-пакет? Мы все знаем, что веб-пакет представляет собой набор очень сложных и абстрактных плагинов.Понимание механизма работы веб-пакета очень полезно для нас, чтобы ежедневно обнаруживать ошибки сборки и писать некоторые плагины для решения задач сборки.

я должен сказать

Webpack — это, по сути, механизм потока событий, его рабочий процесс заключается в последовательном подключении различных плагинов, и ядро ​​всего этого —Tapable, ядро ​​webpack отвечает за компиляциюCompilerи отвечает за создание пакетовCompilationОба являются экземплярами Tapable. Перед Tapable 1.0, то есть webpack3 и его предыдущим Tapable, при условии, что они включают

  • plugin(name:string, handler:function)Зарегистрируйте плагин в Tapable объекте
  • apply(…pluginInstances: (AnyPlugin|function)[])Вызовите определение подключаемого модуля, чтобы зарегистрировать прослушиватель событий в реестре экземпляров Tapable.
  • applyPlugins*(name:string, …)Несколько стратегий для детального управления инициированием событий, в том числеapplyPluginsAsync,applyPluginsParallelи другие методы для реализации контроля запуска событий, реализации

(1) Несколько событий выполняются последовательно (2) Параллельное исполнение (3) Асинхронное выполнение (4) Выполнять плагины один за другим, предыдущий вывод — это каскадный порядок выполнения ввода последнего плагина. (5) Остановить выполнение подключаемого модуля, когда это разрешено, т. е. подключаемый модуль возвращаетundefinedзначение, то есть выход из выполнения Как мы видим, Tapable похож на nodejs.EventEmitter, обеспечивающая регистрацию на мероприятияonи вызватьemit, это очень важно понять, см. каштан: Например, давайте напишем плагин

function CustomPlugin() {}
CustomPlugin.prototype.apply = function(compiler) {
  compiler.plugin('emit', pluginFunction);
}

Он будет выполнен вовремя в жизненном цикле веб-пакета.

this.apply*("emit",options)

Конечно, все Tapable, упомянутые выше, были до версии 1.0. Если вы хотите узнать больше, вы можете проверитьTapable и поток событийЧто такое Tapable 1.0? Версия 1.0 претерпела огромные изменения, больше не предыдущий проходpluginЗарегистрируйтесь на мероприятия черезapplyPlugins*Инициировать вызов события, что такое Tapable 1.0?

Предоставляет множество хуков, которые можно использовать для создания функций хуков для плагинов.

const {
	SyncHook,
	SyncBailHook,
	SyncWaterfallHook,
	SyncLoopHook,
	AsyncParallelHook,
	AsyncParallelBailHook,
	AsyncSeriesHook,
	AsyncSeriesBailHook,
	AsyncSeriesWaterfallHook
 } = require("tapable");

Давайте посмотрим, как его использовать.

class Order {
    constructor() {
        this.hooks = { //hooks
            goods: new SyncHook(['goodsId', 'number']),
            consumer: new AsyncParallelHook(['userId', 'orderId'])
        }
    }

    queryGoods(goodsId, number) {
        this.hooks.goods.call(goodsId, number);
    }

    consumerInfoPromise(userId, orderId) {
        this.hooks.consumer.promise(userId, orderId).then(() => {
            //TODO
        })
    }

    consumerInfoAsync(userId, orderId) {
        this.hooks.consumer.callAsync(userId, orderId, (err, data) => {
            //TODO
        })
    }
}

Конструктор для всех хуков принимает необязательный массив строкового типа.

const hook = new SyncHook(["arg1", "arg2", "arg3"]);
// 调用tap方法注册一个consument
order.hooks.goods.tap('QueryPlugin', (goodsId, number) => {
    return fetchGoods(goodsId, number);
})
// 再添加一个
order.hooks.goods.tap('LoggerPlugin', (goodsId, number) => {
    logger(goodsId, number);
})

// 调用
order.queryGoods('10000000', 1)

дляSyncHook, мы проходимtapдобавить потребителей черезcallдля запуска последовательного выполнения хука.

для не-sync*тип крючка, т.е.async*Тип хуки, мы также можем регистрировать потребителей и вызывать другими способами

// 注册一个sync 钩子
order.hooks.consumer.tap('LoggerPlugin', (userId, orderId) => {
   logger(userId, orderId);
})

order.hooks.consumer.tapAsync('LoginCheckPlugin', (userId, orderId, callback) => {
    LoginCheck(userId, callback);
})

order.hooks.consumer.tapPromise('PayPlugin', (userId, orderId) => {
    return Promise.resolve();
})

// 调用
// 返回Promise
order.consumerInfoPromise('user007', '1024');

//回调函数
order.consumerInfoAsync('user007', '1024')

Через приведенные выше каштаны у вас может быть общее пониманиеTapableиспользование, его употребление

  • Количество регистраций плагина
  • Тип регистрации плагина (синхронизация, асинхронность, обещание)
  • Как вызывать (синхронизация, асинхронность, обещание)
  • Количество параметров при перехвате экземпляра
  • использовать лиinterception

Применитель

Alt text
дляSync*тип крючков.

  • Порядок выполнения плагинов, зарегистрированных под этим хуком, последовательный.
  • использовать толькоtapзарегистрирован, не могу использоватьtapPromiseа такжеtapAsyncрегистр
// 所有的钩子都继承于Hook
class Sync* extends Hook { 
	tapAsync() { // Sync*类型的钩子不支持tapAsync
		throw new Error("tapAsync is not supported on a Sync*");
	}
	tapPromise() {// Sync*类型的钩子不支持tapPromise
		throw new Error("tapPromise is not supported on a Sync*");
	}
	compile(options) { // 编译代码来按照一定的策略执行Plugin
		factory.setup(this, options);
		return factory.create(options);
	}
}

дляAsync*тип крюк

  • служба поддержкиtap,tapPromise,tapAsyncрегистр
class AsyncParallelHook extends Hook {
	constructor(args) {
		super(args);
		this.call = this._call = undefined;
	}

	compile(options) {
		factory.setup(this, options);
		return factory.create(options);
	}
}
class Hook {
	constructor(args) {
		if(!Array.isArray(args)) args = [];
		this._args = args; // 实例钩子的时候的string类型的数组
		this.taps = []; // 消费者
		this.interceptors = []; // interceptors
		this.call = this._call =  // 以sync类型方式来调用钩子
		this._createCompileDelegate("call", "sync");
		this.promise = 
		this._promise = // 以promise方式
		this._createCompileDelegate("promise", "promise");
		this.callAsync = 
		this._callAsync = // 以async类型方式来调用
		this._createCompileDelegate("callAsync", "async");
		this._x = undefined; // 
	}

	_createCall(type) {
		return this.compile({
			taps: this.taps,
			interceptors: this.interceptors,
			args: this._args,
			type: type
		});
	}

	_createCompileDelegate(name, type) {
		const lazyCompileHook = (...args) => {
			this[name] = this._createCall(type);
			return this[name](...args);
		};
		return lazyCompileHook;
	}
	// 调用tap 类型注册
	tap(options, fn) {
		// ...
		options = Object.assign({ type: "sync", fn: fn }, options);
		// ...
		this._insert(options);  // 添加到 this.taps中
	}
	// 注册 async类型的钩子
	tapAsync(options, fn) {
		// ...
		options = Object.assign({ type: "async", fn: fn }, options);
		// ...
		this._insert(options); // 添加到 this.taps中
	}
	注册 promise类型钩子
	tapPromise(options, fn) {
		// ...
		options = Object.assign({ type: "promise", fn: fn }, options);
		// ...
		this._insert(options); // 添加到 this.taps中
	}
	
}

звонить каждый разtap,tapSync,tapPromiseЗарегистрируйте различные типы хуков плагинов, позвонивcall,callAsync,promiseспособ позвонить. На самом деле, при вызове, чтобы выполнить по определенной стратегии выполнения, вызовитеcompileMethods быстро компилирует метод для выполнения этих плагинов.

const factory = new Sync*CodeFactory();
class Sync* extends Hook { 
	// ...
	compile(options) { // 编译代码来按照一定的策略执行Plugin
		factory.setup(this, options);
		return factory.create(options);
	}
}

class Sync*CodeFactory extends HookCodeFactory {
	content({ onError, onResult, onDone, rethrowIfPossible }) {
		return this.callTapsSeries({
			onError: (i, err) => onError(err),
			onDone,
			rethrowIfPossible
		});
	}
}

compileвызыватьHookCodeFactory#createКомпиляция метода генерирует исполняемый код.


class HookCodeFactory {
	constructor(config) {
		this.config = config;
		this.options = undefined;
	}

	create(options) {
		this.init(options);
		switch(this.options.type) {
			case "sync":  // 编译生成sync, 结果直接返回
				return new Function(this.args(), 
				"\"use strict\";\n" + this.header() + this.content({
					// ...
					onResult: result => `return ${result};\n`,
					// ...
				}));
			case "async": // async类型, 异步执行,最后将调用插件执行结果来调用callback,
				return new Function(this.args({
					after: "_callback"
				}), "\"use strict\";\n" + this.header() + this.content({
					// ...
					onResult: result => `_callback(null, ${result});\n`,
					onDone: () => "_callback();\n"
				}));
			case "promise": // 返回promise类型,将结果放在resolve中
				// ...
				code += "return new Promise((_resolve, _reject) => {\n";
				code += "var _sync = true;\n";
				code += this.header();
				code += this.content({
					// ...
					onResult: result => `_resolve(${result});\n`,
					onDone: () => "_resolve();\n"
				});
			    // ...
				return new Function(this.args(), code);
		}
	}
	// callTap 就是执行一些插件,并将结果返回
	callTap(tapIndex, { onError, onResult, onDone, rethrowIfPossible }) {
		let code = "";
		let hasTapCached = false;
		// ...
		code += `var _fn${tapIndex} = ${this.getTapFn(tapIndex)};\n`;
		const tap = this.options.taps[tapIndex];
		switch(tap.type) {
			case "sync":
				// ...
				if(onResult) {
					code += `var _result${tapIndex} = _fn${tapIndex}(${this.args({
						before: tap.context ? "_context" : undefined
					})});\n`;
				} else {
					code += `_fn${tapIndex}(${this.args({
						before: tap.context ? "_context" : undefined
					})});\n`;
				}
				
				if(onResult) { // 结果透传
					code += onResult(`_result${tapIndex}`);
				}
				if(onDone) { // 通知插件执行完毕,可以执行下一个插件
					code += onDone();
				}
				break;
			case "async": //异步执行,插件运行完后再将结果通过执行callback透传
				let cbCode = "";
				if(onResult)
					cbCode += `(_err${tapIndex}, _result${tapIndex}) => {\n`;
				else
					cbCode += `_err${tapIndex} => {\n`;
				cbCode += `if(_err${tapIndex}) {\n`;
				cbCode += onError(`_err${tapIndex}`);
				cbCode += "} else {\n";
				if(onResult) {
					cbCode += onResult(`_result${tapIndex}`);
				}
				
				cbCode += "}\n";
				cbCode += "}";
				code += `_fn${tapIndex}(${this.args({
					before: tap.context ? "_context" : undefined,
					after: cbCode //cbCode将结果透传
				})});\n`;
				break;
			case "promise": // _fn${tapIndex} 就是第tapIndex 个插件,它必须是个Promise类型的插件
				code += `var _hasResult${tapIndex} = false;\n`;
				code += `_fn${tapIndex}(${this.args({
					before: tap.context ? "_context" : undefined
				})}).then(_result${tapIndex} => {\n`;
				code += `_hasResult${tapIndex} = true;\n`;
				if(onResult) {
					code += onResult(`_result${tapIndex}`);
				}
			// ...
				break;
		}
		return code;
	}
	// 按照插件的注册顺序,按照顺序递归调用执行插件
	callTapsSeries({ onError, onResult, onDone, rethrowIfPossible }) {
		// ...
		const firstAsync = this.options.taps.findIndex(t => t.type !== "sync");
		const next = i => {
			// ...
			const done = () => next(i + 1);
			// ...
			return this.callTap(i, {
				// ...
				onResult: onResult && ((result) => {
					return onResult(i, result, done, doneBreak);
				}),
				// ...
			});
		};
		return next(0);
	}

	callTapsLooping({ onError, onDone, rethrowIfPossible }) {
		
		const syncOnly = this.options.taps.every(t => t.type === "sync");
		let code = "";
		if(!syncOnly) {
			code += "var _looper = () => {\n";
			code += "var _loopAsync = false;\n";
		}
		code += "var _loop;\n";
		code += "do {\n";
		code += "_loop = false;\n";
		// ...
		code += this.callTapsSeries({
			// ...
			onResult: (i, result, next, doneBreak) => { // 一旦某个插件返回不为undefined,  即一只调用某个插件执行,如果为undefined,开始调用下一个
				let code = "";
				code += `if(${result} !== undefined) {\n`;
				code += "_loop = true;\n";
				if(!syncOnly)
					code += "if(_loopAsync) _looper();\n";
				code += doneBreak(true);
				code += `} else {\n`;
				code += next();
				code += `}\n`;
				return code;
			},
			// ...
		})
		code += "} while(_loop);\n";
		// ...
		return code;
	}
	// 并行调用插件执行
	callTapsParallel({ onError, onResult, onDone, rethrowIfPossible, onTap = (i, run) => run() }) {
		// ...
		// 遍历注册都所有插件,并调用
		for(let i = 0; i < this.options.taps.length; i++) {
			// ...
			code += "if(_counter <= 0) break;\n";
			code += onTap(i, () => this.callTap(i, {
				// ...
				onResult: onResult && ((result) => {
					let code = "";
					code += "if(_counter > 0) {\n";
					code += onResult(i, result, done, doneBreak);
					code += "}\n";
					return code;
				}),
				// ...
			}), done, doneBreak);
		}
		// ...
		return code;
	}
}

существуетHookCodeFactory#createпозвонитьcontentметод, этот метод будет вызывать различные методы для выполнения компиляции и генерации конечного кода в соответствии со стратегией выполнения этого хука.

  • SyncHook中调用`callTapsSeries`编译生成最终执行插件的函数,`callTapsSeries`做的就是将插件列表中插件按照注册顺序遍历执行。
    
class SyncHookCodeFactory extends HookCodeFactory {
	content({ onError, onResult, onDone, rethrowIfPossible }) {
		return this.callTapsSeries({
			onError: (i, err) => onError(err),
			onDone,
			rethrowIfPossible
		});
	}
}
  • В SyncBailHook, когда определенный возвращаемый результат неundefinedзавершит выполнение плагина в списке
 class SyncBailHookCodeFactory extends HookCodeFactory {
	content({ onError, onResult, onDone, rethrowIfPossible }) {
		return this.callTapsSeries({
			// ...
			onResult: (i, result, next) => `if(${result} !== undefined) {\n${onResult(result)};\n} else {\n${next()}}\n`,
			// ...
		});
	}
}
  • Результат выполнения предыдущего плагина в SyncWaterfallHook используется как входной параметр следующего плагина.
class SyncWaterfallHookCodeFactory extends HookCodeFactory {
	content({ onError, onResult, onDone, rethrowIfPossible }) {
		return this.callTapsSeries({
			// ...
			onResult: (i, result, next) => {
				let code = "";
				code += `if(${result} !== undefined) {\n`;
				code += `${this._args[0]} = ${result};\n`;
				code += `}\n`;
				code += next();
				return code;
			},
			onDone: () => onResult(this._args[0]),
		});
	}
}
  • Вызов AsyncParallelHookcallTapsParallelВыполнять плагины параллельно
class AsyncParallelHookCodeFactory extends HookCodeFactory {
	content({ onError, onDone }) {
		return this.callTapsParallel({
			onError: (i, err, done, doneBreak) => onError(err) + doneBreak(true),
			onDone
		});
	}
}

процесс веб-пакета

Объяснение процесса webpack в этой статье основано на webpack4.

входной файл веб-пакета

Из файла package.json проекта webpack находим функцию выполнения записи, а если ввести в функцию webpack, то запись будетlib/webpack.js, и если выполнить его в оболочке, он перейдет к./bin/webpack.js, мы принимаемlib/webpack.jsНачнем со входа!

{
  "name": "webpack",
  "version": "4.1.1",
  ...
  "main": "lib/webpack.js",
  "web": "lib/webpack.web.js",
  "bin": "./bin/webpack.js",
  ...
  }

запись веб-пакета

const webpack = (options, callback) => {
    // ...
    // 验证options正确性
    // 预处理options
    options = new WebpackOptionsDefaulter().process(options); // webpack4的默认配置
	compiler = new Compiler(options.context); // 实例Compiler
	// ...
    // 若options.watch === true && callback 则开启watch线程
	compiler.watch(watchOptions, callback);
	compiler.run(callback);
	return compiler;
};

Входной файл веб-пакета на самом деле является примеромCompilerи позвонилrunМетод включает компиляцию, и компиляция веб-пакета выполняется в следующей последовательности вызова хука.

  • перед запуском очистить кеш
  • запустить хук данных кэша регистров
  • before-compile
  • компилировать начать компилировать
  • make анализирует зависимые и косвенно зависимые модули от записи и создает объект модуля
  • сборка модуля модуля сборки
  • seal Результат сборки запакован и не может быть изменен.
  • after-compile завершает сборку, кэширует данные
  • вывести вывод в каталог dist

Процесс компиляции и сборки

И сборка, и компиляция в webpackCompilation

class Compilation extends Tapable {
	constructor(compiler) {
		super();
		this.hooks = {
			// hooks
		};
		// ...
		this.compiler = compiler;
		// ...
		// template
		this.mainTemplate = new MainTemplate(this.outputOptions);
		this.chunkTemplate = new ChunkTemplate(this.outputOptions);
		this.hotUpdateChunkTemplate = new HotUpdateChunkTemplate(
			this.outputOptions
		);
		this.runtimeTemplate = new RuntimeTemplate(
			this.outputOptions,
			this.requestShortener
		);
		this.moduleTemplates = {
			javascript: new ModuleTemplate(this.runtimeTemplate),
			webassembly: new ModuleTemplate(this.runtimeTemplate)
		};

		// 构建生成的资源
		this.chunks = [];
		this.chunkGroups = [];
		this.modules = [];
		this.additionalChunkAssets = [];
		this.assets = {};
		this.children = [];
		// ...
	}
	// 
	buildModule(module, optional, origin, dependencies, thisCallback) {
		// ...
		// 调用module.build方法进行编译代码,build中 其实是利用acorn编译生成AST
		this.hooks.buildModule.call(module);
		module.build(/**param*/);
	}
	// 将模块添加到列表中,并编译模块
	_addModuleChain(context, dependency, onModule, callback) {
		    // ...
		    // moduleFactory.create创建模块,这里会先利用loader处理文件,然后生成模块对象
		    moduleFactory.create(
				{
					contextInfo: {
						issuer: "",
						compiler: this.compiler.name
					},
					context: context,
					dependencies: [dependency]
				},
				(err, module) => {
					const addModuleResult = this.addModule(module);
					module = addModuleResult.module;
					onModule(module);
					dependency.module = module;
					
					// ...
					// 调用buildModule编译模块
					this.buildModule(module, false, null, null, err => {});
				}
		});
	}
	// 添加入口模块,开始编译&构建
	addEntry(context, entry, name, callback) {
		// ...
		this._addModuleChain( // 调用_addModuleChain添加模块
			context,
			entry,
			module => {
				this.entries.push(module);
			},
			// ...
		);
	}

	
	seal(callback) {
		this.hooks.seal.call();

		// ...
		const chunk = this.addChunk(name);
		const entrypoint = new Entrypoint(name);
		entrypoint.setRuntimeChunk(chunk);
		entrypoint.addOrigin(null, name, preparedEntrypoint.request);
		this.namedChunkGroups.set(name, entrypoint);
		this.entrypoints.set(name, entrypoint);
		this.chunkGroups.push(entrypoint);

		GraphHelpers.connectChunkGroupAndChunk(entrypoint, chunk);
		GraphHelpers.connectChunkAndModule(chunk, module);

		chunk.entryModule = module;
		chunk.name = name;

		 // ...
		this.hooks.beforeHash.call();
		this.createHash();
		this.hooks.afterHash.call();
		this.hooks.beforeModuleAssets.call();
		this.createModuleAssets();
		if (this.hooks.shouldGenerateChunkAssets.call() !== false) {
			this.hooks.beforeChunkAssets.call();
			this.createChunkAssets();
		}
		// ...
	}


	createHash() {
		// ...
	}
	
	// 生成 assets 资源并 保存到 Compilation.assets 中 给webpack写插件的时候会用到
	createModuleAssets() {
		for (let i = 0; i < this.modules.length; i++) {
			const module = this.modules[i];
			if (module.buildInfo.assets) {
				for (const assetName of Object.keys(module.buildInfo.assets)) {
					const fileName = this.getPath(assetName);
					this.assets[fileName] = module.buildInfo.assets[assetName]; 
					this.hooks.moduleAsset.call(module, fileName);
				}
			}
		}
	}

	createChunkAssets() {
	 // ...
	}
}

в вебпакеmakeкрюк,tapAsyncзарегистрировалDllEntryPlugin, заключается в том, чтобы вызвать входной модуль, вызвавcompilation.addEntryМетод добавляет все входные модули в очередь компиляции и сборки и запускает процесс компиляции.

compiler.hooks.make.tapAsync("DllEntryPlugin", (compilation, callback) => {
		compilation.addEntry(
			this.context,
			new DllEntryDependency(
				this.entries.map((e, idx) => {
					const dep = new SingleEntryDependency(e);
					dep.loc = `${this.name}:${idx}`;
					return dep;
				}),
				this.name
			),
			// ...
		);
	});

затем вaddEntryвызывать_addModuleChainНачать компиляцию. существует_addModuleChainМодули генерируются первыми и строятся последними.

class NormalModuleFactory extends Tapable {
	// ...
	create(data, callback) {
		// ...
		this.hooks.beforeResolve.callAsync(
			{
				contextInfo,
				resolveOptions,
				context,
				request,
				dependencies
			},
			(err, result) => {
				if (err) return callback(err);

				// Ignored
				if (!result) return callback();
				// factory 钩子会触发 resolver 钩子执行,而resolver钩子中会利用acorn 处理js生成AST,再利用acorn处理前,会使用loader加载文件
				const factory = this.hooks.factory.call(null);

				factory(result, (err, module) => {
					if (err) return callback(err);

					if (module && this.cachePredicate(module)) {
						for (const d of dependencies) {
							d.__NormalModuleFactoryCache = module;
						}
					}

					callback(null, module);
				});
			}
		);
	}
}

После компиляции позвонитеcompilation.sealМетод закрыт, генерируя ресурсы, и эти ресурсы хранятся вcompilation.assets, compilation.chunk, будет использоваться при написании плагинов для webpack

class Compiler extends Tapable {
	constructor(context) {
		super();
		this.hooks = {
			beforeRun: new AsyncSeriesHook(["compilation"]),
			run: new AsyncSeriesHook(["compilation"]),
			emit: new AsyncSeriesHook(["compilation"]),
			afterEmit: new AsyncSeriesHook(["compilation"]),
			compilation: new SyncHook(["compilation", "params"]),
			beforeCompile: new AsyncSeriesHook(["params"]),
			compile: new SyncHook(["params"]),
			make: new AsyncParallelHook(["compilation"]),
			afterCompile: new AsyncSeriesHook(["compilation"]),
			// other hooks
		};
		// ...
	}

	run(callback) {
		const startTime = Date.now();

		const onCompiled = (err, compilation) => {
			// ...

			this.emitAssets(compilation, err => {
				if (err) return callback(err);

				if (compilation.hooks.needAdditionalPass.call()) {
					compilation.needAdditionalPass = true;

					const stats = new Stats(compilation);
					stats.startTime = startTime;
					stats.endTime = Date.now();
					this.hooks.done.callAsync(stats, err => {
						if (err) return callback(err);

						this.hooks.additionalPass.callAsync(err => {
							if (err) return callback(err);
							this.compile(onCompiled);
						});
					});
					return;
				}
				// ...
			});
		};

		this.hooks.beforeRun.callAsync(this, err => {
			if (err) return callback(err);
			this.hooks.run.callAsync(this, err => {
				if (err) return callback(err);

				this.readRecords(err => {
					if (err) return callback(err);

					this.compile(onCompiled);
				});
			});
		});
	}
	// 输出文件到构建目录
	emitAssets(compilation, callback) {
		// ...
		this.hooks.emit.callAsync(compilation, err => {
			if (err) return callback(err);
			outputPath = compilation.getPath(this.outputPath);
			this.outputFileSystem.mkdirp(outputPath, emitFiles);
		});
	}
	
	newCompilationParams() {
		const params = {
			normalModuleFactory: this.createNormalModuleFactory(),
			contextModuleFactory: this.createContextModuleFactory(),
			compilationDependencies: new Set()
		};
		return params;
	}

	compile(callback) {
		const params = this.newCompilationParams();
		this.hooks.beforeCompile.callAsync(params, err => {
			if (err) return callback(err);
			this.hooks.compile.call(params);
			const compilation = this.newCompilation(params);

			this.hooks.make.callAsync(compilation, err => {
				if (err) return callback(err);
				compilation.finish();
				// make 钩子执行后,调用seal生成资源
				compilation.seal(err => {
					if (err) return callback(err);
					this.hooks.afterCompile.callAsync(compilation, err => {
						if (err) return callback(err);
						// emit, 生成最终文件
						return callback(null, compilation);
					});
				});
			});
		});
	}
}

конечный результат

существуетsealПосле выполнения он вызоветemitХук, в соответствии с атрибутом пути выходной конфигурации файла конфигурации веб-пакета, выводит файл по указанному пути.


Технологический еженедельник IVWEBШок в сети, обратите внимание на публичный номер: сообщество IVWEB, регулярно каждую неделю публикуйте качественные статьи.

  • Сборник статей еженедельника:weekly
  • Командные проекты с открытым исходным кодом:Feflow