Введение
В предыдущей статье было описано, как понятьзастегивающийся этот крюковый механизм, потому что это душа программы webpack. Хотя механизм ловушек очень гибкий, он стал препятствием для нашего понимания веб-пакета. Всякий раз, когда работает webpack, мой менталитет - буддистский менталитет, молюсь, чтобы не было проблем в середине, иначе долго искать проблемы, поэтому лучше не паковать. Особенно механизм работы загрузчика и плагина, когда они запускаются и какая ссылка веб-пакета применяется? Это вопросы, требующие знакомства с исходным кодом веб-пакета, чтобы получить ответы.
Следуйте за мной шаг за шагом, чтобы раскрыть тайну webpack.
Как отлаживать веб-пакет
В этом разделе в основном описывается отладка веб-пакета. Если у вас есть собственный метод отладки или более распространенный метод, вы можете оставить сообщение для обсуждения.
Запустите облегченную версию веб-пакета
Если рабочий хочет хорошо работать, он должен сначала заточить свои инструменты. Я считаю, что когда вы впервые изучаете веб-пакет, вы должны следовать официальной документации, чтобы запустить веб-сайт упаковки веб-пакета.
документация по началу работы с webpack ->Мэнсин указывает путь
Основные операции должны полагаться на webpack-cli, введя в маленьком черном полеnpx webpack --config webpack.config.js
, а затем введите для выполнения упаковки. Хотя webpack-cli поможет нам учесть большинство вопросов, которые возникнут в процессе упаковки, он сделает нас менее знакомыми с исходным кодом webpack, и кажется, что конфигурация — это все.
В такие неловкие времена мы должны найти другой способ развития и не использовать официальный метод входа.
Простой код отладки, который я написал для запуска веб-пакета, выглядит следующим образом:
//载入webpack主体
let webpack=require('webpack');
//指定webpack配置文件
let config=require("./webpack.config.js");
//执行webpack,返回一个compile的对象,这个时候编译并未执行
let compile=webpack(config);
//运行compile,执行编译
compile.run();
Если вам интересно, откуда пришло вдохновение для моего кода? Я скажу всем, что это из webpack-cli.
Выберите несколько ключевых запусков, затем вы можете выполнить простую реорганизацию запущенного веб-пакета.
Говорящий: Зачем мне это делать? Чем меньше кода, тем легче его анализировать, и тем больше «нерелевантного» кода мы запутаемся. Конечно, когда эту часть освоишь, а потом посмотришь кли код, урожай может быть больше.
Теплое напоминание о настройке
Хотя мы настроим Entry, мы можем игнорировать конфигурацию Context.Если наша команда находится в текущем каталоге, выполнение в порядке, но если мы не в текущем каталоге, а затем выполняем, то есть вероятность, что будет проблема с путем.Чтобы предотвратить трагедию сокрытия,рекомендую настройку контекста на машине,котораяcontext:你当前项目的绝对路径
.
module.exports = {
//...
context: path.resolve(__dirname, 'app')
};
Точка останова! отладчик
Ключевая часть здесь, написание простого веб-пакета в основном для удобства переломных моментов! Повышение читабельности программы.
Вход в проигрыватель без vscode
Если вы поклонник термиала и хрома, пожалуйста, примите следующие методы!Нажмите, чтобы получить справочный документ, вот подробный процесс работы.
node --inspect-brk debugger1.js
Затем мы можем с удовольствием поиграться с дружественным хромом, например, отлаживать веб-страницы. Но тут возникает проблема. Отладка без точек останова ужасна. Хотя каждый шаг отображается очень хорошо, я не хочу знать нативные методы узла, такие как чтение fs, запуск таймера и загрузка модуля, и нажатие кнопки «Далее». раз основной процесс веб-пакета не выполнял несколько шагов, что сильно бросало вызов моему терпению.Если рядом с последним шагом есть небольшой партнер, шаг за шагом, я надеюсь, что вы можете прийти и поделиться им с нами. Чтобы не было слишком много деталей, на данный момент мы можем разбить точку в соответствующем месте:
options = new WebpackOptionsDefaulter().process(options);
debugger//是他是他就是他,我们的救星
compiler = new Compiler(options.context);
После запуска WebpackOptionsDefaulter программа автоматически остановится и позволит вам выполнить отладку.
vscode плеер
Если вы играете в vscode, в дополнение к описанному выше методу отладки мы также можем напрямую нажать на красную точку в качестве точки останова, что более удобно. Наконец, вы можете очистить все точки останова одним щелчком мыши.
В то же время вы также можете ввести параметры, которые хотите знать, в консоли отладки в текущей точке останова.
Каков основной процесс веб-пакета
Для пояснения основного процесса webpack я разделил его на следующие три типа:
Вводная версия: процесс webpack запускается компилятором, компиляция концентрируется на синтаксическом анализе и, наконец, возвращает выходной файл компилятора.
Профессиональная версия: процесс веб-пакета заключается в управлении процессом с помощью компилятора, профессиональном анализе компиляции, ModuleFactory для создания модулей, синтаксическом анализаторе для анализа исходного кода и, наконец, для объединения модулей с помощью шаблона для вывода процесса упаковки файлов.
Грубая версия: веб-пакет — это процесс разделения исходного кода и его реорганизации.
Интерпретация исходного кода
Давайте перейдем непосредственно к пониманию веб-пакета из профессиональной версии. Из кода запуска выше мы можем видетьwebpack(config)
Это ключевой код для запуска упаковки webpack, то есть webpack.js — наш первый объект исследования.
Т.к. автор отлаживает вебпак и различные брейкпоинты, то количество строк исходного кода не соответствует количеству строк в строке, так что тут я вместо количества строк прямо кину код, и все будут сравнивать исходники из вебпака.
Источником всего является webpack.js
Думаете, я начну разбор с первого вступления? Его не существует, давайте начнем непосредственно с ключевой логики.
options = new WebpackOptionsDefaulter().process(options);
compiler = new Compiler(options.context);
compiler.options = options;
new NodeEnvironmentPlugin().apply(compiler);
···省略自定义插件的绑定
compiler.hooks.environment.call();
compiler.hooks.afterEnvironment.call();
compiler.options = new WebpackOptionsApply().process(options, compiler);
Не смущайтесь, не паникуйте, давайте прочитаем построчно, здесь очень важна каждая строчка.
options = new WebpackOptionsDefaulter().process(options);
Ключевое слово Default в этой строке.По ключевому слову мы можем предположить, что функция этого класса состоит в том, чтобы переопределить конфигурацию веб-пакета по умолчанию с помощью настроенной части в нашем webpack.config.js.
Выберите линию кода в этом классе для всех, чтобы понять.
this.set("entry", "./src");
Это конфигурация записи по умолчанию.Если мы не настроим запись, программа автоматически найдет файл в src для его упаковки.
Говорящий автор: Отличительной особенностью webpack 4.0 является то, что у него нулевая конфигурация, и мы можем упаковать его без webpack.config.js. Зачем? Webpack действительно не нуждается в настройке? искусственный интеллект? Не делайте! Потому что есть конфигурации по умолчанию, точно так же, как все программы имеют начальные конфигурации по умолчанию.
new Compiler(options.context)
, очень важный компилятор, в основном процесс компиляции исходит от этого класса.options.context
Это значение представляет собой абсолютный путь к текущей папке, который можно понять из фрагмента кода стандартной конфигурации WebpackOptionsDefaulter.js. Этот класс анализируется позже.
this.set("context", process.cwd());
Затем идет ряд, для настройки компилятора иNodeEnvironmentPlugin
Перехватчики и пользовательские подключаемые модули также являются перехватчиками, которые подключаются к компилятору соответственно, и некоторые перехватчики среды срабатывают после перехвата. Это как завести машину перед поездкой. Например, файловая система, которую необходимо использовать при разборе файлов (разрешитель), как читать файлы. Это для того, чтобы смонтировать входную файловую систему inputFileSystem на компилятор, а затем с помощью компилятора управлять теми плагинами, которым нужна эта функция, и раздавать их ему.
class NodeEnvironmentPlugin {
apply(compiler) {
compiler.inputFileSystem = new CachedInputFileSystem(
new NodeJsInputFileSystem(),
60000
);
//....
compiler.hooks.beforeRun.tap("NodeEnvironmentPlugin", compiler => {
if (compiler.inputFileSystem === inputFileSystem) inputFileSystem.purge();
});
}
}
module.exports = NodeEnvironmentPlugin;
compiler.options = new WebpackOptionsApply().process(options, compiler);
, а здесь обрабатываются опции.Если первый шаг форматирование конфигурации, то это активация конфигурации в компиляторе. Этот класс важен, потому что в компиляторе активируются многие хуки, а на некоторые хуки перехватываются функции.
Основные параметры конфигурации активируют парсинг:
-
Это парсер синтаксического анализа, если файл js, то будет использоваться этот синтаксический анализ, то есть он выполняется во время загрузки.
new JavascriptModulesPlugin().apply(compiler);
-
Эта строка используется для разбора, то есть разбора записи.
SingleEntryPlugin
ещеMultiEntryPlugin
. Этот метод эквивалентен вводу программы в готовое состояние, просто дождитесь выполнения следующей команды.new EntryOptionPlugin().apply(compiler); compiler.hooks.entryOption.call(options.context, options.entry);
-
Хук, который будет выполняться, когда все хуки плагина зависнут.
compiler.hooks.afterPlugins.call(compiler);
-
Кроме того, есть различные хуки разрешения пути, которые разрешаются в соответствии с нашим настраиваемым преобразователем.
compiler.resolverFactory.hooks.resolveOptions
Ключевые моменты для прорыва Compiler.js
Можно сказать, что Compiler.js — это класс, который действительно контролирует процесс упаковки веб-пакета.Если то, что делает webpack.js, — это подготовка, то Compiler просто засучивает рукава и делает это.
constructor
мы начинаем сconstructor
начать разборCompiler
.
Компилятор сначала определяет кучу хуков, если вы внимательно понаблюдаете, то обнаружите, что это разные этапы процесса (здесь код очень читабелен), то есть у каждого этапа есть хук, что это значит? Мы можем использовать эти хуки для подключения наших плагинов, поэтому компилятор очень важен.
крючок для ключей | тип крючка | параметр ловушки | эффект |
---|---|---|---|
beforeRun | AsyncSeriesHook | Compiler | Подготовительные действия перед запуском в основном включают функцию чтения файлов. |
run | AsyncSeriesHook | Compiler | "Машина" уже запущена, и перед компиляцией есть кеш, то включаем кеш, что может повысить эффективность. |
beforeCompile | AsyncSeriesHook | params | Начните подготовку перед компиляцией, создайте ModuleFactory, создайте Compilation и привяжите ModuleFactory к Compilation. Также обрабатывать некоторые модули, которые не нужно компилировать, такие как ExternalModule (удаленный модуль) и DllModule (сторонний модуль). |
compile | SyncHook | params | составлено |
make | AsyncParallelHook | compilation | Начните создавать модули из функции Addentry компиляции |
afterCompile | AsyncSeriesHook | compilation | Сборка окончена |
shouldEmit | SyncBailHook | compilation | Получить телеграмму, отправленную компиляцией, определить, прошла ли компиляция успешно, и можно ли запустить вывод. |
emit | AsyncSeriesHook | compilation | выходной файл |
afterEmit | AsyncSeriesHook | compilation | Готовый вывод |
done | AsyncSeriesHook | Stats | Удачно или нет, но все уже решено. |
Compiler.run()
По названию функции можно примерно догадаться о ее роли, но мы все же углубляем свое понимание Компилятора по запущенному процессу Компилятора.Compiler.run()
бегать!
вызвать первымbeforeRun
Это асинхронный хук, в который привязывается объект, читающий файл. с последующимrun
Этот асинхронный хук в основном работает с кэшированными модулями, уменьшает количество скомпилированных модулей и ускоряет компиляцию. войдет позжеCompiler.compile()
ссылка на компиляцию.
this.hooks.beforeRun.callAsync(this, err => {
....
this.hooks.run.callAsync(this, err => {
....
this.compile(onCompiled);
....
});
....
});
После того, как Compiler.compile завершит работу, он вызовет функцию с именем onCompiled in run, Функция этой функции — создать файл из скомпилированного содержимого. Мы видим, что первыйshouldEmit
Определяем, прошла ли компиляция успешно, если нет, заканчиваемdone
, распечатайте соответствующую информацию. призвать к успехуCompiler.emitAssets
пакетный файл.
if (this.hooks.shouldEmit.call(compilation) === false) {
...
this.hooks.done.callAsync(stats, err => {
...
}
return
}
this.emitAssets(compilation, err => {
...
if (compilation.hooks.needAdditionalPass.call()) {
...
this.hooks.done.callAsync(stats, err => {});
};
})
Compiler.compile()
В предыдущем разделе обсуждался только общий процесс метода Compiler.run и не упоминался Compiler.compile, что, как следует из названия, означает компиляцию. Так что же именно произошло в процессе компиляции?
const params = this.newCompilationParams();
this.hooks.beforeCompile.callAsync(params, err => {
...
this.hooks.compile.call(params);
const compilation = this.newCompilation(params);
this.hooks.make.callAsync(compilation, err => {
...
compilation.finish();
ompilation.seal(err => {
...
this.hooks.afterCompile.callAsync(compilation, err => {
...
此处是回调函数,这个函数主要用于将编译成功的代码输出
...
});
});
});
});
Первый заключается в том, чтобы определитьparams
и прошел вhooks.compile
В этом крючкеparams
Это фабрика модулей, наиболее часто используемая из которыхnormalModuleFactory
, передать эту фабрику в хук, что удобно для последующих плагинов или модулей работы хуков.
Если хук хочет соединиться с программой, например, добавить содержимое в компилятор, необходимо передать компилятор в хук, в противном случае плагину не будет доступен интерфейс.
Затем подготовьте перед компиляцией, а затем запустите хук компиляции.
Компиляция недавно создана здесь, очень важный класс, который фокусируется на компиляции.
hooks.make
Этот хук должен официально начать компиляцию, поэтому завершение выполнения хука означает, что компиляция завершена, и пакет можно запечатать. Итак, когда срабатывает хук make, какие шаги выполняются?
Вы помните, что было упомянуто в webpack.js?EntryOptionPlugin
?
new EntryOptionPlugin().apply(compiler);
compiler.hooks.entryOption.call(options.context, options.entry);
Тогда Tb от автора: модуль WebPack фактически построен на запись, который является входным началом анализа файлов, начать здание. Этот файл будет вызвать запись после дополнения. Adddentry, то после наступления начнутся начать строительные блоки компиляции.
EntryOptionPlugin
Это плагин, который помогает нам работать с типами записей и сопоставлять разные EntryPlugins с разными конфигурациями записей в webpack.config.js. Существует три типа входа через конфигурацию входа: SingleEntryPlugin, MultiEntryPlugin и DynamicEntryPlugin, и их типы можно легко отличить по именам. Как правило, компилятор запускает только один EntryPlugin, а затем в этом EntryPlugin будет запись нашего строительного модуля, то есть запись компиляции.
compiler.hooks.make.tapAsync("SingleEntryPlugin|MultiEntryPlugin|DynamicEntryPlugin",(compilation, callback) => {
...
compilation.addEntry(context, dep, name, callback);
...
});
Помимо того, что помогает нам открыть дверь для компиляции,???EntryPlugin
Событие также привязано, то есть тип фабрики модулей текущей записи.
compiler.hooks.compilation.tap("SingleEntryPlugin",(compilation, { normalModuleFactory }) => {
compilation.dependencyFactories.set(
SingleEntryDependency,
normalModuleFactory
);
});
Эта функция ловушки помогает нам определитьSingleEntry
тип модуля, то он будет использоваться при компиляции компиляции.normalModuleFactory
для создания модулей.
make
Этот хук эквивалентен поворотной точке, мы перескакиваем с основного процесса на настоящий процесс компиляции — компиляцию, класс, ориентированный на оптимизацию компиляции.
После успешной компиляции мы вернемся на основное поле битвы компилятора и скомпилируем успешный контент.emitAssest
на жесткий диск.
100 лет профессиональной компиляции - Compilation.js
Если компилятор — это процесс, то компиляция — это дом компиляции. То есть после того, как исходный код был им обработан, он сублимировался в обычный вид.
Работу компиляции можно обобщить, добавив записи, проанализировав модули через запись и проанализировав зависимости между модулями, как на диаграмме. После того, как построение завершено, запускается печать, которая инкапсулирует ряд мер по оптимизации на этом этапе Компиляция, преобразует проанализированные модули в стандартные модули веб-пакета и выводит резервную копию.Предпосылка заключается в том, что вы подключаете плагин оптимизации к каждому оптимизированному хуку. , запуская оптимизированные хуки, но функции должны быть зарегистрированы на хуках, чтобы они вступили в силу.
Что ж, получаем информацию от Compile для анализа Compilation.js в порядке появления
addEntry - с чего все начинается
упоминалось в предыдущем разделеSingleEntryPlugin
(Есть другие EntryPlugins), который является портом запуска, подождите, пока не сработаетcompile.hooks.make
, он начнетсяSingleEntryPlugin
серединаcompilation.addEntry
Этот метод, этот метод заключается в том, чтобы начать построение входного модуля и добавить входной модуль в программу после успеха.
//context,entry,name都是options中的值。
addEntry(context, entry, name, callback) {
this._addModuleChain(context,entry,module => {
this.entries.push(module);
},(err, module) => {
...
if (module) {
slot.module = module;
} else {
const idx = this._preparedEntrypoints.indexOf(slot);
if (idx >= 0) {
this._preparedEntrypoints.splice(idx, 1);
}
}
...
return callback(null, module);
}
);
}
Добавить зависимости модуля_addModuleChain
Этот метод является основным методом построения модуля, вызывается addEntry и возвращается после завершения построения модуля.
-
_addModuleChain
, создает модули, сохраняя при этом зависимости между модулями.-
const moduleFactory = this.dependencyFactories.get(Dep);moduleFactory.create(...)
,здесьmoduleFactory
По сути, это фабрика по созданию текущего типа модулей, а создание заключается в создании новых продуктов (новых модулей) из этой фабрики.-
this.addModule(module)->this.modules.push(module);
, добавьте модуль в компиляцию.modules. -
onModule(module);
, этот метод вызывает addEntry вthis.entries.push(module)
, то есть добавление модуля entry в компиляцию.entries. -
this.buildModule->this.hooks.buildModule.call(module);module.build(...)
, этот метод должен дать хук, который может работать с модулем, вы можете сами определить плагин для работы с ним. Затем идет создание самого модуля.Способ создания зависит от типа модуля.Например,модуль созданный normalModuleFactor исходит из класса NormalModule.- Встроенный метод _addModuleChain
afterBuild()
, этот метод предназначен для получения времени, затраченного на создание модуля и зависимостей модулей, а затем выполнения функции обратного вызова, если функция обратного вызова есть.
- Встроенный метод _addModuleChain
-
-
После завершения сборки возвращаемся к компилятору и заканчиваем нашу сборку
Здесь finish делает две вещи: одна — запускает хук для завершения сборки, а другая — собирает проблемы, которые создает каждый модуль.
Все готово, начинаем инкапсулировать печать(обратный вызов)
Продукт готов и готов к упаковке для экспорта.
Один за другим начал выполнять оптимизацию крючка, если вы пишете оптимизированный крючок.
Начать оптимизацию:
Вот хук для оптимизации зависимостей
Вот хук для оптимизации модуля
Вот хук для оптимизации Chunk
. . . . .
Оптимизаций слишком много, а автор уже улизнул.
После завершения оптимизации будет выполнена callback-функция от Компилятора, то есть будет сгенерирован файл.
В дополнение к вызовам различных хуков, seal также делает очень важную вещь, а именно повторно агрегирует отформатированные js через шаблон Template, а затем вызывает обратно компилятор для генерации файла. Эта часть будет подробно проанализирована позже в Шаблоне.
Автору есть что сказать, на самом деле основной процесс это Compiler и Compliation, эти два класса взаимодействуют друг с другом. Далее есть еще несколько критических классов, но с моей точки зрения, они не являются частью основного процесса, но важны, потому что это классы, созданные модулями. Как и продукт на конвейере, сам продукт не имеет ничего общего с процессом конвейера.
Родина модулей — moduleFactory
moduleFactory — это экземпляр модуля, но он не принадлежит основному процессу.Это как деталь Lego.Что бы мы без него делали? Делайте кирпичи без соломы! ModuleFactory, который необходимо скомпилировать, делится на две категории: контекст и нормальный.В основном, то, с чем я сталкиваюсь, это обычные типы, поэтому здесь я объясню moduleFactory с классом noraml.
его миссия
Поскольку он фабрика, его миссия — производить продукцию. Здесь модуль — это продукт, поэтому фабрике нужен только один. Наша фабрика была создана в Compiler.compile и передана в качестве параметраcompile.hooks.beforeCompile
а такжеcompile.hooks.compile
Среди этих двух хуков это означает, что когда мы пишем функции монтирования этих двух хуков, мы можем вызвать эту фабрику, чтобы помочь нам создать модули обработки.
const NormalModule = require("./NormalModule");
const RuleSet = require("./RuleSet");
Эти два параметра очень важны, один — это сам продукт, то есть экземпляр, созданный NormalModule, и есть модуль.RuleSet
Это загрузчики, включая встроенные загрузчики и пользовательские загрузчики. Иными словами, Фабрика делает две вещи: первая — сопоставляется с соответствующим синтаксическим анализатором, настраивает синтаксический анализатор так, чтобы он был синтаксическим анализатором, специально используемым для текущего модуля, и анализирует исходный код в режиме AST. генератор для генерации Код для восстановления АСТ (эта часть используется при генерации шаблона) Третий для создания модуля При сборке модуля найдите для него соответствующий загрузчик, замените исходный код, добавьте соответствующий зависимый модуль, а затем проанализируйте модуль. Соответствующий анализатор предоставляется при создании шаблона, а соответствующий генератор предоставляется при создании шаблона.
класс normalModule
Fatory предоставляет исходные материалы (опции) и инструменты (парсер), что равносильно передаче параметров в автоматическую машину.Этот normalModule представляет собой созданную машину, которая строит модуль и превращает исходный код в синтаксическое дерево AST.
build(options, compilation, resolver, fs, callback) {
//...
return this.doBuild(options, compilation, resolver, fs, err => {
//...
this._cachedSources.clear();
//...
try {
const result = this.parser.parse(//重点在这里。
//....
);
//...
});
}
После того, как модуль будет создан в компиляции, начните запускать метод сборки модуля и начните генерировать модуль, Его логика очень проста, то есть ввод исходного исходного файла, а затем парсинг файлового загрузчика и зависимых файлов через reslover, и вернуть результат. Затем преобразуйте его в стандартный модуль веб-пакета через загрузчик, сохраните исходный код и подождите, пока он будет использован при создании шаблона.
Когда приходит время упаковки, скомпилированный исходный код рекомбинируется в код JS, в основном с помощью генератора, оборудованного для модуля Facotry.
source(dependencyTemplates, runtimeTemplate, type = "javascript") {
//...获取缓存
const source = this.generator.generate(
this,
dependencyTemplates,
runtimeTemplate,
type
);
//...存到缓存中
return cachedSource;
}
Марш погрузчика
Где именно выполняется загрузчик и как его выполнить
Для новичков загрузчик и плагин может быть глупо различать (да, я такой дурак). После глубокого понимания исходного кода я ясно понимаю разницу между ними.
невежественный я | я знаю хитрость |
---|---|
Отличие 1: у плагина широкий ассортимент, ну и смысл действительно широкий | Разница 1: плагин может появиться в любом узле процесса, погрузчик имеет определенный объем активности |
Отличие 2: Конфигурация несогласованная, конфигурация загрузчика очень странная, это не module.loaders, а module.ruleset | Отличие 2: Плагин может делать вещи, не связанные с исходным кодом, такие как мониторинг, в то время как загрузчик может только разобрать исходный код в стандартный модуль. |
Так где именно выполняется загрузчик? понялCompilation
,NormalModuleFactory
,NormalModule
После функции послушайте, как я объясню, как загрузчик входит в модуль!
прежде всегоCompilation._addModuleChain
При запуске добавления модуля срабатываетCompilation.buildModule
этот метод, а затем вызовитеNormalModule.build
, начните создавать модуль. Когда модуль создан, он вызываетсяrunLoaders
Идем выполнять загрузчики, но программа все еще путается в расположении загрузчика, поэтому нужно запросить в это времяNormalModuleFactory.resolveRequestArray
, помогите нам прочитать адрес, где находится загрузчик, выполнить и вернуться. Таким образом, модули генерируются один за другим, а загрузчики генерируются один за другим, пока не будет создан последний модуль, а затем он прибывает.Compilation.seal
обработать.
Парсер душ
После того, как текущий модуль обработал загрузчики и превратил импортированный модуль в стандартный JS-модуль, необходимо приступить к декомпозиции исходного кода и сделать из него стандартное синтаксическое дерево AST, в это время необходимо опираться на Parser. Парсер очень мощный, он помогает нам конвертировать нестандартный контент в стандартные модули, удобные для упаковки и других операций. Парсер эквивалентен машине.Исходные файлы вводятся, обрабатываются, а затем выводятся.Исходные файлы не оказывают химического воздействия на Парсер. Парсер существует не по номеру, созданному normalModule, а по типу модуля. Подумайте, если фабрика оснащена парсером для каждого продукта, то эффективность успешного biubiubiu падает.
Существует три типа синтаксического анализатора javascript: «авто», «скрипт» и «модуль».В соответствии с потребностями модуля Factoy помогает нам сопоставлять различные типы синтаксического анализатора.
normalModuleFactory.hooks.createParser.for("javascript/auto").tap("JavascriptModulesPlugin", options => {
return new Parser(options, "auto");
});
normalModuleFactory.hooks.createParser.for("javascript/dynamic").tap("JavascriptModulesPlugin", options => {
return new Parser(options, "script");
});
normalModuleFactory.hooks.createParser.for("javascript/esm").tap("JavascriptModulesPlugin", options => {
return new Parser(options, "module");
});
Как Parser на самом деле анализирует наш исходный код?
Во-первых, он становится AST — стандартным синтаксическим деревом, структурированным кодом, который удобен для последующего парсинга.Если входящий источник не является AST, он также будет принудительно обработан AST.
Эта библиотека синтаксического анализа, webpack использует acorn.
static parse(code, options) {
.....
ast = acorn.parse(code, parserOptions);
.....
return ast;
}
parse(source, initialState) {
//...
ast = Parser.parse(source, {
sourceType: this.sourceType,
onComment: comments
});
//...
}
Dingdong - шаблон вашей упаковки Шаблон
Наконец-то пришло время подвести итоги, но эта часть не так проста, как кажется.
Шаблон запускается, когда компиляция.seal завершена, то есть после сборки модуля. Нам нужно собрать модули, которые мы наконец встроили в код js, который мы видим в комплекте.
JS, который мы упаковали, всегда использует одну и ту же процедуру? почему это? Очевидно, есть стандартный шаблон. После того, как наш исходный файл станет ast, обработка подготовки к выводу зависит от того, как выводится операция Template, а webpack-source помогает нам объединять и заменять модули в формате ast. Наконец, вывод объединяется в соответствии с фрагментом.
Всего существует 5 классов шаблонов:
- Template.js
- MainTemplate.js
- ModuleTemplate.js
- RuntimeTemplate
- ChunkTemplate.js
Конечно! Замена шаблона производится в компиляции, ведь компиляция это как проводник, а проводник компилируется один за другим по порядку.
Compilation.seal запускает MainTemplate.getRenderManifest, получает информацию, которую необходимо отобразить, а затем запускает ее через хук вmainTemplate.hooks.renderManifest
Этот хук, вызывая соответствующую функцию в JavascriptModulePlugin, создает файл-манифест, содержащий информацию об упаковке, который нужно вернуть для резервного копирования.
result.push({
render: () =>
compilation.mainTemplate.render(
hash,
chunk,
moduleTemplates.javascript,
dependencyTemplates
),
filenameTemplate,
pathOptions: {
noChunkHash: !useChunkHash,
contentHashType: "javascript",
chunk
},
identifier: `chunk${chunk.id}`,
hash: useChunkHash ? chunk.hash : fullHash
});
createChunkAssets(){
//...
const manifest = template.getRenderManifest(...)//获取渲染列表
//...
for (const fileManifest of manifest) {
//...
source = fileManifest.render();
//...
}
//...
}
После завершения подготовки запустится рендеринг и будет вызвана функция рендеринга fileManifest, которая фактическиmainTemplate.render
.mainTemplate.render
запущенныйhooks.render
Этот хук возвращаетConcatSource
ресурс из. Есть фиксированные шаблоны и так называемые модули.
//...
this.hooks.render.tap("MainTemplate",(bootstrapSource, chunk, hash, moduleTemplate, dependencyTemplates) => {
const source = new ConcatSource();
source.add("/******/ (function(modules) { // webpackBootstrap\n");
//...
source.add(
this.hooks.modules.call(//获取模块的资源
new RawSource(""),
chunk,
hash,
moduleTemplate,
dependencyTemplates
)
);
source.add(")");
return source;
}
);
//..
render(hash, chunk, moduleTemplate, dependencyTemplates) {
//...
let source = this.hooks.render.call(
new OriginalSource(
Template.prefix(buf, " \t") + "\n",
"webpack/bootstrap"
),
chunk,
hash,
moduleTemplate,
dependencyTemplates
);
//...
return new ConcatSource(source, ";");
}
Шаблон каждого модуля заменяет MainTemplate и назначает задачу Шаблону, позволяя ему решать проблемы модулей, поэтому звонитеTemplate.renderChunkModules
Сюда. Этот метод сначала получает ресурсы замены для всех модулей.
static renderChunkModules(chunk,filterFn,moduleTemplate,dependencyTemplates,prefix = ""){
const source = new ConcatSource();
const modules = chunk.getModules().filter(filterFn);
//...
const allModules = modules.map(module => {
return {
id: module.id,
source: moduleTemplate.render(module, dependencyTemplates, {
chunk
})
};
});
//...
//...
}
потомModuleTemplate
Спроси еще разNormalModule.source
Сюда. Здесь модуль использует генератор, предоставленный Factory, для генерации кода замены, который будет запрошен на этапе генерации.RuntimeTemplate
, как видно из названия, — это код, используемый для замены среды выполнения.
source(dependencyTemplates, runtimeTemplate, type = "javascript") {
//...
const source = this.generator.generate(
this,
dependencyTemplates,
runtimeTemplate,
type
);
const cachedSource = new CachedSource(source);
//..
return cachedSource;
}
Затем бросьте NormalModule в cachedSource и верните его вModuleTemplate
дальнейшая обработка.ModuleTemplate
После упаковки этого модуля окончательный результат выглядит следующим образом:
мы возвращаемсяTemplate
, продолжить обработку послеModuleTemplate
После обработки мы возвращаем такие данные.
Революция еще не закончилась! Замена еще впереди! мы возвращаемсяTemplate.renderChunkModules
, продолжайте замену.
static renderChunkModules(chunk,filterFn,moduleTemplate,dependencyTemplates,prefix = ""){
const source = new ConcatSource();
const modules = chunk.getModules().filter(filterFn);
//...如果没有模块,则返回"[]"
source.add("[]");
return source;
//...如果有模块则获取所有模块
const allModules = modules.map(//...);
//...开始添加模块
source.add("[\n");
//...
source.add(`/* ${idx} */`);
source.add("\n");
source.add(module.source);
source.add("\n" + prefix + "]");
//...
return source;
}
Мы возвращаем ConcatSource вMainTemplate.render()
, плюс;
, то композиция возвращается кCompliation.createChunkAssets
.
Шаблон в этой печати подходит к концу. Что касается сгенерированных файлов, то есть через пакет webpack-source, наши голодные массивы превращаются в строки, затем сращиваются и, наконец, выводятся.
Все фотоматериалы от руки автора, приветствуется перепечатка, просьба указывать источник. После более чем месяца возни я чувствую, что лысею.
Что назревает для следующего исследования. Чувствую, что загрузчик еще надо порубить побольше. (смеется~)