третья серия веб-пакетов

исходный код Webpack

Автор: Цуй Цзин

вводить

Одной из характеристик веб-пакета является то, что он обрабатывает все модули, мы можем разделить логику на разные файлы, а затем экспортировать и импортировать по модульной схеме. Теперь модуль ES6 является наиболее часто используемым модульным решением, поэтому вы должны были написатьimport './xxx'илиimport 'something-in-nodemodules'илиimport '@/xxx'(Символ @ устанавливается с помощью настройки псевдонима в конфигурации веб-пакета). webpack обрабатывает импорт этих модулейimportКогда есть важный шаг, вот как правильно найти'./xxx','something-in-nodemodules'или'@/xxx'И так, какому файлу соответствует. Этот шаг представляет собой логику, которую должна обрабатывать часть разрешения.

На самом деле не только модули в исходниках нужно резолвить, в том числе и загрузчик, в общем процессе обработки вебпака, процесс резолвинга неотделим от пути к файлу.

В то же время webpack имеет конфигурацию разрешения в файле конфигурации, которая может соответствующим образом настроить процесс разрешения, например, установить расширение файла, найти искомый каталог и т. д. (подробнее см.Официальное введение).

Ниже мы в основном представим процесс разрешения для общих файлов и основной процесс разрешения для загрузчика.

Введение в основной процесс решения

Сначала подготовьте простую демонстрацию

import { A } from './a.js'

Затем посмотрите на основной процесс для этой демонстрации. В обзорной статье одной из серий веб-пакетов есть общая блок-схема компиляции веб-пакета.На рисунке видно, что перед тем, как веб-пакет обработает каждый файл, будет выполняться процесс разрешения, чтобы найти полную информацию о пути к файлу.

webpack编译流程

Запись процесса разрешения в исходном коде веб-пакета находится на заводской стадии. Событие factory запускает функции в NormalModuleFactory. Сначала поместите приблизительную общую блок-схему и получите приблизительную диаграмму кадра, прежде чем углубляться в исходный код.

resolve总览

Далее мы начинаем с файла NormalModuleFactory.js

this.hooks.factory.tap("NormalModuleFactory", () => (result, callback) => {
    // 首先得到 resolver
	let resolver = this.hooks.resolver.call(null);

	// Ignored
	if (!resolver) return callback();

   // 执行
	resolver(result, (err, data) => {
		if (err) return callback(err);

		// Ignored
		if (!data) return callback();

		// direct module
		if (typeof data.source === "function") return callback(null, data);

		this.hooks.afterResolve.callAsync(data, (err, result) => {
			//... resolve结束后流程,此处省略
		});
	});
});

Первый шаг для получения логики резолвера относительно прост: инициировать событие резолвера (хук типа SyncWaterfallHook, тип хука см. в предыдущей статье), и событие резолвера регистрируется в NormalModuleFactory. Ниже приведен код события преобразователя, вы можете видеть, что возвращается функция.

this.hooks.resolver.tap("NormalModuleFactory", () => (data, callback) => {
  //...先展示省略具体内容,后面会详细解释。
})

Итак, после завершения this.hooks.resolver.call(null); вы получите функцию. Затем следующим шагом является выполнение функции для получения результата преобразователя. В функции распознавателя она разделена на два основных процесса: загрузчик и файл в целом.

процесс загрузки

  1. Получите часть запроса встроенного загрузчика. Например, для следующего письма
import Styles from 'style-loader!css-loader?modules!./styles.css';

будет разобран изstyle-loaderиcss-loader. Поскольку этот шаг предназначен только для разбора пути, он не заботится о части конфигурации загрузчика.

  1. Получите экземпляр обработки преобразователя типа загрузчика, то естьconst loaderResolver = this.getResolver("loader");

  2. Используйте loaderResolver для обработки каждого загрузчика по очереди, чтобы получить путь к исполняемому файлу.

документооборот

  1. Получить экземпляр обработки распознавателя обычного файла, то есть кодconst normalResolver = this.getResolver("normal", data.resolveOptions);

  2. Обработайте файл с помощью normalResolver, чтобы получить абсолютный путь к конечному файлу.

Ниже приведен конкретный код преобразователя:

this.hooks.resolver.tap("NormalModuleFactory", () => (data, callback) => {
	const contextInfo = data.contextInfo;
	const context = data.context;
	const request = data.request;
   
   // ... 省略部分和 loader 处理相关的代码
   // 处理 inline loaders,拿到 loader request 部分(loader 的名称或者 loader 的路径,由于这里不关系 loader 的配置等其他细节,所以直接将开头的 -!, 和 ! 直接替换掉,将多个 ! 替换成一个,方便后面处理)
	let elements = request
		.replace(/^-?!+/, "")
		.replace(/!!+/g, "!")
		.split("!");
	let resource = elements.pop();
	// 提取出具体的 loader
	elements = elements.map(identToLoaderRequest);

	const loaderResolver = this.getResolver("loader");
	const normalResolver = this.getResolver("normal", data.resolveOptions);

	asyncLib.parallel(
		[
			callback =>
				this.resolveRequestArray(
					contextInfo,
					context,
					elements,
					loaderResolver,
					callback
				),
			callback => {
				if (resource === "" || resource[0] === "?") {
					return callback(null, {
						resource
					});
				}

				normalResolver.resolve(
					contextInfo,
					context,
					resource,
					{},
					(err, resource, resourceResolveData) => {
						if (err) return callback(err);
						callback(null, {
							resourceResolveData,
							resource
						});
					}
				);
			}
		],
		(err, results) => {
		  // ... reslover callback
		})
	)
})
		

Совмещая вышеописанные шаги и код, по сути, общий процесс класса загрузчика и нормального типа файла (далее — обычный класс) аналогичен. Давайте сначала рассмотрим раздел Получение различных типов экземпляров Resolver.

Получите различные типы экземпляров обработки преобразователя

Функция getResolver вызовет метод get в webpack/lib/ResolverFactory.js. Конкретный процесс получения экземпляра преобразователя в этом методе выглядит следующим образом.

获取resolver实例

На приведенном выше рисунке сначала получите параметры в соответствии с разными типами. Так где же существуют эти конфигурации опций?

настройка опций в webpack

Конфигурация разрешения, предоставляемая непосредственно веб-пакетом, находится в разделах разрешения и разрешенияLoader файла конфигурации.Подробные поля смотрите на официальном сайте. Но внутри будет дефолтная конфигурация, в функции обработки записи webpack.js инициализируются все дефолтные конфигурации

// ...
if (Array.isArray(options)) {
	compiler = new MultiCompiler(options.map(options => webpack(options)));
} else if (typeof options === "object") {
	options = new WebpackOptionsDefaulter().process(options);
	compiler = new Compiler(options.context);
	compiler.options = options;
// ...

существуетWebpackOptionsDefaulter()В настраивается много настроек для разрешения и разрешенияLoader.processМетод объединяет конфигурацию веб-пакета, которую мы написали, с конфигурацией по умолчанию.

// WebpackOptionsDefaulter.js 文件
//...
this.set("resolve", "call", value => Object.assign({}, value));
this.set("resolve.unsafeCache", true); // 默认开启缓存
this.set("resolve.modules", ["node_modules"]); // 默认从 node_modules 中查找
// ...

В webpack.js есть следующее предложение

new WebpackOptionsApply().process(options, compiler);

Среди них процесс обработки будет внедрять функцию получения конфигурации по умолчанию normal/context/loader.

  compiler.resolverFactory.hooks.resolveOptions
  	.for("normal")
  	.tap("WebpackOptionsApply", resolveOptions => {
  		return Object.assign(
  			{
  				fileSystem: compiler.inputFileSystem
  			},
  			options.resolve,
  			resolveOptions
  		);
  	});
  compiler.resolverFactory.hooks.resolveOptions
  	.for("context")
  	.tap("WebpackOptionsApply", resolveOptions => {
  		return Object.assign(
  			{
  				fileSystem: compiler.inputFileSystem,
  				resolveToContext: true
  			},
  			options.resolve,
  			resolveOptions
  		);
  	});
  compiler.resolverFactory.hooks.resolveOptions
  	.for("loader")
  	.tap("WebpackOptionsApply", resolveOptions => {
  		return Object.assign(
  			{
  				fileSystem: compiler.inputFileSystem
  			},
  			options.resolveLoader,
  			resolveOptions
  		);
  	});

Это конец введения опций, давайте продолжим смотреть вниз по блок-схеме выше. Когда экземпляр резолвера получен, начинается процесс резолвера: есть normalResolver и loaderResolver в зависимости от типа, а в normalResolver различаются файлы и модули.

В веб-пакете есть много конфигураций, специфичных для пути, таких как псевдонимы, расширения, модули и т. д. Требование в node.js больше не может соответствовать требованиям веб-пакета для разбора пути. Поэтому webpack инкапсулирует отдельную библиотекуenhanced-resolve, который специально используется для обработки различных путей и по-прежнему использует режим плагина webpack для организации кода. Далее мы углубимся в эту библиотеку, и по очереди представим процесс обработки обычных файлов, модулей и загрузчика (в вебпаке также есть процесс разрешения контекста, т.к. процесс не слишком особенный, он будет представлен вместе в процесс модуля). Сначала рассмотрим обработку обычных файлов.

Процесс разрешения обычных файлов

Обычная запись обработки преобразователя файлов находится в веб-пакетеnormalResolver.resolveметод, и весь процесс разрешения можно рассматривать как конкатенацию событий.Когда все объединенные события выполняются, разрешение заканчивается.

resolve事件环

Ключевая часть объединения этих событий в цепочку находится в doResolve и обработчике для каждого события. Здесь возьмите doResolve и вызываемый UnsafePlugin в качестве примера, чтобы увидеть процесс подключения.

// 第一个参数 hook,函数中用到的 hook 是通过参数传进来的。
doResolve(hook, request, message, resolveContext, callback) {
	// ...
	// 生成 context 栈。
	const stackLine = hook.name + ": (" + request.path + ") " +
		(request.request || "") + (request.query || "") +
		(request.directory ? " directory" : "") +
		(request.module ? " module" : "");

	let newStack;
	if(resolveContext.stack) {
		newStack = new Set(resolveContext.stack);
		if(resolveContext.stack.has(stackLine)) {
			// Prevent recursion
			const recursionError = new Error("Recursion in resolving\nStack:\n  " + Array.from(newStack).join("\n  "));
			recursionError.recursion = true;
			if(resolveContext.log) resolveContext.log("abort resolving because of recursion");
			return callback(recursionError);
		}
		newStack.add(stackLine);
	} else {
		newStack = new Set([stackLine]);
	}
	// 简单的demo中这里没有事件注册,先忽略
	this.hooks.resolveStep.call(hook, request);

    // 如果该hook有注册过事件,则调触发该 hook
	if(hook.isUsed()) {
		const innerContext = createInnerContext({
			log: resolveContext.log,
			missing: resolveContext.missing,
			stack: newStack
		}, message);
		return hook.callAsync(request, innerContext, (err, result) => {
			if(err) return callback(err);
			if(result) return callback(null, result);
			callback();
		});
	} else {
		callback();
	}
}

При вызове hook.callAsync введите UnsafeCachePlugin, а затем посмотрите на частичную реализацию в UnsafeCachePlugin:

class UnsafeCachePlugin {
	constructor(source, filterPredicate, cache, withContext, target) {
		this.source = source;
       // ... 省略部分
		this.target = target;
	}
	apply(resolver) {
	   // ensureHook 主要逻辑:如果 resolver 已经有对应的 hook 则返回;如果没有,则会给 resolver 增加一个 this.target 类型的 hook
		const target = resolver.ensureHook(this.target);
		// getHook 会根据 this.source 字符串获取对应的 hook
		resolver.getHook(this.source).tapAsync("UnsafeCachePlugin", (request, resolveContext, callback) => {
		   //... 先省略 UnsafeCache 中其他逻辑,只看衔接部分
			// 继续调用 doResolve,但是注意这里的 target 
			resolver.doResolve(target, request, null, resolveContext, (err, result) => {
				if(err) return callback(err);
				if(result) return callback(null, this.cache[cacheId] = result);
				callback();
			});
		});
	}
}

UnsafeCachePlugin разделен на две части: регистрация события (применяется новое и выполнение) и выполнение события (resolver.getHook(this.source).tapAsyncчасть обратного вызова). Этап регистрации события происходит, когда веб-пакет получает различные типы экземпляров обработки разрешения (ранееПолучите различные типы экземпляров обработки преобразователяВ разделе, когда getResolver) будет передано исходное значение (строковый тип) и целевое значение (строковый тип), код выглядит следующим образом

// source 值为 resolve,target 值为 new-resolve
new UnsafeCachePlugin("resolve", cachePredicate, unsafeCache, cacheWithContext, "new-resolve")`
//...然后会调用 apply 方法

существуетapply, зарегистрируйте логику обработки UnsafeCachePlugin в качестве обратного вызова исходного события и убедитесь в существовании целевого события (если нет, зарегистрируйте его).

На этапе выполнения события после завершения логики самого UnsafeCachePlugin рекурсивно вызватьresolver.doResolve(target, ...), то первый параметр — это целевое событие в UnsafeCachePlugin. Таким образом, после входа в doResolve запускается событие target, формируя таким образом поток событий. Общий процесс вызова, упрощенный, общая логика такова:

doResolve(target1) 
  -> target1 事件(srouce:target1, target: target2) 
  -> 递归调用doResolve(target2) 
     -> target2 事件(srouce:target2, target: target3) 
     -> 递归调用doResolve(target3) 
        -> target3 事件(srouce:target3, target: target4) 
        ...
        ->遇到递归结束标识,结束递归
     

Через рекурсивные вызовы doResolve события соединяются, чтобы сформировать полный поток событий, и, наконец, получается результат разрешения. в файле ResolverFactory.jscreateResolverМетод регистрации каждого плагина в методе определяет поток событий всего разрешения.

exports.createResolver = function(options) {
    // ...
	// 根据 options 中条件的不同,加入各种 plugin
	if(unsafeCache) {
		plugins.push(new UnsafeCachePlugin("resolve", cachePredicate, unsafeCache, cacheWithContext, "new-resolve"));
		plugins.push(new ParsePlugin("new-resolve", "parsed-resolve"));
	} else {
		plugins.push(new ParsePlugin("resolve", "parsed-resolve"));
	}
    // ... plugin 加入的代码
	plugins.forEach(plugin => {
		plugin.apply(resolver);
	});
	// ...

Разобравшись с приведенным выше кодом, вы можете получить полную схему потока событий (на картинке ниже приведен упрощенный вариант, а прикрепленный вариант прилагается)

resolve事件流简版

Объединив приведенную выше диаграмму и демонстрацию, давайте пошагово рассмотрим, что делает каждая ссылка в этом потоке событий. (ps: на следующих шагах будет задействован параметр запроса. Этот параметр проходит через всю логику обработки событий и сохраняет всю информацию о разрешении)

  1. UnsafeCachePlugin

Добавьте уровень кэширования, потому что webpack включает в себя множество процессов разрешения в процессе упаковки. Следовательно, для повышения эффективности необходимо добавить слой кэша. webpack включает UnsafeCache по умолчанию.

  1. ParsePlugin

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

  2. ОписаниеFilePlugin и NextPlugin

    DescriptionFilePlugin будет искать файл описания, и по умолчанию он будет искать package.json. Сначала он будет искаться в директории request.path, если нет, то будет искаться послойно по пути. Наконец, информация package.json и информация о каталоге/пути, где он находится, считываются и сохраняются в запросе. У нас есть файл package.json в корневом каталоге демо, поэтому файлы в корневом каталоге будут получены здесь.

    NextPlugin действует как соединение, а внутренняя логика заключается в непосредственном вызове doResolve и последующем запуске следующего события. Если файл package.json не найден в DescriptionFilePlugin, вводится NextPlugin, и поток событий продолжается.

  3. AliasPlugin/AliasFieldPlugin

Этот шаг запускает обработку псевдонимов. Поскольку AliasFieldPlugin зависит от конфигурации package.json, этот шаг помещается после DescriptionFilePlugin. Помимо записи некоторых псевдонимов в конфигурационный файл, webpack также будет иметь некоторые встроенные псевдонимы, для каждой конфигурации псевдонимов будет прописана функция. На этом шаге будут выполняться все функции одна за другой. Если вы нажмете на конфигурацию псевдонима или aliasField, вы войдете в ветвь красной пунктирной линии на рисунке выше. Замените содержимое параметра запроса новым псевдонимом и снова запустите процесс разрешения. Если попадания нет, введите следующую функцию обработки ModuleKindPlugin

  1. ModuleKindPlugin

в соответствии сrequest.moduleЗначение принимает разные ветви. Если это модуль, то введите логику rawModule позже. В результате, полученном в предыдущем ParsePluginrequest.moduleзаfalse, поэтому здесь возвращается undefined, переходите к следующему обработчику.

  1. JoinRequestPlugin

Объедините путь и запрос в запросе, а также соедините относительный путь и запрос в запросе, чтобы получить два полных пути. В этой демонстрации вы получите/Users/didi/dist/webpackdemo/webpack-demos/demo01/a.jsи./demo01/a.js

  1. DescriptionFilePlugin

Это снова войдет в DescriptionFilePlugin. Однако отличие от первой записи в том, что request.path в это время становится /dir/demo/a.js`. Поскольку путь изменился, вам нужно снова найти package.json.

Затем запустите событие descriptionRelative, чтобы перейти к следующему процессу.

  1. FileKindPlugin

Определите, является ли это каталогом, если да, верните неопределенное значение, введите следующий tryNextPlugin, а затем введите ветку каталога. В противном случае он указывает файл и переходит к событию rawFile. В нашей демонстрации это будет идти в ветку rawFile.

  1. TryNextPlugin/ConcordExtensionsPlugin/AppendPlugin

Так как значение по умолчанию EnforceExtension в webpack равноtrue, поэтому здесь будет введен TryNextPlugin, а enableConcordfalse, ConcordExtensionsPlugin не будет.

TryNextPlugin похож на NextPlugin.Он играет роль соединения.Внутренняя логика заключается в непосредственном вызове doResolve и последующем запуске следующего события. Так что на этом этапе он пойдет прямо на триггерfileветвь событий. Когда TryNextPlugin возвращает значение undefined . Это означает, что файл, соответствующий request.path, не найден, последующий AppendPlugin продолжит выполнение.

Основная логика AppendPlugin: webpack установит параметр resolve.extensions (устанавливается в конфигурации или использует webpack по умолчанию), AppendPlugin добавит эти суффиксы в request.path и request.relativePath один за другим, а затем введитеfileВетвь, чтобы продолжить поток событий.

  1. AliasPlugin/AliasFields/ConcorModulesPlugin/SymlinkPlugin

В это время снова будет введена логика обработки псевдонима.Обратите внимание, что на этом этапе многие псевдонимы, поставляемые с веб-пакетом, больше не будут существовать. Как и прежде, ConcorModulesPlugin по-прежнему отсутствует. SymlinkPlugin используется для обработки наличия ссылки в пути. Поскольку веб-пакет по умолчанию анализирует в соответствии с реальным путем, здесь будет проверяться каждый сегмент пути, и если встречается ссылка, она будет заменена реальным путем. Поскольку путь изменился, он вернется кrelativeсцена. Если в пути нет ссылки, введите FileExistsPlugin

  1. FileExistsPlugin

читатьrequest.pathфайл, чтобы узнать, существует ли файл. Если файл существует, перейдите к событию existsFile.

  1. NextPlugin/ResultPlugin

Подключитесь через NextPlugin, а затем введите событие Resolved. Затем выполните ResultPlugin, на этом весь процесс разрешения завершен, и запрос сохраняет результат разрешения.

процесс разрешения модуля

В webpack помимо импорта файла мы также импортируем модуль, напримерimport Vue from 'vue'. Затем в это время веб-пакет должен правильно найти, где находится файл записи, соответствующий vue. Для vue ParsePlugin приводит кrequest.module = true, а затем в ModuleKindPlugin войдет в ветку rawModule на рисунке выше. мы будем использоватьimport Vue from 'vue'Для демонстрации взгляните на процесс ветки rawModule.

  1. ModuleAppendPlugin/TryNextPlugin

ModuleAppendPlugin похож на описанный выше AppendPlugin, но с добавленным суффиксом. TryNextPlugin входит в событие модуля

  1. ModulesInHierachicDirectoriesPlugin/ModulesInRootPlugin

ModulesInHierachicDirectoriesPlugin будет искать node_modules в каждом каталоге request.path по очереди. Напримерrequest.path = 'dir/demo'Тогда процесс поиска node_modules таков:

dir/demo/node_modules
dir/node_modules
/node_modules

еслиdir/demo/node_modulesЕсли он существует, измените request.path и request.request.

const obj = Object.assign({}, request, {
  	path: addr, // node_module 所在的路径
  	request: "./" + request.request
  });

Для ModulesInRootPlugin по умолчанию выполняется поиск в корневом каталоге и прямая замена

  const obj = Object.assign({}, request, {
  	path: this.path,
  	request: "./" + request.request
  });

Впоследствии, поскольку request.path и request.request были изменены, он возвращается к этапу, где начинается разрешение. Но затем request.request изменился с модуля на обычный тип файла../vue.

  1. Точка разветвления с обычным процессом разрешения файлов

Найти как обычный файлdir/demo/node_module/vueЭтот процесс аналогичен обычному процессу разрешения файлов в предыдущем разделе, выполняя шаги 1–7 в предыдущем разделе, а затем инициируя описанное относительное событие (две функции FileKindPlugin и TryNextPlugin регистрируются в этом событии). Сначала введите логику FileKindPlugin, т.к.dir/demo/node_module/vueне является адресом файла, поэтому на шаге 8 FileKindPlugin в конечном итоге вернет значение undefined. В это время он войдет в следующее событие обработки TryNextPlugin, а затем вызовет событие каталога, поместивdir/demo/node_module/vueРазбирать по папкам.

  1. DirectoryExisitsPlugin

подтверждатьdir/demo/node_module/vueон существует. (ps: процесс разрешения для контекста, если папка существует здесь, он завершен.)

  1. MainFieldPlugin

По умолчанию mainField веб-пакета['browser', 'module', 'main']. Здесь по порядку, вdir/demo/node_module/vue/package.jsonНайдите соответствующее поле. vue package.json определен в

{
  "module": "dist/vue.runtime.esm.js"
}

Поэтому, когда поле будет найдено, значение request.request будет заменено на./dist/vue.runtime.esm.js. Затем вернитесь к узлу разрешения и начните новый раунд, ища нормальный файл../dist/vue.runtime.esm.jsпроцесс. Когда MainFieldPlugin выполняется и нет результата, он войдет в UseFilePlugin

  1. UseFilePlugin

Когда мы не пишем в нашем package.json browser, module, main, webpack автоматически найдет индексный файл в каталоге, и запрос станет таким

{
  //...省略其他部分
  relativePath: "./index",
  path: 'dir/demo/node_modules/vue/index'
}

Затем запустите неописанное событие RawFile.

  1. DescriptionFilePlugin/TryNextPlugin

Для нового request.path заново найдите файл описания, т.е. package.json.

  1. AppendPlugin

По очереди добавьте суффикс к 'dir/demo/node_modules/vue/index', а затем проверьте наличие файла. Процесс такой же, как после файла в предыдущей статье. Пока существующий файл не будет найден, весь процесс разрешения модуля будет завершен.

процесс разрешения загрузчика

Процесс разрешения загрузчика аналогичен процессу модуля.В качестве примера мы возьмем url-loader, а запись — это функция resolveRequestArray в NormalModuleFactory.js. будет выполняться здесьresolver.resolve, резолвер здесь — полученный ранее loaderResolver, а параметры запроса в начале процесса резолва следующие:

{
  context: {
    compiler: undefined,
    issuer: "/dir/demos/main.js"
  },
  path: "/dir/demos"
  request: "url-loader"
}

В ParsePlugin,request: "url-loader"будет проанализирован как модуль. Весь процесс такой же, как и процесс выполнения модуля в последующем процессе.

На этом процесс разрешения в webpack завершен. Кроме того, в веб-пакете есть много деталей, с которыми нужно разобраться. Из-за ограниченного места мы не будем подробно обсуждать его здесь. Вы можете внимательно попробовать его, просматривая код веб-пакета в сочетании со статьей.

От принципа к оптимизации

Каждый раз, когда файл участвует в веб-пакете, он проходит процесс разрешения. В процессе разрешения для некоторых неопределенных факторов, таких как имя суффикса, путь node_modules и т. д., будет процесс исследования, что делает всю цепочку разрешения очень длинной. Во многих оптимизациях для веб-пакета упоминается использование конфигурации разрешения для уменьшения объема поиска файлов:

  1. использовать разрешение.псевдоним

В наших ежедневных проектах разработки часто используются такие каталоги, как общие, и часто используются ссылки на файлы в общем каталоге. Например, «common/index.js». Если мы создадим псевдоним для общего каталога, во всех файлах, которые используют «common/index.js», мы можем написатьimport xx from 'common/index.js'. Из-за существования UnsafeCachePlugin, когда веб-пакет снова разрешается в «common/index.js», кеш можно использовать напрямую.

Мало того, дело в том, что цепочка парсинга укорачивается, а кеш — это только ее часть.

  1. установить разрешение.модули

Значение по умолчанию для разрешения.модули:['node_modules'], поэтому в процессе резолва модуля ./node_modules, ../node_modules, ../../node_modules и т.д. будут перебираться по очереди, то есть будет перебираться слой за слоем по пути, пока node_modules находится. можно установить напрямую

resolve.modules:[path.resolve(__dirname, 'node_modules')]

Это войдет в ModulesInRootPlugin вместо ModulesInHierachicDirectoriesPlugin, избегая накладных расходов на поиск node_modules слой за слоем.

  1. Установить resolve.alias для сторонних модулей

В процессе разрешения сторонних модулей, помимо упомянутого выше процесса поиска в каталоге node_modules, также используется разбор конфигурации в package.json. Вы можете напрямую установить псевдоним в качестве исполняемого файла, чтобы упростить весь процесс разрешения, следующим образом:

resolve.alias: {
    'vue': path.resolve(__dirname, './node_modules/vue/dist/vue.common.js')
}
  1. Установите разрешение.extensions разумно, чтобы уменьшить поиск файлов

Когда наш файл не имеет суффикса, AppendPlugin по очереди добавит суффикс в соответствии со значением в resolve.extensions, а затем найдет файл. Чтобы сократить поиск файлов, мы можем напрямую написать суффикс файла или установить значение в resolve.extensions,Перечислите как можно меньше значений,Суффикс типа файла с высокой частотой пишется впереди.

Разобравшись в деталях решения, а затем рассмотрев эти стратегии оптимизации, вы сможете лучше понять причины и достичь «знания причины и знания причины».

Рисунок (полная версия потока событий разрешения):

resolve事件流完整版