Автор: CUI Jing
введение
Для webpack каждый файл является модулем.В этой статье будет показано, как webpack начинается с определения записи в конфигурации, находит все файлы и конвертирует их в модули.
Обзор
запись записи веб-пакета, параметр записи представляет собой строку с одной записью, массив с одной записью, объект с несколькими записями или динамическую функцию, независимо от того, что это такое, он будет вызыватьсяcompilation.addEntry
метод, этот метод будет выполняться_addModuleChain
, добавьте файл записи в очередь, которую необходимо скомпилировать. Затем файлы в очереди обрабатываются один за другим, а файлы в файлеimport
Введен другими документами будетaddModuleDependencies
Добавьте в очередь компиляции. Наконец, когда содержимое очереди компиляции обработано, преобразование файла в модуль завершено.
Вышеприведенный примерный план, а затем мы будем добавлять детали в этот план один за другим. Сначала взгляните на общее управление процессом компиляции — управление очередью компиляции.
Управление очередью компиляции — семафор
_addModuleChain 和 addModuleDependencies
функция вызоветthis.semaphore.acquire
Конкретная реализация этой функции находится вlib/util/Semaphore.js
в файле. Посмотрите на конкретную реализацию
class Semaphore {
constructor(available) {
// available 为最大的并发数量
this.available = available;
this.waiters = [];
this._continue = this._continue.bind(this);
}
acquire(callback) {
if (this.available > 0) {
this.available--;
callback();
} else {
this.waiters.push(callback);
}
}
release() {
this.available++;
if (this.waiters.length > 0) {
process.nextTick(this._continue);
}
}
_continue() {
if (this.available > 0) {
if (this.waiters.length > 0) {
this.available--;
const callback = this.waiters.pop();
callback();
}
}
}
}
Есть только два метода воздействия на внешний мир:
- Приобретение: Подать заявку на обработку ресурсов.Если есть свободные ресурсы (т.е. параллельные номера), обработка будет выполнена немедленно, а свободные ресурсы будут уменьшены на 1, в противном случае они будут сохранены в очереди ожидания.
- Release: освободить ресурс. При приобретении будет вызываться метод обратного вызова, где нужно использовать release для освобождения ресурсов и прибавлять 1 к простаивающим ресурсам. При этом он проверит, есть ли еще контент для обработки, и если да, то продолжит обработку
Этот класс Semaphore заимствует концепцию Semaphore (семафора), который управляет использованием ресурсов в многопоточной среде. Количество параллелизма определяется доступным, так каково значение по умолчанию? существуетCompilation.js
можно найти в
this.semaphore = new Semaphore(options.parallelism || 100);
Число параллелизма по умолчанию — 100. Обратите внимание, что параллелизм, упомянутый здесь, — это только параллелизм в дизайне кода, его не следует путать с однопоточной функцией js. В целом процесс компиляции выглядит следующим образом
От входа до _addModuleChain
официальный сайт вебпакаРуководство по настройкеЗапись может иметь следующие формы:
- строка: строка, например.
{
entry: './demo.js'
}
- [строка]: массив строкового типа, например.
{
entry: ['./demo1.js', './demo2.js']
}
- объекты, такие как
{
entry: {
app: './demo.js'
}
}
- функция, динамическая возвращаемая запись, например
{
entry: () => './demo.js'
}
// 或者
{
entry: () => new Promise((resolve) => resolve('./demo.js'))
}
Где они обрабатываются? В стартовом файле webpack.js webpack параметры будут обрабатываться в первую очередь следующим образом:
compiler.options = new WebpackOptionsApply().process(options, compiler);
существуетprocess
в процессеentry
конфигурация для обработки
// WebpackOptionsApply.js 文件中
new EntryOptionPlugin().apply(compiler);
compiler.hooks.entryOption.call(options.context, options.entry);
Первый взглядEntryOptionsPlugin
Что вы наделали
const SingleEntryPlugin = require("./SingleEntryPlugin");
const MultiEntryPlugin = require("./MultiEntryPlugin");
const DynamicEntryPlugin = require("./DynamicEntryPlugin");
const itemToPlugin = (context, item, name) => {
if (Array.isArray(item)) {
return new MultiEntryPlugin(context, item, name);
}
return new SingleEntryPlugin(context, item, name);
};
module.exports = class EntryOptionPlugin {
apply(compiler) {
compiler.hooks.entryOption.tap("EntryOptionPlugin", (context, entry) => {
// string 类型则为 new SingleEntryPlugin
// array 类型则为 new MultiEntryPlugin
if (typeof entry === "string" || Array.isArray(entry)) {
itemToPlugin(context, entry, "main").apply(compiler);
} else if (typeof entry === "object") {
// 对于 object 类型,遍历其中每一项
for (const name of Object.keys(entry)) {
itemToPlugin(context, entry[name], name).apply(compiler);
}
} else if (typeof entry === "function") {
// function 类型则为 DynamicEntryPlugin
new DynamicEntryPlugin(context, entry).apply(compiler);
}
return true;
});
}
};
существуетEntryOptionsPlugin
зарегистрирован вentryOption
Функция обработчика событий, согласноentry
Различные типы значений (каждый элемент/функция в строке/массиве/объекте) создают экземпляры и выполняют разныеEntryPlugin
: соответствующая строкаSingleEntryPlugin
; массив соответствуетMultiEntryPlugin
; функция соответствуетDynamicEntryPlugin
. Для типа объекта пройдитесь по каждому ключу, обработайте каждый ключ как запись и выберите SingleEntryPlugin или MultiEntryPlugin в соответствии с типом строки/массива. Ниже мы в основном анализируем: SingleEntryPlugin, MultiEntryPlugin, DynamicEntryPlugin
Сравнивая эти три плагина по горизонтали, все они делают две вещи:
- Зарегистрирован обратный вызов события компиляции (это событие будет запущено перед событием make ниже), установленное на этапе компиляции.
dependencyFactories
compiler.hooks.compilation.tap('xxEntryPlugin', (compilation, { normalModuleFactory }) => {
//...
compilation.dependencyFactories.set(...)
})
- Зарегистрируйте обратный вызов события make, вызовите метод addEntry на этапе make, а затем введите
_addModuleChain
Войдите в формальную фазу компиляции.
compiler.hooks.make.tapAsync('xxEntryPlugin',(compilation, callback) => {
// ...
compilation.addEntry(...)
})
В сочетании с процессом упаковки webpack давайте начнем с метода компиляции в Compiler.js и посмотрим, что делают событие компиляции и обратный вызов события make.
xxxEntryPlugin вызывается в событии компиляции для установкиcompilation.dependencyFactories
, гарантированно следовать_addModuleChain
На этапе обратного вызова соответствующую зависимость можно получить по зависимостиmoduleFactory
.
В обратном вызове события make зависимости генерируются в соответствии с различными конфигурациями записи, а затем вызываются.addEntry
и передать зависимость.
существует_addModuleChain
В соответствии с различными типами зависимостей в обратном вызове, а затем выполнитьmultiModuleFactory.create
илиnormalModuleFacotry.create
.
Зависимости постоянно упоминаются в приведенных выше шагах, и различные зависимости появятся в следующих статьях. Видно, что зависимость — очень важная вещь в webpack, в папке webpack/lib/dependencies вы увидите различные зависимости. Структура отношений между зависимостью и модулем выглядит следующим образом:
module: {
denpendencies: [
dependency: {
//...
module: // 依赖的 module,也可能为 null
}
]
}
}
В webpack файл записи также рассматривается как зависимость записи, поэтому xxEntryDependency генерируется в вышеприведенном плагине xxEntryPlugin. Зависимость в модуле сохраняет информацию о зависимости модуля от других файлов, содержимое, экспортируемое им самим, и так далее. В следующих статьях вы увидите, что при создании чанков вы будете полагаться на зависимости для получения графа зависимостей.При создании окончательного файла вы будете полагаться на методы и сохраненную информацию в зависимости для преобразования информации в исходном файле. .import
и другие операторы заменяются окончательным выходным исполняемым оператором js.
Прочитав общие моменты каждого entryPlugin, давайте углубимся в каждый плагин по вертикали и сравним различия.
SingleEntryPlugin
Логика SingleEntryPlugin проста: свяжите SingleEntryDependency с normalModuleFactory, чтобы последующие методы создания выполнялисьnormalModuleFactory.create
метод.
apply(compiler) {
compiler.hooks.compilation.tap(
"SingleEntryPlugin",
(compilation, { normalModuleFactory }) => {
// SingleEntryDependency 对应的是 normalModuleFactory
compilation.dependencyFactories.set(
SingleEntryDependency,
normalModuleFactory
);
}
);
compiler.hooks.make.tapAsync(
"SingleEntryPlugin",
(compilation, callback) => {
const { entry, name, context } = this;
const dep = SingleEntryPlugin.createDependency(entry, name);
// dep 的 constructor 为 SingleEntryDependency
compilation.addEntry(context, dep, name, callback);
}
);
}
static createDependency(entry, name) {
const dep = new SingleEntryDependency(entry);
dep.loc = name;
return dep;
}
MultiEntryPlugin
По сравнению с SingleEntryPlugin выше,
- В компиляции у dependencyFactories установлено два соответствующих значения
MultiEntryDependency: multiModuleFactory
SingleEntryDependency: normalModuleFactory
- createDependency: рассматривать каждое значение в записи как SingleEntryDependency.
static createDependency(entries, name) {
return new MultiEntryDependency(
entries.map((e, idx) => {
const dep = new SingleEntryDependency(e);
// Because entrypoints are not dependencies found in an
// existing module, we give it a synthetic id
dep.loc = `${name}:${100000 + idx}`;
return dep;
}),
name
);
}
3.multiModuleFactory.create
На втором шаге, поMultiEntryPlugin.createDependency
Сгенерированный dep имеет следующую структуру:
{
dependencies:[]
module: MultiModule
//...
}
зависимости — это массив, содержащий несколько зависимостей SingleEntryDependencies. Это dep будет передано в качестве параметра методу multiModuleFactory.create, который является data.dependencies[0] в следующем коде.
// multiModuleFactory.create
create(data, callback) {
const dependency = data.dependencies[0];
callback(
null,
new MultiModule(data.context, dependency.dependencies, dependency.name)
);
}
Новый MultiModule создается при создании, и метод сборки в MultiModule будет выполняться в обратном вызове,
build(options, compilation, resolver, fs, callback) {
this.built = true; // 标记编译已经完成
this.buildMeta = {};
this.buildInfo = {};
return callback();
}
В этом методе установите значение переменной, указывающей, завершена ли компиляция, на true, а затем напрямую введите успешный обратный вызов. На данный момент запись была скомпилирована и преобразована в модуль, и это модуль только с зависимостями. Поскольку каждый элемент в createDependency обрабатывается как SingleEntryDependency, каждый элемент в зависимостях является SingleEntryDependency. Затем перейдите к этапу обработки зависимостей для этого модуля, и несколько файлов, которые мы настроим в записи, будут добавлены в цепочку компиляции как зависимости и обработаны как SingleEntryDependency.
В общем, для записи нескольких файлов можно просто понять, что webpack сначала преобразует запись в следующий вид:
import './demo1.js'
import './demo2.js'
Затем обработайте его.
DynamicEntryPlugin
Конфигурация динамического ввода поддерживает как синхронный метод, так и асинхронный метод с возвращаемым значением типа Promise, поэтому при обработке addEntry сначала вызовите функцию ввода, а затем введите логику строки/массива/объекта в соответствии с типом объекта возвращаемый результат.
compiler.hooks.make.tapAsync(
"DynamicEntryPlugin",
(compilation, callback) => {
const addEntry = (entry, name) => {
const dep = DynamicEntryPlugin.createDependency(entry, name);
return new Promise((resolve, reject) => {
compilation.addEntry(this.context, dep, name, err => {
if (err) return reject(err);
resolve();
});
});
};
Promise.resolve(this.entry()).then(entry => {
if (typeof entry === "string" || Array.isArray(entry)) {
addEntry(entry, "main").then(() => callback(), callback);
} else if (typeof entry === "object") {
Promise.all(
Object.keys(entry).map(name => {
return addEntry(entry[name], name);
})
).then(() => callback(), callback);
}
});
}
);
Таким образом, разница между динамическим входом и другими состоит только в еще одном слое вызовов функций.
После того, как запись найдена, файл преобразуется в модуль. Следующийстатья, процесс конвертации в модуль будет подробно описан.