Как друзья, добро пожаловать, чтобы следовать за мнойблогилиНовостная лента. Прошлые статьи:
1. загрузчик десять вопросов
В процессе изучения загрузчика webpack я также прочитал много связанных статей в Интернете и многому научился. Однако большинство из них представляют только метод настройки загрузчика или метод написания загрузчика, а введение параметров, API и других деталей неясно.
Вот «Десять вопросов о загрузчиках», некоторые из вопросов, которые у меня возникли перед чтением исходного кода загрузчика:
- Где обрабатывается конфигурация веб-пакета по умолчанию?Есть ли у загрузчика какая-либо конфигурация по умолчанию?
- В веб-пакете есть понятие распознавателя, которое используется для анализа реального абсолютного пути к файлу модуля, поэтому загрузчик и распознаватель обычных модулей используют один и тот же?
- Мы знаем, что помимо загрузчика в конфиге можно написать еще и встроенный загрузчик, так каков же порядок выполнения встроенного загрузчика и обычного загрузчика конфига?
- в конфигурации
module.rules
Как это работает и реализуется в веб-пакете? - Как и когда загрузчики играют роль в процессе компиляции веб-пакета?
- Почему загрузчик выполняется справа налево?
- Что именно произойдет, если значение будет возвращено в поле?
- Если вы написали загрузчик, его можно использовать в функции загрузчика.
this
,здесьthis
Что это, это экземпляр веб-пакета? - в функции загрузчика
this.data
Как это достигается? - Как написать асинхронный загрузчик и как Webpack реализует асинхронный загрузчик?
Возможно, у вас возникнут подобные вопросы. Ниже я объединим часть исходного кода, относящегося к загрузчику, чтобы восстановить принципы проектирования и реализации загрузчика и ответить на эти сомнения.
2. Общий процесс работы погрузчика
Процесс компиляции веб-пакета очень сложен, но части, связанные с загрузчиками, в основном включают:
- Конфигурация загрузчика по умолчанию (веб-пакет)
- Используйте loaderResolver для разрешения путей модулей загрузчика.
- согласно с
rule.modules
Создайте набор правил RulesSet - Запустите загрузчик с помощью loader-runner
Соответствующий общий процесс выглядит следующим образом:
Прежде всего,Compiler.js
Чтобы объединить пользовательскую конфигурацию с конфигурацией по умолчанию, включена часть загрузчика.
Затем webpack создаст два ключевых объекта на основе конфигурации:NormalModuleFactory
а такжеContextModuleFactory
. Они эквивалентны двум фабрикам классов, через которыеNormalModule
а такжеContextModule
. вNormalModule
Основное внимание в этой статье уделяется классу, webpack сгенерирует соответствующий файл модуля в исходном коде.NormalModule
пример.
Создан на заводеNormalModule
Есть несколько необходимых шагов перед экземпляром, наиболее важным для загрузчика является разрешение пути загрузчика через преобразователь загрузчика.
существуетNormalModule
После того, как экземпляр будет создан, он пройдет.build()
способ сборки модуля. Первым шагом в создании модуля является использование загрузчика для загрузки и обработки содержимого модуля. Библиотека loader-runner является исполнителем загрузчика в webpack.
Наконец, выведите содержимое модуля, обработанного загрузчиком, и приступайте к последующему процессу компиляции.
Выше приведен общий процесс, связанный с загрузчиком в webpack. Далее будет подробный анализ исходного кода, и в процессе чтения и анализа исходного кода вы найдете ответ на «Десять вопросов о загрузчиках».
3. Конкретный разбор ходовой части погрузчика
3.1 конфигурация веб-пакета по умолчанию
В: 1. Где обрабатывается дефолтная конфигурация вебпака, есть ли у загрузчика дефолтная конфигурация?
Как и другие инструменты, webpack работает через настройку. С непрерывным развитием веб-пакета его конфигурация по умолчанию также постоянно меняется; и некоторые передовые методы из предыдущих версий также вошли в конфигурацию веб-пакета по умолчанию с обновлением версии.
Входной файл веб-пакетаlib/webpack.js
, установит параметры конфигурации во время компиляции в соответствии с файлом конфигурации(source code)(Предыдущий«Визуальный дисплей WebPack Internal Plug и Clooth Review 📈»Здесь же срабатывает упомянутый плагин)
options = new WebpackOptionsDefaulter().process(options);
compiler = new Compiler(options.context);
compiler.options = options;
Видно, что конфигурация по умолчанию находится вWebpackOptionsDefaulter
внутренний. Поэтому, если вы хотите просмотреть конкретное содержимое текущих элементов конфигурации веб-пакета по умолчанию, вы можете перейти кмодульПосмотреть в.
Например, вmodule.rules
Значение по умолчанию для этой части[]
; но вдобавок естьmodule.defaultRules
Элемент конфигурации хоть и не открыт для разработчиков, но содержит конфигурацию загрузчика по умолчанию(source code):
this.set("module.rules", []);
this.set("module.defaultRules", "make", options => [
{
type: "javascript/auto",
resolve: {}
},
{
test: /\.mjs$/i,
type: "javascript/esm",
resolve: {
mainFields:
options.target === "web" ||
options.target === "webworker" ||
options.target === "electron-renderer"
? ["browser", "main"]
: ["main"]
}
},
{
test: /\.json$/i,
type: "json"
},
{
test: /\.wasm$/i,
type: "webassembly/experimental"
}
]);
Также стоит упомянуть, что,
WebpackOptionsDefaulter
унаследовано отOptionsDefaulter
,а такжеOptionsDefaulter
Это инкапсулированный метод доступа к элементам конфигурации, который инкапсулирует некоторые специальные методы для управления объектами конфигурации.
3.2. СоздатьNormalModuleFactory
NormalModule
Это функция класса, которая должна быть упомянута в webpack. Модули в исходном коде будут генерировать соответствующие модули в процессе компиляции.NormalModule
пример.
NormalModuleFactory
даNormalModule
заводской класс. Он был создан вCompiler.js
осуществляется в г.Compiler.js
Это управляющий класс для основного процесса компиляции webpack.compiler.run()
Поток тела (хука) в методе выглядит следующим образом:
.run()
запуск рядаbeforeRun
,run
Дождавшись хука, он вызовет.compile()
метод, первым шагом которого является вызовthis.newCompilationParams()
СоздайтеNormalModuleFactory
пример.
newCompilationParams() {
const params = {
normalModuleFactory: this.createNormalModuleFactory(),
contextModuleFactory: this.createContextModuleFactory(),
compilationDependencies: new Set()
};
return params;
}
3.3. Определите реальный абсолютный путь загрузчика
В: 2. В веб-пакете есть понятие резолвера, который используется для анализа реального абсолютного пути к файлу модуля, поэтому один и тот же резолвер, используемый модулем загрузчика и обычным модулем (модулем исходного кода)?
существуетNormalModuleFactory
, СоздайтеNormalModule
Перед экземпляром задействованы четыре хука:
- beforeResolve
- resolve
- factory
- afterResolve
Двумя наиболее важными являются:
- Часть разрешения отвечает за разбор пути модуля загрузчика (например, какой путь к модулю загрузчика css-loader);
- Фабрика отвечает за создание на основе возвращаемого значения хука разрешения.
NormalModule
пример.
resolve
Метод, зарегистрированный на хуке, длиннее, что также включает в себя разрешение пути самого ресурса модуля.resolver
Существует два типа: loaderResolver и normalResolver.
const loaderResolver = this.getResolver("loader");
const normalResolver = this.getResolver("normal", data.resolveOptions);
Помимо загрузчика, который можно настроить в конфиге, есть еще метод записи встроенного загрузчика, поэтому анализ пути файла загрузчика тоже делится на два типа: встроенный загрузчик и загрузчик в конфиге . Хук распознавателя сначала обработает встроенный загрузчик.
3.3.1. inline loader
import Styles from 'style-loader!css-loader?modules!./styles.css';
Выше приведен пример встроенного загрузчика. где запросstyle-loader!css-loader?modules!./styles.css
.
Во-первых, webpack будет парсить нужный загрузчик из запроса(source code):
let elements = requestWithoutMatchResource
.replace(/^-?!+/, "")
.replace(/!!+/g, "!")
.split("!");
Следовательно, изstyle-loader!css-loader?modules!./styles.css
Есть два погрузчика, которые можно вывезти:style-loader
а такжеcss-loader
.
Тогда "массив загрузчика модуля парсера" будет выполняться параллельно с "самим модулем парсера", который используется здесьneo-async
эта библиотека.
neo-async
библиотека иasync
Библиотеки похожи тем, что все они предоставляют некоторые инструменты и методы для асинхронного программирования, но они болееasync
Библиотеки работают быстрее.
Формат результата, возвращаемого синтаксическим анализом, примерно следующий:
[
// 第一个元素是一个loader数组
[ {
loader:
'/workspace/basic-demo/home/node_modules/html-webpack-plugin/lib/loader.js',
options: undefined
} ],
// 第二个元素是模块本身的一些信息
{
resourceResolveData: {
context: [Object],
path: '/workspace/basic-demo/home/public/index.html',
request: undefined,
query: '',
module: false,
file: false,
descriptionFilePath: '/workspace/basic-demo/home/package.json',
descriptionFileData: [Object],
descriptionFileRoot: '/workspace/basic-demo/home',
relativePath: './public/index.html',
__innerRequest_request: undefined,
__innerRequest_relativePath: './public/index.html',
__innerRequest: './public/index.html'
},
resource: '/workspace/basic-demo/home/public/index.html'
}
]
Первый элемент — это все встроенные загрузчики, задействованные при обращении к модулю, включая абсолютный путь и элементы конфигурации файла загрузчика.
3.3.2. config loader
В: 3. Мы знаем, что помимо загрузчика в конфиге можно написать еще и встроенный загрузчик, так каков порядок выполнения встроенного загрузчика и обычного загрузчика конфига?
В приведенном выше разделе веб-пакет сначала анализирует абсолютный путь и конфигурацию встроенного загрузчика. Следующим шагом будет разбор загрузчика в конфигурационном файле(source code),Прямо сейчасmodule.rules
Часть конфигурации:
const result = this.ruleSet.exec({
resource: resourcePath,
realResource:
matchResource !== undefined
? resource.replace(/\?.*/, "")
: resourcePath,
resourceQuery,
issuer: contextInfo.issuer,
compiler: contextInfo.compiler
});
NormalModuleFactory
есть одинruleSet
Вы можете просто понять это так: он может соответствовать загрузчику, требуемому модулем, в соответствии с именем пути к модулю.RuleSet
Детали не перечислены здесь первыми, а конкретное содержание будет представлено в следующем разделе.
здесь, чтобыthis.ruleSet.exec()
Путь исходного модуля передается, а возвращаемыйresult
Это загрузчик в конфиге, соответствующий текущему модулю. Если вы знакомы с конфигурацией веб-пакета, вы будете знатьmodule.rules
есть одинenforce
поле. На основании этого поля webpack делит загрузчики на три типа: preLoader, postLoader и loader.(source code):
for (const r of result) {
if (r.type === "use") {
// post类型
if (r.enforce === "post" && !noPrePostAutoLoaders) {
useLoadersPost.push(r.value);
// pre类型
} else if (
r.enforce === "pre" &&
!noPreAutoLoaders &&
!noPrePostAutoLoaders
) {
useLoadersPre.push(r.value);
} else if (
!r.enforce &&
!noAutoLoaders &&
!noPrePostAutoLoaders
) {
useLoaders.push(r.value);
}
}
// ……
}
Наконец, используйте neo-aysnc для параллельного анализа трех типов массивов загрузчика.(source code):
asyncLib.parallel(
[
this.resolveRequestArray.bind(
this,
contextInfo,
this.context,
useLoadersPost, // postLoader
loaderResolver
),
this.resolveRequestArray.bind(
this,
contextInfo,
this.context,
useLoaders, // loader
loaderResolver
),
this.resolveRequestArray.bind(
this,
contextInfo,
this.context,
useLoadersPre, // preLoader
loaderResolver
)
]
// ……
}
Итак, каков порядок финальных загрузчиков? Следующая строка кода объясняет это:
loaders = results[0].concat(loaders, results[1], results[2]);
вresults[0]
,results[1]
,results[2]
,loader
Это postLoader, loader (обычный загрузчик конфигурации), preLoader и inlineLoader. Таким образом, порядок объединенного загрузчика следующий: post, inline, normal и pre.
Однако загрузчик выполняется справа налево, а реальный порядок выполнения загрузчика обратный, поэтому inlineLoader выполняется целиком после обычного загрузчика в конфиге.
3.3.3. RuleSet
В: 4. В конфигурации
module.rules
Как это работает и реализуется в веб-пакете?
использование веб-пакетаRuleSet
объект для соответствия загрузчикам, требуемым модулем.RuleSet
Эквивалентно фильтру правил, применяющему resourcePath ко всемmodule.rules
правила для фильтрации нужного загрузчика. Два наиболее важных метода:
- статический метод класса
.normalizeRule()
- метод экземпляра
.exec()
Компиляция Webpack создаст экземпляр конфигурации пользователя и конфигурации по умолчанию.RuleSet
. Во-первых, через статический метод на нем.normalizeRule()
Преобразуйте значение конфигурации в нормализованный тестовый объект; он также хранитthis.references
Свойство — тип карты хранилища, а ключ — тип и расположение загрузчика в конфигурации, например,ref-2
Представляет третий в массиве конфигурации загрузчика.
p.s. Если вы распечатаете некоторые поля, связанные с запросом, в NormalModule на хуке в .compilation, те модули, которые используют загрузчик, будут выглядеть аналогично
ref-
ценность . Отсюда вы можете увидеть, использует ли модуль загрузчик и какое правило конфигурации срабатывает.
созданныйRuleSet
Его можно использовать для получения соответствующего загрузчика для каждого модуля. это созданоRuleSet
это то, что мы упоминали вышеNormalModuleFactory
на экземпляреthis.ruleSet
Атрибуты. Фабрика каждый раз создает новыйNormalModule
будет вызван, когдаRuleSet
пример.exec()
метод, загрузчик будет помещен в массив результатов только при выполнении различных тестовых условий.
3.4 Запуск загрузчика
3.4.1 Время работы загрузчика
В: 5. Как и когда работает загрузчик в процессе компиляции веб-пакета?
После разрешения абсолютного пути загрузчика вNormalModuleFactory
изfactory
Хук создаст текущий модульNormalModule
объект. На данный момент работа по предзаказу загрузчика практически завершена, и далее предстоит собственно запуск каждого загрузчика.
Все мы знаем, что запуск загрузчика для чтения и обработки модулей — это первый шаг в обработке модуля webpack. Но если дело доходит до подробного времени выполнения, это включает в себя компиляцию веб-пакета.compilation
этот очень важный объект.
webpack компилируется в измерении входа,compilation
Есть важный метод -.addEntry()
, модуль будет построен на основе записи..addEntry()
метод называется._addModuleChain()
выполнит ряд методов модуля(source code)
this.semaphore.acquire(() => {
moduleFactory.create(
{
// ……
},
(err, module) => {
if (err) {
this.semaphore.release();
return errorAndCallback(new EntryModuleNotFoundError(err));
}
// ……
if (addModuleResult.build) {
// 模块构建
this.buildModule(module, false, null, null, err => {
if (err) {
this.semaphore.release();
return errorAndCallback(err);
}
if (currentProfile) {
const afterBuilding = Date.now();
currentProfile.building = afterBuilding - afterFactory;
}
this.semaphore.release();
afterBuild();
});
}
}
)
}
Среди них, для модулей, которые не были построены, он в конечном итоге будет вызван дляNormalModule
объект.doBuild()
метод. в то время как строительный модуль (.doBuild()
), первый шаг –запустить все загрузчики.
В это время на сцену выходит грузчик-раннер.
3.4.2 loader-runner — библиотека выполнения загрузчика
Q:.6 загрузчик Почему выполняются справа налево?
Webpack удаляет рабочий инструмент загрузчика и становится независимымбиблиотека загрузчика-раннера. Следовательно, вы можете написать загрузчик и использовать автономный загрузчик-раннер для проверки эффекта загрузчика.
Загрузчик-раннер разделен на две части: loadLoader.js и LoaderRunner.js.
loadLoader.js — это совместимый загрузчик модулей, который может загружать определения модулей, такие как cjs, esm или SystemJS. LoaderRunner.js — это основная часть работы модуля загрузчика. подвергается в.runLoaders()
Метод — это метод запуска для запуска загрузчика.
Если вы писали или знаете, как написать загрузчик, вы точно знаете, что каждый модуль загрузчика поддерживает.pitch
вышеприведенный метод будет иметь приоритет над фактическим выполнением метода загрузчика. На самом деле официальный веб-пакет также дает диаграмму последовательности выполнения методов питча и загрузчика:
|- a-loader `pitch`
|- b-loader `pitch`
|- c-loader `pitch`
|- requested module is picked up as a dependency
|- c-loader normal execution
|- b-loader normal execution
|- a-loader normal execution
Эти две стадии (шаговая и нормальная) соответствуют друг другу в загрузчике-раннере.iteratePitchingLoaders()
а такжеiterateNormalLoaders()
два метода.
iteratePitchingLoaders()
будет выполняться рекурсивно и записывать загрузчикаpitch
статус и выполняется в настоящее времяloaderIndex
(loaderIndex++
). Когда будет достигнут максимальный порядковый номер загрузчика, фактический модуль будет обработан:
if(loaderContext.loaderIndex >= loaderContext.loaders.length)
return processResource(options, loaderContext, callback);
когдаloaderContext.loaderIndex
Когда значение достигает длины всего массива загрузчиков, это указывает на то, что все питчи выполнены (выполняется последний загрузчик), после чего будет выполнен вызов.processResource()
для управления ресурсами модуля. В основном включают: добавление модуля в качестве зависимости и чтение содержимого модуля. Затем он будет выполняться рекурсивноiterateNormalLoaders()
и выполнятьloaderIndex--
операция, поэтому загрузчик выполняется «в обратном порядке».
Далее мы обсуждаем несколько деталей погрузчика-бегуна:
В: 7. Что произойдет, если значение будет возвращено в поле?
Официальный сайт говорит:
if a loader delivers a result in the pitch method the process turns around and skips the remaining loaders
В этом описании сказано, что возврат значения в поле пропускает остальные загрузчики. Это утверждение является относительно грубым, и есть несколько деталей, которые необходимо объяснить:
Во-первых, только когдаloaderIndex
Достигнута максимальная длина массива, то есть он будет выполнен только после раскачки всех загрузчиков.processResource()
.
if(loaderContext.loaderIndex >= loaderContext.loaders.length)
return processResource(options, loaderContext, callback);
Следовательно, помимо пропуска остальной части загрузчика, возвращаемое значение в поле не только сделает.addDependency()
Не запускать (без добавления ресурса модуля) и не может прочитать содержимое файла модуля. Загрузчик обработает значение, возвращенное PITCH в «Содержимое файла», и вернет его в WebPack.
В: 8. Если вы написали загрузчик, его можно использовать в функции загрузчика
this
,здесьthis
Что это, это экземпляр веб-пакета?
На самом деле здесьthis
Ни экземпляры webpack, ни экземпляры компилятора, компиляции, normalModule и т.д. ноВызовloaderContext
конкретный объект загрузчика-бегуна.
каждый звонокrunLoaders()
метод, если контекст не передан явно, новый будет создан по умолчаниюloaderContext
. Таким образом, различные API-интерфейсы загрузчика (обратный вызов, данные, loaderIndex, addContextDependency и т. д.), упомянутые на официальном веб-сайте, являются свойствами объекта.
В: 9. В функции загрузчика
this.data
Как это достигается?
знать в загрузчикеthis
На самом делеloaderContext
объект, тоthis.data
Реализация на самом делеloaderContext.data
реализация(source code):
Object.defineProperty(loaderContext, "data", {
enumerable: true,
get: function() {
return loaderContext.loaders[loaderContext.loaderIndex].data;
}
});
здесь определяет.data
(магазин) аксессуар. Видно, что вызовthis.data
, другой нормальный загрузчик из-заloaderIndex
Разные, вы получите разные значения, а формальные параметры метода основного тонаdata
Это и есть данные под разными загрузчиками(source code).
runSyncOrAsync(
fn,
loaderContext,
[loaderContext.remainingRequest, loaderContext.previousRequest, currentLoaderObject.data = {}],
function(err) {
// ……
}
);
runSyncOrAsync()
массив в[loaderContext.remainingRequest, loaderContext.previousRequest, currentLoaderObject.data = {}]
- входной параметр метода основного тона, аcurrentLoaderObject
текущийloaderIndex
Объект загрузчика для ссылки.
Поэтому, если вы хотите сохранить «полные» данные, рассмотрите возможность сохранения вthis
В других свойствах или путем изменения loaderIndex можно получить данные о других загрузчиках (сравните хак).
Q: 10. Как написать асинхронный загрузчик и как Webpack реализует асинхронный загрузчик?
Фактическое выполнение шага и нормального загрузчика находится вrunSyncOrAsync()
в этом методе.
Согласно документации веб-пакета, когда мы вызываемthis.async()
, он превратит загрузчик в асинхронный погрузчик и вернет асинхронный обратный вызов.
В конкретной реализации,runSyncOrAsync()
есть один внутриisSync
переменная, по умолчаниюtrue
; когда мы звонимthis.async()
, будет установлено значениеfalse
и возвращаетinnerCallback
В качестве уведомления обратного вызова после асинхронного выполнения:
context.async = function async() {
if(isDone) {
if(reportedError) return; // ignore
throw new Error("async(): The callback was already called.");
}
isSync = false;
return innerCallback;
};
Обычно мы используемthis.async()
Обратный вызов вернулся, чтобы уведомить об асинхронном завершении, но на самом деле выполнениеthis.callback()
Тот же эффект:
var innerCallback = context.callback = function() {
// ……
}
В то же время вrunSyncOrAsync()
, ТолькоisSync
определены какtrue
Когда функция загрузчика выполняется (синхронно), обратный вызов будет называться обратно для продолжения погрузчика-бегуна.
if(isSync) {
isDone = true;
if(result === undefined)
return callback();
if(result && typeof result === "object" && typeof result.then === "function") {
return result.catch(callback).then(function(r) {
callback(null, r);
});
}
return callback(null, result);
}
Увидев это, вы обнаружите, что в коде есть место, которое будет определять, является ли возвращаемое значение обещанием (typeof result.then === "function"
), если это обещание, обратный вызов будет вызываться асинхронно. Поэтому, чтобы получить асинхронный загрузчик, кроме упомянутых в документации вебпакаthis.async()
метод, вы также можете напрямую вернуть Promise.
4. Эпилог
Выше приведен анализ исходного кода соответствующей части загрузчика webapck. Я полагаю, что к этому моменту вы уже ответили на первые «десять вопросов загрузчика». Я надеюсь, что эта статья поможет вам узнать больше о реализации загрузчика в дополнение к обучению настройке загрузчика и написанию простого загрузчика.
В процессе чтения исходного кода могут быть некоторые ошибки.Приглашаем к общению друг с другом.
Попрощайтесь с «инженером по настройке веб-пакета»
webpack — это мощный и сложный интерфейсный инструмент автоматизации. Одной из особенностей является то, что конфигурация сложная, что также делает популярным шутливое название «инженер по настройке веб-пакета»🤷 Однако вас действительно устраивает только игра с конфигурацией веб-пакета?
Очевидно нет. В дополнение к изучению того, как использовать веб-пакет, нам нужно углубиться в веб-пакет и изучить дизайн и реализацию каждой части. Даже если однажды веб-пакет «устарел», некоторые из его проектов и реализаций по-прежнему будут иметь ценность для изучения и справочное значение. Поэтому в процессе изучения webpack я подытожу серию статей [webpack advanced] и поделюсь ими с вами.
Заинтересованные студенты приглашаются к общению и вниманию!
Прошлые статьи:
- [Расширенный веб-пакет] Модульный дизайн и реализация клиентской среды выполнения
- [Расширенный веб-пакет] Используйте babel, чтобы избежать зависимостей модуля времени выполнения компиляции веб-пакета.
- [Расширенный веб-пакет] Визуально отображайте взаимосвязь между плагинами и хуками внутри веб-пакета 📈