Автор: Цуй Цзин
вводить
Одной из характеристик веб-пакета является то, что он обрабатывает все модули, мы можем разделить логику на разные файлы, а затем экспортировать и импортировать по модульной схеме. Теперь модуль ES6 является наиболее часто используемым модульным решением, поэтому вы должны были написатьimport './xxx'
илиimport 'something-in-nodemodules'
илиimport '@/xxx'
(Символ @ устанавливается с помощью настройки псевдонима в конфигурации веб-пакета). webpack обрабатывает импорт этих модулейimport
Когда есть важный шаг, вот как правильно найти'./xxx'
,'something-in-nodemodules'
или'@/xxx'
И так, какому файлу соответствует. Этот шаг представляет собой логику, которую должна обрабатывать часть разрешения.
На самом деле не только модули в исходниках нужно резолвить, в том числе и загрузчик, в общем процессе обработки вебпака, процесс резолвинга неотделим от пути к файлу.
В то же время webpack имеет конфигурацию разрешения в файле конфигурации, которая может соответствующим образом настроить процесс разрешения, например, установить расширение файла, найти искомый каталог и т. д. (подробнее см.Официальное введение).
Ниже мы в основном представим процесс разрешения для общих файлов и основной процесс разрешения для загрузчика.
Введение в основной процесс решения
Сначала подготовьте простую демонстрацию
import { A } from './a.js'
Затем посмотрите на основной процесс для этой демонстрации. В обзорной статье одной из серий веб-пакетов есть общая блок-схема компиляции веб-пакета.На рисунке видно, что перед тем, как веб-пакет обработает каждый файл, будет выполняться процесс разрешения, чтобы найти полную информацию о пути к файлу.
Запись процесса разрешения в исходном коде веб-пакета находится на заводской стадии. Событие factory запускает функции в NormalModuleFactory. Сначала поместите приблизительную общую блок-схему и получите приблизительную диаграмму кадра, прежде чем углубляться в исходный код.
Далее мы начинаем с файла 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); вы получите функцию. Затем следующим шагом является выполнение функции для получения результата преобразователя. В функции распознавателя она разделена на два основных процесса: загрузчик и файл в целом.
процесс загрузки
- Получите часть запроса встроенного загрузчика. Например, для следующего письма
import Styles from 'style-loader!css-loader?modules!./styles.css';
будет разобран изstyle-loader
иcss-loader
. Поскольку этот шаг предназначен только для разбора пути, он не заботится о части конфигурации загрузчика.
-
Получите экземпляр обработки преобразователя типа загрузчика, то есть
const loaderResolver = this.getResolver("loader");
-
Используйте loaderResolver для обработки каждого загрузчика по очереди, чтобы получить путь к исполняемому файлу.
документооборот
-
Получить экземпляр обработки распознавателя обычного файла, то есть код
const normalResolver = this.getResolver("normal", data.resolveOptions);
-
Обработайте файл с помощью 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. Конкретный процесс получения экземпляра преобразователя в этом методе выглядит следующим образом.
На приведенном выше рисунке сначала получите параметры в соответствии с разными типами. Так где же существуют эти конфигурации опций?
настройка опций в 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
метод, и весь процесс разрешения можно рассматривать как конкатенацию событий.Когда все объединенные события выполняются, разрешение заканчивается.
Ключевая часть объединения этих событий в цепочку находится в 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);
});
// ...
Разобравшись с приведенным выше кодом, вы можете получить полную схему потока событий (на картинке ниже приведен упрощенный вариант, а прикрепленный вариант прилагается)
Объединив приведенную выше диаграмму и демонстрацию, давайте пошагово рассмотрим, что делает каждая ссылка в этом потоке событий. (ps: на следующих шагах будет задействован параметр запроса. Этот параметр проходит через всю логику обработки событий и сохраняет всю информацию о разрешении)
- UnsafeCachePlugin
Добавьте уровень кэширования, потому что webpack включает в себя множество процессов разрешения в процессе упаковки. Следовательно, для повышения эффективности необходимо добавить слой кэша. webpack включает UnsafeCache по умолчанию.
-
ParsePlugin
Предварительно разбираем путь, определяем, является ли он модулем/директорией/файлом, и сохраняем результат в параметре запроса.
-
ОписаниеFilePlugin и NextPlugin
DescriptionFilePlugin будет искать файл описания, и по умолчанию он будет искать package.json. Сначала он будет искаться в директории request.path, если нет, то будет искаться послойно по пути. Наконец, информация package.json и информация о каталоге/пути, где он находится, считываются и сохраняются в запросе. У нас есть файл package.json в корневом каталоге демо, поэтому файлы в корневом каталоге будут получены здесь.
NextPlugin действует как соединение, а внутренняя логика заключается в непосредственном вызове doResolve и последующем запуске следующего события. Если файл package.json не найден в DescriptionFilePlugin, вводится NextPlugin, и поток событий продолжается.
-
AliasPlugin/AliasFieldPlugin
Этот шаг запускает обработку псевдонимов. Поскольку AliasFieldPlugin зависит от конфигурации package.json, этот шаг помещается после DescriptionFilePlugin. Помимо записи некоторых псевдонимов в конфигурационный файл, webpack также будет иметь некоторые встроенные псевдонимы, для каждой конфигурации псевдонимов будет прописана функция. На этом шаге будут выполняться все функции одна за другой. Если вы нажмете на конфигурацию псевдонима или aliasField, вы войдете в ветвь красной пунктирной линии на рисунке выше. Замените содержимое параметра запроса новым псевдонимом и снова запустите процесс разрешения. Если попадания нет, введите следующую функцию обработки ModuleKindPlugin
- ModuleKindPlugin
в соответствии сrequest.module
Значение принимает разные ветви. Если это модуль, то введите логику rawModule позже. В результате, полученном в предыдущем ParsePluginrequest.module
заfalse
, поэтому здесь возвращается undefined, переходите к следующему обработчику.
- JoinRequestPlugin
Объедините путь и запрос в запросе, а также соедините относительный путь и запрос в запросе, чтобы получить два полных пути. В этой демонстрации вы получите/Users/didi/dist/webpackdemo/webpack-demos/demo01/a.js
и./demo01/a.js
- DescriptionFilePlugin
Это снова войдет в DescriptionFilePlugin. Однако отличие от первой записи в том, что request.path в это время становится /dir/demo/a.js`. Поскольку путь изменился, вам нужно снова найти package.json.
Затем запустите событие descriptionRelative, чтобы перейти к следующему процессу.
- FileKindPlugin
Определите, является ли это каталогом, если да, верните неопределенное значение, введите следующий tryNextPlugin, а затем введите ветку каталога. В противном случае он указывает файл и переходит к событию rawFile. В нашей демонстрации это будет идти в ветку rawFile.
- 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
Ветвь, чтобы продолжить поток событий.
- AliasPlugin/AliasFields/ConcorModulesPlugin/SymlinkPlugin
В это время снова будет введена логика обработки псевдонима.Обратите внимание, что на этом этапе многие псевдонимы, поставляемые с веб-пакетом, больше не будут существовать.
Как и прежде, ConcorModulesPlugin по-прежнему отсутствует.
SymlinkPlugin используется для обработки наличия ссылки в пути. Поскольку веб-пакет по умолчанию анализирует в соответствии с реальным путем, здесь будет проверяться каждый сегмент пути, и если встречается ссылка, она будет заменена реальным путем. Поскольку путь изменился, он вернется кrelative
сцена.
Если в пути нет ссылки, введите FileExistsPlugin
- FileExistsPlugin
читатьrequest.path
файл, чтобы узнать, существует ли файл. Если файл существует, перейдите к событию existsFile.
- NextPlugin/ResultPlugin
Подключитесь через NextPlugin, а затем введите событие Resolved. Затем выполните ResultPlugin, на этом весь процесс разрешения завершен, и запрос сохраняет результат разрешения.
процесс разрешения модуля
В webpack помимо импорта файла мы также импортируем модуль, напримерimport Vue from 'vue'
. Затем в это время веб-пакет должен правильно найти, где находится файл записи, соответствующий vue. Для vue ParsePlugin приводит кrequest.module = true
, а затем в ModuleKindPlugin войдет в ветку rawModule на рисунке выше. мы будем использоватьimport Vue from 'vue'
Для демонстрации взгляните на процесс ветки rawModule.
- ModuleAppendPlugin/TryNextPlugin
ModuleAppendPlugin похож на описанный выше AppendPlugin, но с добавленным суффиксом. TryNextPlugin входит в событие модуля
- 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
.
- Точка разветвления с обычным процессом разрешения файлов
Найти как обычный файлdir/demo/node_module/vue
Этот процесс аналогичен обычному процессу разрешения файлов в предыдущем разделе, выполняя шаги 1–7 в предыдущем разделе, а затем инициируя описанное относительное событие (две функции FileKindPlugin и TryNextPlugin регистрируются в этом событии). Сначала введите логику FileKindPlugin, т.к.dir/demo/node_module/vue
не является адресом файла, поэтому на шаге 8 FileKindPlugin в конечном итоге вернет значение undefined. В это время он войдет в следующее событие обработки TryNextPlugin, а затем вызовет событие каталога, поместивdir/demo/node_module/vue
Разбирать по папкам.
- DirectoryExisitsPlugin
подтверждатьdir/demo/node_module/vue
он существует. (ps: процесс разрешения для контекста, если папка существует здесь, он завершен.)
- 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
- UseFilePlugin
Когда мы не пишем в нашем package.json browser, module, main, webpack автоматически найдет индексный файл в каталоге, и запрос станет таким
{
//...省略其他部分
relativePath: "./index",
path: 'dir/demo/node_modules/vue/index'
}
Затем запустите неописанное событие RawFile.
- DescriptionFilePlugin/TryNextPlugin
Для нового request.path заново найдите файл описания, т.е. package.json.
- 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 и т. д., будет процесс исследования, что делает всю цепочку разрешения очень длинной. Во многих оптимизациях для веб-пакета упоминается использование конфигурации разрешения для уменьшения объема поиска файлов:
- использовать разрешение.псевдоним
В наших ежедневных проектах разработки часто используются такие каталоги, как общие, и часто используются ссылки на файлы в общем каталоге. Например, «common/index.js». Если мы создадим псевдоним для общего каталога, во всех файлах, которые используют «common/index.js», мы можем написатьimport xx from 'common/index.js'
. Из-за существования UnsafeCachePlugin, когда веб-пакет снова разрешается в «common/index.js», кеш можно использовать напрямую.
Мало того, дело в том, что цепочка парсинга укорачивается, а кеш — это только ее часть.
- установить разрешение.модули
Значение по умолчанию для разрешения.модули:['node_modules']
, поэтому в процессе резолва модуля ./node_modules, ../node_modules, ../../node_modules и т.д. будут перебираться по очереди, то есть будет перебираться слой за слоем по пути, пока node_modules находится. можно установить напрямую
resolve.modules:[path.resolve(__dirname, 'node_modules')]
Это войдет в ModulesInRootPlugin вместо ModulesInHierachicDirectoriesPlugin, избегая накладных расходов на поиск node_modules слой за слоем.
- Установить resolve.alias для сторонних модулей
В процессе разрешения сторонних модулей, помимо упомянутого выше процесса поиска в каталоге node_modules, также используется разбор конфигурации в package.json. Вы можете напрямую установить псевдоним в качестве исполняемого файла, чтобы упростить весь процесс разрешения, следующим образом:
resolve.alias: {
'vue': path.resolve(__dirname, './node_modules/vue/dist/vue.common.js')
}
- Установите разрешение.extensions разумно, чтобы уменьшить поиск файлов
Когда наш файл не имеет суффикса, AppendPlugin по очереди добавит суффикс в соответствии со значением в resolve.extensions, а затем найдет файл. Чтобы сократить поиск файлов, мы можем напрямую написать суффикс файла или установить значение в resolve.extensions,Перечислите как можно меньше значений,Суффикс типа файла с высокой частотой пишется впереди.
Разобравшись в деталях решения, а затем рассмотрев эти стратегии оптимизации, вы сможете лучше понять причины и достичь «знания причины и знания причины».
Рисунок (полная версия потока событий разрешения):