Блок-схема работы Webpack
Нарисуйте краткую блок-схему результатов моего исследования. Если вам интересно ее прочитать, вернитесь и посмотрите на эту блок-схему.
Порядковый номер текста в поле на картинке - это порядок работы.
Преимущества изучения исходного кода Webpack
- Понять запущенный процесс Webpack
- Понимание исходного кода — основа понимания плагинов Webpack.
- Изучите навыки и идеи в исходных кодах WebPack
Думаю над написанием плагина
Написать плагин так же просто, как:
class TestPlugin {
apply(compiler) {
console.log('compiler');
compiler.hooks.compilation.tap('TestPlugin', function (compilation) {
console.log('compilation', compilation);
})
}
}
// 导出 Plugin
module.exports = TestPlugin;
Благодаря нашему предыдущему пониманию Tapable мы знаем, что можем прослушивать соответствующие события класса Tapable через ловушки и выполнять соответствующую обработку.
Tapable является обязательным условием для понимания исходного кода Webpack, вы можете прочитать"Исследования использования веб-пакетов"а также"Исследование исходного кода Webpack"Учить.
Ключевой вопрос при написании плагинов заключается не в регистрации хуков, а в том, что такое компилятор и компилятор.Хуки раскрывают нам эти два объекта, позволяя нам манипулировать ими произвольно. Но мы до сих пор не знаем, какие переменные, методы и механизм работы есть у этих двух объектов, и только понимая это, мы можем писать плагины.
Подводя итог, если вы хотите понять плагины, вы должны понимать исходный код и понимать работающий процесс Webpack.
от входа
Сравнивая Webpack с волшебной коробкой, что мы вкладываем (вводим)? Что вывозится (выводится)?
Входные данные — это файл конфигурации + исходный код, а выходные данные — это пакетный файл.
Запись Webpack это функция webpack.Давайте посмотрим.Я опущу все несущественное,оставив только основной процесс и основной код:
webpack = (options) => {
// 1,第一步就是整合options
// options就是配置,有我们配置文件中的配置,加上Webpack默认的配置。这些配置指导webpack后续如何运行,比如从哪里开始读取源代码文件,bundle文件输出到哪里,如何进行代码分割等等等。
...
// 2, 第二步实例化compiler对象
compiler = new Compiler(options.context);
...
// 3,实例化所有的(内置的和我们配置的)Webpack插件。调用它们的apply方法。
// 4, 返回compiler对象
return compiler;
}
// 使用,这里模仿webpack-cli中的代码,相当于在命令行里输入webpack。
const options = require("./webpack.config.js");
const compiler = webpack(options);
compiler.run();
Основная логика функции webpack примерно такая же. Суть в том, чтобы сгенерировать компилятор и вернуть его. Затем, получив экземпляр компилятора извне, запустите его.
Внутри функции webpack есть четыре шага. Здесь мы говорим о третьем шаге, создании экземпляра плагина. Плагины не создаются, и конфигурация может контролироваться. См. код ниже:
// WebpackOptionsApply.js
if (options.optimization.removeAvailableModules) {
const RemoveParentModulesPlugin = require("./optimize/RemoveParentModulesPlugin");
new RemoveParentModulesPlugin().apply(compiler);
}
Если в нашей конфигурации оптимизация.removeAvailableModules неверна, подключаемый модуль RemoveParentModulesPlugin не будет создан.
Однако существует подключаемый модуль, который необходимо создать, это NodeEnvironmentPlugin.Исходный код записывается непосредственно после компилятора = new Compiler(options.context);new NodeEnvironmentPlugin().apply(compiler);
Давайте посмотрим на код этого плагина:
class NodeEnvironmentPlugin {
apply(compiler) {
compiler.inputFileSystem = new CachedInputFileSystem(
new NodeJsInputFileSystem(),
60000
);
const inputFileSystem = compiler.inputFileSystem;
compiler.outputFileSystem = new NodeOutputFileSystem();
compiler.watchFileSystem = new NodeWatchFileSystem(
compiler.inputFileSystem
);
compiler.hooks.beforeRun.tap("NodeEnvironmentPlugin", compiler => {
if (compiler.inputFileSystem === inputFileSystem) inputFileSystem.purge();
});
}
}
module.exports = NodeEnvironmentPlugin;
Не обращайте внимания на значение каждой строки здесь, мы понимаем, что ее функция такова: она инкапсулирует API для доступа к файлам.
Очевидно, этот плагин дает компилятору возможность доступа к файловым объектам, что является обязательной возможностью для Webpack.Вы не можете упаковывать без доступа к файлам. Таким образом, NodeEnvironmentPlugin является обязательным для использования плагином.
Подводя итог, можно сказать, что существует два типа подключаемых модулей, один из которых является вишенкой на торте, например, различные подключаемые модули оптимизации. Один из них должен использоваться в процессе Webpack, например этот NodeEnvironmentPlugin и EntryOptionPlugin, которые будут упомянуты позже, и все они являются частью процесса. Мы смотрим на процесс и должны понять эти необходимые плагины.
Обязанности функций webpack
Резюме: чтение конфигурации, компилятор экземпляров, создание экземпляров и монтирование плагинов.
compiler
Поняв логику функции webpack, давайте посмотрим на логику в компиляторе. После того, как мы знаем, что он создан, вызывается метод run. Посмотрите непосредственно на метод запуска:
// Compiler.js
class Compiler extends Tapable {
constructor(context) {
this.hooks = {
...
beforeRun,
run
}
}
...
run(callback) {
...
this.hooks.beforeRun.callAsync(this, err => {
this.hooks.run.callAsync(this, err => {
...
this.compile(onCompiled);
});
});
}
}
Основная логика метода run заключается в том, чтобы сначала вызвать хук beforeRun, который, как мы знаем, зарегистрирован вышеприведенным NodeEnvironmentPlugin. В компилятор добавлена функция доступа к файлам.
Следующим шагом является вызов хука запуска. После вызова хука запуска будет вызван метод компиляции.
// Compiler.js
class Compiler extends Tapable {
...
createCompilation() {
return new Compilation(this);
}
newCompilation(params) {
const compilation = this.createCompilation();
...
this.hooks.thisCompilation.call(compilation, params);
this.hooks.compilation.call(compilation, params);
return compilation;
}
compile(callback) {
this.hooks.beforeCompile.callAsync(params, err => {
this.hooks.compile.call(params);
const compilation = this.newCompilation(params);
this.hooks.make.callAsync(compilation, err => {
compilation.finish(err => {
compilation.seal(err => {
this.hooks.afterCompile.callAsync(compilation, err => {
return callback(null, compilation);
});
});
});
});
});
}
}
Вы можете видеть, что основная логика функции компиляции состоит в том, чтобы вызвать ловушку beforeCompile, затем вызвать ловушку компиляции, а затем создать экземпляр объекта компиляции.В процессе создания экземпляра вызываются две ловушки thisCompilation и компиляция. Затем выполните хук make. После выполнения make преобразование модуля завершено, и вот-вот начнется пакет уплотнения. После завершения печати вызывается хук afterCompile, и семантика этого afterCompile может быть проанализирована, и компиляция окончена.
Вернемся к методу run. После завершения компиляции вызывается callback-функция onCompiled. Эта callback-функция отвечает за вывод. Код выглядит следующим образом:
const onCompiled = (err, compilation) => {
if (this.hooks.shouldEmit.call(compilation) === false) {
...
this.hooks.done.callAsync(stats, err => {
...
});
return;
}
this.emitAssets(compilation, err => {
this.hooks.done.callAsync(stats, err => {
...
});
return;
});
};
Сначала вызовите хук shouldEmit и вызовите хук done напрямую, если компиляция не удалась, указывая на конец. Если компиляция прошла успешно, вызывается метод emitAssets.EmmitAssets внутренне вызывает хук emit, выполняет вывод файла и, наконец, вызывает done для успешного завершения вывода.
Обязанности объекта компилятора
Резюме: Запустите компиляцию и управляйте выводом. Создайте экземпляр объекта компиляции и используйте плагин make hook, чтобы заставить его работать. Компиляция модулей выполняется объектом компиляции.
Разобрать стек вызовов объекта компилятора: run->compile->onCompiled
- Хуки, срабатывающие в функции запуска: beforeRun, run
- Хуки, срабатывающие в функции компиляции: beforeCompile, compile, thisCompilation,compile, make, afterCompile
- Хуки, срабатывающие в функции onCompiled: should-emit, emit, done.
compilation
Зная объект компилятора, давайте посмотрим на объект компиляции.
Изучение компиляции занимает относительно много времени, потому что очень мало вещей, связанных с функцией веб-пакета и компилятором.Веб-пакет связан только с компилятором, а компилятор связан только с компиляцией, поэтому проще сортировать вне.
Но если вы хотите понять компиляцию, вам нужно выяснить взаимосвязь между компиляцией, moduleFactory, модулем и тремя объектами.Логика сложнее.Давайте посмотрим.
Эта диаграмма представляет собой схематическое изображение основных используемых объектов и их обязанностей на этапе построения цепочки модулей компиляции.Запись модуля компиляции компиляции
Из хука make компилятора мы можем видеть из метода compile выше, что после того, как объект компиляции создан, он ничего с ним не делает, а напрямую вызывает хук make, и подключаемые модули, связанные с записью, монтируются в hook are , компиляция операции, давайте посмотрим:
class SingleEntryPlugin {
constructor(context, entry, name) {
this.context = context;
this.entry = entry;
this.name = name;
}
apply(compiler) {
compiler.hooks.compilation.tap(
"SingleEntryPlugin",
(compilation, { normalModuleFactory }) => {
compilation.dependencyFactories.set(
SingleEntryDependency,
normalModuleFactory
);
}
);
compiler.hooks.make.tapAsync(
"SingleEntryPlugin",
(compilation, callback) => {
const { entry, name, context } = this;
const dep = SingleEntryPlugin.createDependency(entry, name);
compilation.addEntry(context, dep, name, callback);
}
);
}
static createDependency(entry, name) {
const dep = new SingleEntryDependency(entry);
dep.loc = { name };
return dep;
}
}
Здесь в качестве примера используется SingleEntryPlugin.Этот плагин будет использоваться при настройке одиночной записи.Плагин будет монтировать callback-функцию на хук make. Он не только монтирует хук make, но и монтирует хук компиляции. Этот хук вызывается перед хуком make и добавляет значение в dependencyFactories объекта компиляции. Значение представляет собой пару ключ-значение, ключ — SingleEntryDependency, а значение — normalModuleFactory.normalModuleFactory — это тип фабрики модулей, который мы будем использовать позже при создании модулей.
Webpack обрабатывает асинхронность с помощью обратных вызовов
Оглядываясь назад на обратный вызов хука make, обратный вызов имеет два параметра: компиляцию и обратный вызов. Здесь следует отметить, что для асинхронных задач мне нравится писать обещания и ожидания каждый день, но в Webpack все возможности — это обратные вызовы.Исходный код легко понять, когда вы привыкли видеть это выражение, например:
// 对于异步,我们会写
const { err,module } = await moduleFactory.create(params);
// 但源码中都是这样的,在回调中拿到调用的返回值
moduleFactory.create(params,(err,module)=> { ... })
Основной процесс компиляции модуля
Начиная с плагина хука make, плагин может управлять компиляцией и вызывать addEntry компиляции. Работа по компиляции начинается с входного файла. Параметр dep должен найти normalModuleFactory в компиляции.dependencyFactories.
Что делает метод addEntry
- Вызовите хук addEntry.
- Вызовите метод _addModuleChain и выполните обратный вызов addEntry после вызова, чтобы уведомить обработчик make о завершении компиляции подключаемого модуля.
Что делает метод _addModuleChain
- Как видно из названия, этот метод заключается в добавлении цепочки модулей. То есть после его выполнения модули строятся и формируется цепочка.
- Согласно dep, чтобы найти moduleFactory, то, что мы нашли, это normalModuleFactory.
- Вызовите метод moduleFactory.create. Получите модуль в возвращаемом значении, где модуль является объектом типа NormalModule.
- Получив объект модуля, вызовите метод buildModule. Этот метод вызывает хук buildModule, который затем вызывает собственный метод сборки модуля. Метод сборки заключается в вызове загрузчика, сборке модуля и получении логики зависимостей.
- Получите скомпилированный модуль и вызовите метод afterBuild. В afterBuild определите, от каких модулей зависит модуль, рекурсивно создайте их с помощью фабрики модулей и повторите процессы 3, 4 и 5. Пока все связанные модули не будут собраны, мы получим цепочку модулей.
построить в NormalModule
Давайте подробнее рассмотрим метод сборки в вышеуказанных 4 шагах.DoBuild вызывается внутри, а runLoaders вызывается в doBuild.Как видно из названия метода, именно здесь загрузчик используется при построении модуля.
Сборка модуля заканчивается
То есть завершается выполнение addEntry, завершается выполнение SingleEntryPlugin и завершается вызов make hook. Пришло время выполнить функцию обратного вызова хука make.
Оптимизация сборки и упаковки чанков
Метод компиляции seal вызывается в обратном вызове make hook. Начинает процесс оптимизации построения и упаковки чанков. Можно сказать, что увидев здесь, мы неотделимы от процесса выполнения Webpack.
Что делает метод уплотнения.
- Вызывая крюк уплотнения, можно сказать, что крючок в методе уплотнения показывает свою силу. Метод уплотнения просто вызывает различные крючки. Реальная работа по построению и оптимизации выполняется плагином.
- Циклический вызов хуков
- Вызовите хук afterOptimizeDependencies.
- Вызвать хук beforeChunks. Из названия мы также можем видеть оптимизацию зависимостей, сделанную выше, и здесь мы начинаем строить Чанк.
- Вызвать хук afterChunks.
- Вызов хука оптимизации.
- Вызовите хуки optimizeModulesBasic, optimizeModules, optimizeModulesAdvanced, afterOptimizeModules. Модуль оптимизирован здесь. , параметр this.modules.
- Вызовите optimizeChunksBasic, optimizeChunks, optimizeChunksAdvanced, afterOptimizeChunks. Здесь оптимизируется чанк, а параметр — this.chunkGroups.
- Серия вызовов хуков не будет повторяться, и мы будем изучать ее по мере необходимости.
Короче говоря, метод уплотнения завершает оптимизацию построения чанка и зависимостей, чанка, модуля и других аспектов.
вернуться к компилятору
После выполнения метода seal генерируется объект Chunks, завершается работа по компиляции и управление возвращается компилятору, в это время выполняется метод compile компилятора. Пришло время выполнить функцию обратного вызова onCompiled компиляции. Выше мы также разместили краткий код onCompiled.
Что делает метод onCompiled
onCompiled завершает финальную стадию вывода. Выводим наш сгенерированный чанк на диск. После вызова хука done сборка завершена.
Обязанности объекта компиляции
Резюме: Создание модулей и фрагментов, а также использование плагинов для оптимизации процесса сборки
Процесс перекомпиляции для изменения бизнес-кода
Подумайте об этом, когда мы изменим бизнес-код, webpack-cli снова вызовет компилятор.run() напрямую. Перекомпилируйте наш код. Итак, с этим изображением:
Функция wepack вызываться не будет, потому что нет необходимости повторять инициализацию. В памяти уже есть объект компилятора, просто запустите его и перекомпилируйте.
Справочная статья
Изучите процесс упаковки из исходного кода Webpack, Mengxin также может понять ~
Я изучаю исходный код, читая их статьи. Студенты, которые хотят изучить исходный код, также могут использовать этот метод. Я думаю, что все еще сложно разобраться в исходном коде, просто прочитав статью. упражняться.
заключительные замечания
В этой главе лишь кратко рассматривается основной процесс Webpack.Я с нетерпением жду возможности продолжить изучение различных подключаемых модулей Webpack в будущем, а также лучше ознакомиться с Webpack в сочетании с подключаемыми модулями и учиться вместе с вами.