Количество слов: 12197, время чтения: 35 минут, кликичитать оригинал
Камни можно разбить, но не твердые; Дан можно отшлифовать, но не докрасна - "Весенний и осенний период Лу, Честность и Честность"
[Внешняя инженерия] серия ссылок на статьи:
- 01 Отплытие — среда разработки
- 02 Baibi слегка поврежден — менеджер пакетов
- 03 Подметая восемь пустошей — основы Webpack
Репозиторий примеров кода:GitHub.com/B неправильно/Dev-…
Отказ от ответственности: эта статья основана на веб-пакете версии 4.43.0. Если в соответствии с кодом в статье сообщается об ошибке, проверьте, совпадает ли версия зависимого модуля с версией в репозитории примеров кода.
*Теплое напоминание: *Содержание этой статьи более практично и включает в себя много кода.Если у вас фобия, связанная с интенсивным использованием кода, пожалуйста, покиньте игру как можно скорее.
В содержании предыдущей статьи я представил основные концепции и общие основные конфигурации веб-пакета, а также попытался настроить каркас проекта. Далее введите ссылку восхождения - оптимизация производительности и принципы реализации.
Вишенка на торте — оптимизация
Используйте инструменты анализа
Если оптимизация подкреплена не количественной оценкой данных, а только ощущением, то это нонсенс.Поэтому перед оптимизацией необходимо разобраться в некоторых инструментах анализа результатов построения для получения соответствующих количественных показателей, что дает направление и направление работы по оптимизации в соответствии с участием.
инструмент анализа статистики
инструмент анализа статистикиЭто онлайн-инструмент, официально предоставленный webpack.Метод использования следующий:
- генерировать
stat.json
документ.
webpack --profile --json > stats.json
- Буду
stat.json
загрузить файл винструмент анализа статистикиНа странице можно получить результаты анализа.
использоватьwebpack-bundle-analyzer
В дополнение к инструментам анализа, предоставляемым на официальном веб-сайте, сообщество также предоставляет более полезный артефакт анализа.webpack-bundle-analyzer, не только интерфейс красивый, но и каждый зависимый пакет и его размер более интуитивно понятны.
npm install webpack-bundle-analyzer -D
// build/webpack.prod.conf.js
const merge = require('webpack-merge');
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
const baseConfig = require('./webpack.base.conf.js');
module.exports = merge(baseConfig, {
mode: 'production',
plugins: [
new BundleAnalyzerPlugin(),
// ...
],
// ...
});
На рисунке мы можем просмотреть результаты Gzip и оригинальной упаковки, а также увидеть, какие пакеты содержит каждый фрагмент, а также их размер и другую информацию.
анализ скорости
Вышеупомянутые два инструмента ориентированы на анализ результатов упаковки с упором на оптимизацию качества упаковки. и черезspeed-measure-webpack-pluginЭтот плагин также может анализировать процесс сборки, чтобы оптимизировать эффективность сборки и улучшить качество упаковки.
npm i -D speed-measure-webpack-plugin
// build/webpack.prod.conf.js
const SpeedMeasurePlugin = require("speed-measure-webpack-plugin");
const smp = new SpeedMeasurePlugin();
const webpackConfig = {
// ...
}
module.exports = smp.wrap(webpackConfig) // 需要包裹原来的配置
Теперь вы можете видеть время, потраченное каждым загрузчиком и плагином, чтобы оптимизировать их.
Оптимизация качества упаковки
Когда режим установлен на производство, webpack 4.0 уже по умолчанию использует множество методов оптимизации для улучшения качества результатов вывода, но понимание этого поможет нам выполнить более глубокую оптимизацию. В конце концов, это содержимое напрямую связано с пользовательским опытом и очень важно.
Tree shaking
Термин «тряска дерева» произошел отrollup, встроенный в официальную версию webpack 2.0, может обнаруживать неиспользуемые модули и удалять их. Это зависит от модульной системы ESModule (другие модульные системы не поддерживаются)статические структурные свойства, вы можете удалить неиспользуемый код в контексте JavaScript, удалить неиспользуемый код, что может эффективно уменьшить размер файла кода JS.
Название Tree Shake очень образно, Tree Shake — стряхивание с дерева лишнего.
// src/math.js
export function square(x) {
return x * x;
}
export function cube(x) {
return x * x * x;
}
// src/index.js
import { cube } from './math.js' // 仅引用了 cube 这个方法
console.log(cube(3))
Приведенный выше код на самом деле использует только куб.В общем, мы надеемся, что неиспользуемый код (мертвый код) не должен быть включен в результат. В новой версии веб-пакета в рабочем режиме он автоматически поможет нам удалить неиспользуемый код и уменьшить размер вывода.
В режиме разработки мы можем добавить следующую конфигурацию:
// build/webpack.dev.conf.js
module.exports = {
mode: 'development',
optimization: {
usedExports: true, // 不导出模块中未使用的代码
},
}
После этой настройки webpack идентифицирует неиспользуемый код, а затемTerserPluginУдалите эту часть кода при выполнении сжатия (без дополнительных побочных эффектов).
Если разработчику необходимо указать, какие модули не имеют побочных эффектов, можно использовать другой способ (побочный эффект), который можно настроить вpackage.json
Свойство "sideEffects" (в основном для некоторых библиотек) также можно найти вmodule.rules
параметры конфигурацииустановить в"sideEffects"
. Возможны следующие ситуации:
- Если весь код не содержит кода побочных эффектов, просто установите для этого свойства значение false, чтобы сообщить веб-пакету, что он может безопасно удалять неиспользуемые экспорты.
//package.json
{
"name": "your-project",
"sideEffects": false
}
- Если тег файла имеет побочные эффекты, нужны маркеры
//package.json
{
"name": "your-project",
"sideEffects": [
"./src/some-side-effectful-file.js",
"*.css" // 一般css都是具有副作用的,需要在此声明
]
}
«Побочный эффект» определяется как код, который выполняет особое поведение при импорте, а не просто предоставляет экспорт или экспорт. Например, полифиллы, которые влияют на глобальную область видимости и, как правило, не обеспечивают экспорт.
Соответствующие инструменты также предоставляются для css.PurgeCSSдля удаления неиспользуемого кода, что похоже на встряхивание дерева.
разделение кода
В эпоху одностраничных приложений (сокращенно называемых SPA) мы не можем упаковать весь код в файл js, и невозможно загрузить все файлы js одновременно при запуске приложения, в противном случае это займет много времени Белый экран сведет пользователей с ума и сильно повлияет на пользовательский опыт. В это время код можно разделить, чтобы его можно было загружать параллельно, или необходимый код можно загрузить на разных этапах (например, по разным маршрутам), что является разделением кода и загрузкой по требованию.
Webpack имеет очень мощную встроенную функцию автоматического разделения кода, которая также является одной из его самых ярких функций и при правильном использовании может значительно сократить время загрузки приложения.
Используемый разделительный код:
- Разбиение фрагментов записи: используйте запись для настройки нескольких фрагментов и ручного разделения кода.
- Извлечь общий код:
SplitChunksPlugin
Дедупликация и разделение чанков. - Динамический импорт и загрузка по требованию: в настоящее время рекомендуется отдельный код с помощью встроенных вызовов функций в модулях.
import()
грамматика.
Сегментация входящего чанка
Самый простой способ разделения — вручную разделить код на разные куски в точке входа.
// build/webpack.base.conf.js
module.exports = {
entry: {
app: '../src/index.js',
another: '../src/another-module.js'
},
output: {
filename: '[name].bundle.js',
path: path.resolve(__dirname, '../dist')
}
};
Результат сборки:
...
Asset Size Chunks Chunk Names
another.bundle.js 550 KiB another [emitted] another
index.bundle.js 550 KiB index [emitted] index
Entrypoint index = index.bundle.js
Entrypoint another = another.bundle.js
...
Хотя этот метод прост, есть некоторые проблемы:
- Недостаточно полагаться на то, что разработчик настроит его вручную.
- Если каждый фрагмент записи содержит модуль, он не будет использоваться совместно, а будет упакован в каждый пакет.
дедупликация модуля
вебпак имеет встроенныйSplitChunksPlugin
, который может легко извлекать повторяющиеся модули в общедоступные зависимости и уменьшать размер файла. Более того, некоторые сторонние зависимости (как правило, не требующие частого изменения) извлекаются в публичные зависимости, что также удобно кешировать клиенту, при каждом обновлении кода пользователю не нужно заново скачивать эти модули без изменений.
// build/webpack.base.conf.js
module.exports = {
// ...
entry: {
app: '../src/index.js',
another: '../src/another-module.js'
},
output: {
filename: '[name].bundle.js',
path: path.resolve(__dirname, '../dist')
},
optimization: {
splitChunks: { // 添加此配置
chunks: 'all'
}
}
};
Теперь переупакуйте, модули, используемые приложением и другими, будут извлечены в поставщика, избегая дублирования модулей.
...
Asset Size Chunks Chunk Names
another.bundle.js 5.95 KiB another [emitted] another
index.bundle.js 5.89 KiB index [emitted] index
vendors~another~index.bundle.js 547 KiB vendors~another~index [emitted] vendors~another~index
Entrypoint index = vendors~another~index.bundle.js index.bundle.js
Entrypoint another = vendors~another~index.bundle.js another.bundle.js
...
Автоматическое разделение чанков — самое большое улучшение в webpack 4.0.Если одновременно выполняются следующие условия, чанки будут разделены:
- Новые фрагменты используются повторно или из каталога node_modules.
- Размер нового фрагмента превышает 30 КБ (до min+gzip).
- Количество одновременных запросов на загрузку фрагментов по запросу меньше или равно 5.
- Количество одновременных запросов при первоначальной загрузке страницы меньше или равно 3.
splitChunks
Официальная конфигурация по умолчанию выглядит следующим образом, при необходимости ее можно изменить:
{
optimization: {
splitChunks: {
chunks: "async", // 对哪些模块优化,取值有"initial"(初始化值) | "all"(所有,推荐) | "async" (动态导入,默认) | 函数
minSize: 30000, // 最小尺寸,小于此值才会分割
minChunks: 1, // 最小 chunk ,包被引用几次以上才会分割
maxAsyncRequests: 5, // 按需加载的最大并行请求数, 默认5
maxInitialRequests: 3, // 最大的初始化加载次数,默认3
automaticNameDelimiter: '~', // 打包分隔符
name: true, // 拆分出来块的名字,默认由块名和 hash 值自动生成,此选项可接收 function
cacheGroups: { // 这里开始设置缓存的 chunks ,缓存组
vendors: {
test: /[\\/]node_modules[\\/]/,
priority: -10,
},
default: {
minChunks: 2,
priority: -20,
reuseExistingChunk: true,
}
}
}
}
}
Динамический импорт и загрузка по требованию
Webpack предоставляет два способа динамического импорта кода.
-
require.ensure
: Путь остался с первых дней. -
import()
грамматика: Метод динамического импорта модулей в спецификации ES (рекомендуется).
import()
даПредложение ECMAScriptМетод динамического импорта модулей (асинхронно) в .Когда веб-пакет встречает этот синтаксис, он генерирует отдельный фрагмент с импортированным файлом в качестве записи и выводит отдельный пакет. При выполнении кода не все пакеты будут загружены, но пакет будет загружен при выполнении импортированного файла, что является процессом загрузки по требованию.
Загрузка по требованию, также известная как «ленивая загрузка», является очень хорошим методом оптимизации, который может значительно повысить начальную скорость загрузки приложения, а также значительно уменьшить размер загружаемого пользователем файла.
Например, в приложении есть 9 страниц, к которым могут получить доступ администраторы, а обычные пользователи могут получить доступ только к двум из них.Если загрузка по запросу не используется, обычные пользователи могут получить доступ только к двум страницам, но они все равно будут загружать все Страница зависит от файла, что неразумно.
Точно так же следующую страницу не нужно загружать в первую очередь.module.js
, и загружать и выполнять код только при нажатии кнопки.Вы можете наблюдать за загрузкой на вкладке сети инструментов разработчика браузера.
document.getElementById('my-button').onclick = function() {
import('./module.js').then(fn => {
fn.default && fn.default();
});
}
module.js
и его внутренние зависимости будут генерировать отдельный пакет и загружать его по требованию. Если мы хотим контролировать имя выходного пакета, мы также можем указать имя чанка, что требует использования магических аннотаций веб-пакета.
document.getElementById('my-button').onclick = function() {
import(/* webpackChunkName: "moduleA" */ './module.js').then(fn => { // 指定chunkName为moduleA
fn.default && fn.default();
});
}
намекать:import()
вернет обещание, поэтому вы также можете использоватьasync function
Оптимизированная цепочка.
document.getElementById('my-button').onclick = async function() {
const fn = await import(/* webpackChunkName: "moduleA" */ './module.js')
fn.default && fn.default();
}
**Расширение:** В дополнение к webpackChunkName, webpack также имеет две другие волшебные аннотации для предварительной выборки и предварительной загрузки модулей, вы можете проверить конкретный метод использования.официальная документация.
В реальных проектах сегментация кода также может относиться к следующим принципам разделения:
- Разделение бизнес-кода и сторонних зависимостей
- Разделение бизнес-кода, бизнес-общего кода и сторонних зависимостей
- Разделение кода, загружаемого после первой загрузки и после доступа
Включить сжатие
Если на сервере развертывания включен gzip, его можно использоватьcompression-webpack-plugin
Создавайте пакеты gzip, чтобы сократить время загрузки.
npm i compression-webpack-plugin -D
// build/webpack.prod.conf.js
const CompressionWebpackPlugin = require('compression-webpack-plugin');
const productionGzipExtensions = ['js', 'css', 'json', 'txt', 'html','ico','svg'];
module.exports = {
plugins:[
new CompressionWebpackPlugin({
// 开启gzip压缩
algorithm: 'gzip', // 压缩算法
test: new RegExp('\\.(' + productionGzipExtensions.join('|') + ')$'),
threshold: 10240, // 仅处理大于此大小的资源(以字节为单位)
minRatio: 0.8 // 压缩比大于此值才处理
})
]
}
Используйте CDN
Также распространенным методом оптимизации является введение некоторых сторонних зависимостей в CDN, потому что обычно сторонние модули обновляются не так часто, как бизнес-коды.После использования CDN клиент будет кэшировать эти ресурсы для повышения скорости загрузки приложения. Мало того, эти зависимости не будут участвовать в процессе сборки (не упакованы в выходной файл), что также может повысить скорость сборки.
// build/webpack.base.conf.js
module.exports = {
externals: {
'vue': 'Vue', // 配置需要排除的包名称
// ...
}
}
В JS или используйтеimport Vue from 'vue'
Вот и все, но не забудьте импортировать в html.
<!-- public/index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>app</title>
</head>
<body>
<div id="root">root</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.11"></script>
</body>
</html>
Повышение эффективности сборки
С развитием бизнеса масштаб нашего проекта может становиться все больше и больше, и кодов будет все больше и больше, и мы попадем в неловкую ситуацию: десять секунд на изменение кода и десять минут на сборку, а сверхурочная работа не является проблема. Поэтому, чтобы иметь больше времени на свидания с богиней, необходимо повысить эффективность построения веб-пакетов.
Суть повышения эффективности построения webpack заключается в том, чтобы позволить ему делать меньше работы, и его следует максимально избегать делать какие-то ненужные вещи. Примерно в этом направлении мы можем начать что-то делать.
оптимизацияresolve
Правила парсинга
Оптимизируя правила синтаксического анализа при разрешении, веб-пакет может быстрее находить указанный модуль, не выполняя дополнительную работу по сопоставлению запросов.
// build/webpack.base.conf.js
resolve: {
modules: [
path.resolve(__dirname, 'node_modules'), // 使用绝对路径明确指定 node_modules
],
// 减少后缀自动补全类型,减少自动匹配工作,缩短文件路径查询的时间,其他类型的文件需要在引入时指定后缀名
extensions: [".js"],
// 避免使用默认文件,而是必须使用完整的路径
mainFiles: ['index'],
},
сузить поиск
Например, когда настроены правила загрузчика, некоторые правила (включить, исключить и т. д.) используются для сужения области поиска модулей, поэтому ищется и обрабатывается меньше файлов, а скорость, естественно, повышается.
// build/webpack.base.conf.js
module.exports = {
module: {
rules: [
{
test: /\.js$/,
include: path.resolve(__dirname, 'src'), // 仅仅搜索src下的文件,一般node_modules中的文件都已编译好,不需再处理,需要排除掉
loader: 'babel-loader'
}
]
}
};
рационализироватьloader/plugin
Каждый загрузчик/плагин должен потреблять время на разработку, пытаться сократить количество избыточных или ненужных инструментов и конфигураций, а также пытаться выбрать более высокую производительность при выборе этих инструментов.
Использовать DLL-плагин
DLLPluginЭто плагин, официально предоставленный webpack, использующийDllPlugin
Создавайте отдельные результаты компиляции для редко изменяемого кода (например, vue, react и других сторонних модулей) и кэшируйте их, а затем используйте эти файлы непосредственно в последующих сборках, чтобы избежать повторных сборок. Это может значительно повысить скорость компиляции приложения.
Сначала вам нужно использовать встроенный плагин webpackDLLPluginСоздайте библиотеку динамической компоновки ресурсов, а затем используйтеDLLReferencePluginДостаточно сопоставить соответствующие ресурсы с этими библиотеками динамической компоновки, но эта конфигурация еще сложнее.
мы можем выбратьAutoDllPlugin, который объединяет функции двух вышеуказанных плагинов, а его конфигурация относительно проста.
npm install --save-dev autodll-webpack-plugin
// build/webpack.base.conf.js
plugins: [
//...
new AutoDllPlugin({
inject: true,
filename: '[name].js',
entry: {
vendor: [
'vue'
]
}
})
]
Видно, что разница между двумя временами упаковки довольно большая, и она действительно более эффективна.В настоящее время образцовых модулей проекта немного, и эффект должен быть более значительным в больших проектах.
маленький это быстро
Используйте меньшее количество библиотек меньшего размера, например, замените momentJS на dayJS, чтобы уменьшить общий размер скомпилированных результатов и повысить производительность сборки. Старайтесь, чтобы кусочки были как можно меньше.
- Используйте меньше/меньше библиотек.
- Использование в многостраничных приложениях
SplitChunksPlugin
. - Использование в многостраничных приложениях
SplitChunksPlugin
, и включитеasync
модель. - Удалить неиспользуемый код.
- Компилируйте только тот код, который вы сейчас разрабатываете.
постоянный кеш
использоватьcache-loader
Включение постоянного кэширования для некоторых загрузчиков, интенсивно использующих производительность, может уменьшить количество файлов перестроения и эффективно повысить скорость сборки.
npm install cache-loader -D
// build/webpack.base.conf.js
module.exports = {
module: {
rules: [{
test: /\.jsx?$/,
use: ['cache-loader','babel-loader'] // 仅需将cache-loader放在需要缓存的loader前面就行了
}]
}
}
**Совет.** Некоторые загрузчики сами поддерживают настройку кеша. Вы можете использовать функцию кеша, поставляемую вместе с загрузчиком. если даноbabel-loader
cacheDirectory настраивает путь к кешу (true или no path установлен для использования пути по умолчаниюnode_modules/.cache/babel-loader
), чтобы включить кэширование.
В дополнение к использованию загрузчика некоторые плагины также могут предоставлять функции кэширования, такие какHardSourceWebpackPlugin.
Так как кэшу нужно записать файл на диск, а запись файла тоже стоит дорого, поэтому на краю следует использовать хорошую сталь, и рекомендуется включать только ресурсоемкий загрузчик.
webpack5.0 имеет встроенныйСхема постоянного кэширования, что может эффективно повысить скорость сборки.
Адаптироваться к местным условиям
Выберите подходящие конфигурации для разных сред, например, настройте соответствующий инструмент разработки (исходную карту) и отсутствие оптимизации сжатия кода в среде разработки.
включить многопроцессорность
thread-loader
Можно управлять несколькими процессами, а ресурсоемкий загрузчик можно назначить рабочему процессу, тем самым снижая нагрузку на производительность основного процесса.
npm install thread-loader -D
// build/webpack.base.conf.js
module.exports = {
module: {
rules: [
{
test: /\.jsx?$/,
use: ['thread-loader', 'babel-loader'] // 仅需在对应的loader前面添加thread-loader即可
}
]
}
}
Не используйте слишком много рабочих процессов, потому что и среда выполнения Node.js, и загрузчики имеют дополнительные затраты на запуск. Свести к минимуму передачу данных между воркером и основным процессом (main process), т.к. межпроцессное взаимодействие (IPC, Inter Process Communication) тоже очень ресурсоемкое.
Кроме
thread-loader
, HappyPack тоже может открывать мультипроцесс, но конфигурация немного сложнее, т.к. больше не поддерживается, использовать не рекомендуется.Теперь встроенное JS-сжатие веб-пакета использует TerserWebpackPlugin, который уже открыл многопроцессорность, поэтому его больше не нужно открывать вручную.
В дополнение к вышеперечисленным методам каждая итерация webpack и nodeJS также будет значительно повышать производительность, а своевременное обновление инструментов также является эффективным методом повышения эффективности сборки. Другие методы оптимизации производительности можно просмотретьОфициальная документация — Производительность сборки.
Развитие внутренней силы — принципы
Принцип работы
Запуск процесса
Процесс работы WebPack - это последовательный, следующим образом:
- Параметры инициализации: чтение и объединение конфигураций параметров из файлов конфигурации и операторов оболочки для получения окончательной конфигурации параметров;
- Начать компиляцию: Инициализируйте объект Compiler с конфигурацией параметров, полученной на предыдущем шаге, загрузите все настроенные плагины и выполните метод запуска объекта, чтобы начать компиляцию;
- Определить запись: найти все файлы записи в соответствии с записью в конфигурации;
- Скомпилируйте модуль: начиная с входного файла, вызовите все сконфигурированные загрузчики для преобразования модуля, затем найдите модули, от которых зависит модуль, а затем повторите этот шаг. Пока на этом шаге не будут обработаны все файлы, зависящие от записи;
- Завершение компиляции модуля: После перевода всех модулей с помощью Loader на шаге 4 получается финальное содержимое каждого модуля после перевода и зависимости между ними;
- Выходные ресурсы: в соответствии с зависимостями между записями и модулями соберите фрагменты, содержащие несколько модулей, один за другим, а затем преобразуйте каждый фрагмент в отдельный файл и добавьте его в список выходных данных.Этот шаг — последний шанс изменить выходное содержимое. ;
- Вывод завершен: после определения содержимого вывода определите путь вывода и имя файла в соответствии с конфигурацией и запишите содержимое файла в файловую систему.
Совет: В вышеописанном процессе webpack будет транслировать соответствующие события в определенное время, что удобно для запуска работы хуков, прослушивающих эти события в плагине.
Вроде процессов много, можно упростить:
-
Инициализация: запустите сборку, прочитайте и объедините параметры конфигурации, загрузите подключаемый модуль и создайте экземпляр компилятора.
-
Компиляция: отправляется из Entry, последовательно вызывает соответствующий загрузчик для каждого модуля, чтобы перевести содержимое файла, затем находит модуль, от которого зависит модуль, и рекурсивно компилирует его.
-
Вывод: объедините скомпилированные модули в куски, преобразуйте куски в файлы и выведите их в файловую систему.
Использованная литература:Обзор того, как работает веб-пакет
Реализовать простой веб-пакет
Как говорится: прочитать тысячи книг не так хорошо, как проехать тысячи миль, а говорить о большем количестве принципов не так реалистично, как набирать код. Давайте шаг за шагом реализуем простой упаковщик.
Готов к работе
Создайте проект со следующими файлами и папками:
// /src/index.js
import a from './a.js';
console.log(a);
// /src/a.js
import b from './b.js';
const a = `b content: ${b}`;
export default a;
// /src/b.js
const b = 'Im B';
export default b;
Теперь такой код нельзя запускать в браузерах, не поддерживающих ESModule, его нужно конвертировать с помощью упаковщика, и он сразу запустится.
Реализовать упаковку модулей
Перед вскрытием уточняем цели и процесс упаковки:
- Найдите запись проекта (т.е.
/src/index.js
) и прочитать его содержимое; - Анализировать содержимое входного файла, рекурсивно находить его зависимости и генерировать граф зависимостей;
- В соответствии с сгенерированным графом зависимостей скомпилируйте и сгенерируйте окончательный выходной код.
/myBundle.js
Это наш упаковщик, в нем также будет написан весь соответствующий код, приступим!
1. Получите содержимое модуля
Прочитайте содержимое файла ввода, это очень просто, мы создаем методgetModuleInfo
,использоватьfs
чтобы прочитать содержимое файла:
// myBundle.js
const fs = require('fs')
const getModuleInfo = file => {
const content = fs.readFileSync(file, 'utf-8')
console.log(content)
}
getModuleInfo('./src/index.js')
Несомненно, это выведетindex.js
Содержимое файла, но это набор строк, откуда нам знать, от каких модулей он зависит? Есть два способа:
- Обычный: получить соответствующий путь к файлу, регулярно сопоставляя ключевое слово «импорт», но это слишком хлопотно и ненадежно Что, если в коде есть строка, которая также имеет это содержимое?
- бабель: может пройти
@babel/parser
Преобразовать код в AST (Абстрактное синтаксическое дерево, сокращенно AST), а затем проанализировать AST для поиска зависимостей. Это кажется более надежным.
Не сомневайтесь, воспользуйтесь вторым способом.
npm i @babel/parser ## 安装 @babel/parser
// myBundle.js
const fs = require('fs')
const parser = require('@babel/parser')
const getModuleInfo = file => {
const content = fs.readFileSync(file, 'utf-8')
const ast = parser.parse(content, {
sourceType: 'module' // 解析ESModule须配置
})
console.log(ast)
console.log(ast.program.body)
}
getModuleInfo('./src/index.js')
Результат преобразования выглядит следующим образом, вы можете увидеть всего два узла,type
атрибут идентифицирует тип узла,ImportDeclaration
что соответствуетimport
предложение, котороеsource.value
То есть относительный путь импортируемого модуля. У меня есть все данные, которые я хочу, разве это не здорово!
2. Создайте таблицу зависимостей
С данными на предыдущем шаге нам нужно создать для них структурированную таблицу зависимостей, чтобы облегчить последующую обработку кода.
На самом деле он проходитast.program.body
, которые будутImportDeclaration
Тип узла извлекается и сохраняется в таблице зависимостей.
Здесь нет необходимости вручную реализовывать детали, просто используйте их напрямую.@babel/traverse
Вот и все.
npm i @babel/traverse ## 安装@babel/traverse
getModuleInfo
Метод модифицируется следующим образом:
// myBundle.js
const fs = require('fs')
const path = require('path')
const parser = require('@babel/parser')
const traverse = require('@babel/traverse').default
const getModuleInfo = (file) => {
const content = fs.readFileSync(file, 'utf-8');
const ast = parser.parse(content, {
sourceType: 'module'
});
const dependencies = {}; // 用于存储依赖
traverse(ast, {
ImportDeclaration({ node }) { // 只处理ImportDeclaration类型的节点
const dirname = path.dirname(file);
const newFile = '.'+ path.sep + path.join(dirname, node.source.value); // 此处将相对路径转化为绝对路径,
dependencies[node.source.value] = newFile;
}
});
console.log(dependencies);
};
getModuleInfo('./src/index.js')
Результат выглядит следующим образом:
Далее мы можем вернуть полную информацию о модуле.
Здесь мы проходим мимоbabel
инструменты ("@babel/core,
@babel/preset-env`), чтобы преобразовать код в синтаксис ES5.
npm i @babel/core @babel/preset-env ## 安装@babel/core @babel/preset-env
// myBundle.js
const fs = require('fs');
const path = require('path');
const parser = require('@babel/parser');
const traverse = require('@babel/traverse').default;
const babel = require('@babel/core');
const getModuleInfo = (file) => {
const content = fs.readFileSync(file, 'utf-8');
const ast = parser.parse(content, {
sourceType: 'module'
});
const dependencies = {};
traverse(ast, {
ImportDeclaration({ node }) {
const dirname = path.dirname(file);
const newFile = '.'+ path.sep + path.join(dirname, node.source.value);
dependencies[node.source.value] = newFile; // 使用文件相对路径为key,绝对路径为value
}
});
const { code } = babel.transformFromAst(ast, null, {
presets: ['@babel/preset-env']
});
const moduleInfo = { file, dependencies, code };
console.log(moduleInfo);
return moduleInfo;
};
getModuleInfo('./src/index.js');
Результат выглядит следующим образом:
Теперь код модуля был преобразован в объект, содержащий абсолютный путь к модулю, зависимости и код, преобразованный Babel, но приведенное выше касается толькоindex.js
зависимость,a.js
Зависимости не обрабатываются, поэтому это неполная таблица зависимостей, нам нужно продолжить обработку.
На самом деле тоже очень просто, то есть начиная с записи вызывается каждый модуль и его зависимостиgetModuleInfo
Метод анализируется, и, наконец, будет возвращена полная таблица зависимостей (также называемая графом зависимостей, графом зависимостей).
Мы напрямую пишем новый метод для обработки:
// myBundle.js
const generDepsGraph = (entry) => {
const entryModule = getModuleInfo(entry);
const graphArray = [ entryModule ];
for(let i = 0; i < graphArray.length; i++) {
const item = graphArray[i];
const { dependencies } = item;
if(dependencies) {
for(let j in dependencies) {
graphArray.push(
getModuleInfo(dependencies[j])
);
}
}
}
const graph = {};
graphArray.forEach(item => {
graph[item.file] = {
dependencies: item.dependencies,
code: item.code
};
});
return graph;
};
Теперь мы создали полную таблицу зависимостей, а затем можем сгенерировать окончательный код на основе этих данных.
3. Сгенерируйте выходной код
Прежде чем генерировать код, давайте посмотрим на приведенный выше код, вы можете найти его внутри.export
а такжеrequire
Такойcommonjs
Синтаксис, и наша среда выполнения (здесь браузер) не поддерживает этот синтаксис, поэтому вам нужно реализовать эти два метода самостоятельно. Сначала вставьте код, а затем медленно произнесите:
Создайте новый метод сборки для генерации выходного кода.
// myBundle.js
const build = (entry) => {
const graph = JSON.stringify(generDepsGraph(entry));
return `
(function(graph){
function require(module) {
var exports = {};
return exports;
};
require('${entry}')
})(${graph});
`;
};
const code = build('./src/index.js');
проиллюстрировать:
- Третий ряд
JSON.stringify
состоит в том, чтобы преобразовать данные в строку, иначе то, что будет получено в немедленно выполняемой ниже функции, будет[object object]
, так как в строковом шаблоне используется следующее, происходит преобразование типов. - Возвращенный код упаковывается в IIFE (немедленно выполняемая функция), чтобы предотвратить загрязнение области между модулями.
-
require
Функция должна быть определена в выходном содержимом, а не в текущей среде выполнения, поскольку она будет выполняться в сгенерированном коде.
Далее нам нужно получить код входного файла и использоватьeval
функция для его выполнения:
// myBundle.js
const build = (entry) => {
const graph = JSON.stringify(generDepsGraph(entry));
return `
(function(graph){
function require(module) {
var exports = {};
(function(require, exports, code){
eval(code)
})(require, exports, graph[module].code);
return exports;
};
require('${entry}')
})(${graph});
`;
};
const code = build('./src/index.js');
console.log(code);
проиллюстрировать:
- Для того, чтобы код в коде не конфликтовал с нашей областью видимости (в возвращаемой строке), мы все же используем пакет IIFE и передаем в него нужные параметры.
-
graph[module].code
Код входа можно получить из приведенной выше таблицы зависимостей.
Результат выглядит следующим образом:
Это результат упаковки, но не радуйтесь, здесь все равно большая дыра.
То, как мы импортируем модули в сгенерированный код, основано на относительном пути 'index.js', если пути модулей, представленные в других модулях, сравниваются сindex.js
Когда он несовместим, соответствующий модуль не будет найден (путь неверен), поэтому мы должны иметь дело с путем модуля. К счастью, фронт зависит от столаdependencies
Абсолютный путь модуля прописан в атрибуте, и его нужно только вынуть и использовать.
добавить одинlocalRequire
функция для использования изdependencies
Получите абсолютный путь к модулю в формате .
// myBundle.js
const build = (entry) => {
const graph = JSON.stringify(generDepsGraph(entry));
return `
(function(graph){
function require(module) {
function localRequire(relativePath) {
return require(graph[module].dependencies[relativePath]);
}
var exports = {};
(function(require, exports, code){
eval(code)
})(localRequire, exports, graph[module].code);
return exports;
};
require('${entry}')
})(${graph});
`;
};
Затем запишите выходной код в файл.
// myBundle.js
const code = build('./src/index.js')
fs.mkdirSync('./dist')
fs.writeFileSync('./dist/bundle.js', code)
Наконец, внедрите его в html и проверьте, может ли он нормально работать. Нет сомнений, что он будет работать нормально. :Улыбка улыбка:
Наконец, вставьте полный код: :cow::beers:
// myBundle.js
const fs = require('fs');
const path = require('path');
const parser = require('@babel/parser');
const traverse = require('@babel/traverse').default;
const babel = require('@babel/core');
const getModuleInfo = (file) => {
const content = fs.readFileSync(file, 'utf-8');
const ast = parser.parse(content, {
sourceType: 'module'
});
const dependencies = {};
traverse(ast, {
ImportDeclaration({ node }) {
const dirname = path.dirname(file);
const newFile = '.'+ path.sep + path.join(dirname, node.source.value);
dependencies[node.source.value] = newFile;
}
});
const { code } = babel.transformFromAst(ast, null, {
presets: ['@babel/preset-env']
});
const moduleInfo = { file, dependencies, code };
return moduleInfo;
};
const generDepsGraph = (entry) => {
const entryModule = getModuleInfo(entry);
const graphArray = [ entryModule ];
for(let i = 0; i < graphArray.length; i++) {
const item = graphArray[i];
const { dependencies } = item;
if(dependencies) {
for(let j in dependencies) {
graphArray.push(
getModuleInfo(dependencies[j])
);
}
}
}
const graph = {};
graphArray.forEach(item => {
graph[item.file] = {
dependencies: item.dependencies,
code: item.code
};
});
return graph;
};
const build = (entry) => {
const graph = JSON.stringify(generDepsGraph(entry));
return `
(function(graph){
function require(module) {
function localRequire(relativePath) {
return require(graph[module].dependencies[relativePath]);
}
var exports = {};
(function(require, exports, code){
eval(code)
})(localRequire, exports, graph[module].code);
return exports;
};
require('${entry}')
})(${graph});
`;
};
const code = build('./src/index.js');
fs.mkdirSync('./dist');
fs.writeFileSync('./dist/bundle.js', code);
Пока это дело не завершено, вебпак, как благородная красавица, держит людей на расстоянии. И через это дело мы содрали с нее таинственную шубку и обнаружили, что она такая чудесная внутри, не правда ли красивая? Конечно, на практике все не может быть так просто, чтобы справиться с различными граничными условиями, и для поддержки загрузчика и плагина, в красоте еще есть что 😍.
Горячая замена модуля чата
Причина, по которой горячая замена модуля (HMR) указана здесь отдельно, заключается в том, что я всегда думал, что это крутая функция, как черная магия.
В чем преимущество
Горячая замена модуля сокращенно называется HMR (горячая замена модуля), что может значительно улучшить опыт разработки и является очень практичной функцией. До этого чаще использовалиlive-reload
, после того как редактор и браузер установит соответствующие плагины, при сохранении редактора браузер обновит страницу, что на самом деле удобнее, чем ручная F5. Однако он может использовать только полное обновление страницы, поэтому есть некоторые недостатки:
- Неэффективность, полное обновление страницы означает, что все ресурсы будут перезагружены, а скорость можно себе представить.
- Если состояние на странице непостоянно, оно будет сброшено и потеряно после обновления.
Когда gulp был популярен, чаще всего использовалсяBrowserSync
Плагин может открывать несколько браузеров на нескольких устройствах одновременно, а операции щелчка и прокрутки будут синхронизированы во всех браузерах, что также является относительно простым в использовании инструментом. Здесь нет главного героя, нет вступления.
HMR Webpack предназначен для динамической замены, добавления или удаления модулей на основе перезагрузки всей страницы во время работы приложения.По сравнению с предыдущими инструментами, он имеет следующие преимущества:
- Состояние приложения не теряется при обновлении содержимого страницы (не абсолютное, в зависимости от области действия модуля).
- Обновляйте только тот контент, который изменился, вместо полной перезагрузки страницы, быстро и эффективно.
Как это работает
О том, как использовать горячую замену модуля, уже было рассказано ранее, поэтому я не буду здесь повторять использование, просто кратко поясню соответствующие концепции.
Manifest
Сначала вам нужно знатьManifest
, которая представляет собой таблицу данных, поддерживаемую веб-пакетом для управления всеми модулями и их связями в процессе создания. Она содержит подробную информацию, такую как зависимости между модулями и содержимым модуля. Это важная основа для веб-пакета для анализа и загрузки модулей.
процесс обновления
На рисунке выше представлена блок-схема горячего обновления модуля для webpack и webpack-dev-server для разработки приложений, на которой записан весь процесс обновления.
- Красное поле в нижней части рисунка выше — это сторона сервера, а оранжевое поле вверху — сторона браузера.
- Зеленое поле — это область, контролируемая кодом веб-пакета. Синее поле — это область, контролируемая кодом webpack-dev-server, пурпурное поле — это файловая система, в которой происходят изменения файлов, а голубое поле — это само приложение.
На рисунке выше показан цикл от изменения кода до завершения горячего обновления модуля (этапы отмечены порядковыми номерами), процесс выглядит следующим образом:
- webpack наблюдает за файловой системой, упакованной в память. В режиме просмотра WebPACK файловая система модифицируется в документ, отслеживаются изменения файлов WebPACK, переупаковываются скомпилированные модули в соответствии с файлом конфигурации и упаковывается с помощью простого кода объекта JavaScript, хранящегося в памяти (Файл записывается в память, что быстрее и производительнее, используяmemory-fsинструмент полный).
- Взаимодействие интерфейса между webpack-dev-server и webpack. На этом этапе это в основном взаимодействие между промежуточным программным обеспечением dev-сервера webpack-dev-middleware и webpack, webpack-dev-middleware вызывает API, предоставляемый webpack, для отслеживания изменений кода и сообщает webpack упаковать код в середину памяти.
- webpack-dev-server Монитор изменений файлов. Этот шаг отличается от первого тем, что он не отслеживает изменения кода для переупаковки. Когда мы настраиваем в файле конфигурацииdevServer.watchContentBaseКогда это правда, сервер будет отслеживать изменения статических файлов в этих папках конфигурации и уведомит браузер о необходимости выполнить перезагрузку приложения в реальном времени после изменения. Обратите внимание, что здесь обновление браузера и HMR — это два разных понятия.
- Код webpack-dev-server работает. Этот шаг в основном осуществляется черезsockjs(зависимость от webpack-dev-server) Установить длинное соединение по вебсокету между браузером и сервером, сообщить браузеру информацию о статусе каждого этапа компиляции и упаковки вебпака, а также включить мониторинг статических файлов сервером на третьем шаге. изменение информации. Сторона браузера выполняет различные операции на основе этих сообщений сокета. Разумеется, самой важной информацией, передаваемой сервером, является хеш-значение нового модуля, и последующие шаги будут выполнять горячую замену модуля на основе этого хэш-значения.
- Сторона webpack-dev-server/client не может ни запрашивать обновленный код, ни выполнять операции горячего модуля и передавать эти задачи webpack.Работа webpack/hot/dev-server основана на webpack-dev. -server/client и конфигурация dev-server определяют, следует ли обновлять браузер или выполнять горячее обновление модуля. Конечно, если вы просто обновите браузер, дальнейших шагов не будет.
- HotModuleReplacement.runtime является хабом HMR клиента, получает хеш-значение нового модуля, переданного ему на предыдущем шаге, отправляет Ajax-запрос на сервер через JsonpMainTemplate.runtime, и сервер возвращает json, содержащий все обновления для обновления.После получения обновленного списка хэш-значения модуля модуль снова запрашивает последний код модуля через jsonp. Это шаги 7, 8 и 9 на изображении выше.
- 10-й шаг является ключевым шагом для определения успеха HMR.На этом этапе HotModulePlugin сравнит старый и новый модули, чтобы решить, следует ли обновлять модуль.Приняв решение об обновлении модуля, проверьте зависимости между модулями и обновите module Также обновите зависимости между модулями.
- На последнем шаге, когда HMR дает сбой, вернитесь к операции перезагрузки в реальном времени, которая заключается в обновлении браузера для получения последнего упакованного кода.
Вышеуказанный процесс горячего обновления.Конечно, это просто работа, проделанная webpack.В это время бизнес-код не может знать, изменился ли код или нет.Мы должны использовать ранее упомянутыеaccept
метод для мониторинга и осуществления соответствующих изменений соответственно.
if(module.hot) { // 先判断是否开启HMR
module.hot.accept('./xxx.js', function() {
// do something
})
}
намекать:accept
Содержимое метода, как правило, не нужно реализовывать самостоятельно, многие инструменты (такие как vue-loader, style-loader) предоставляются внутри и могут использоваться напрямую.
Использованная литература:Принципиальный анализ Webpack HMR,Потрясающее учебное пособие по Webpack HMR.
загрузчик разработки
Официальный вебпак и комьюнити предоставили много загрузчиков, обычно готовые загрузчики можно найти в обычном разборе файлов. Однако изучение того, как разработать загрузчик, поможет вам понять принцип работы загрузчика, и вы также сможете создать загрузчик самостоятельно, когда он вам понадобится.
Разработать простой загрузчик
На самом деле настроить загрузчик очень просто, по сути загрузчик это функция, которая получает параметры и обрабатывает их, а потом возвращает результаты обработки (Должен быть буфером или строкой).
- новый
/loaders/replaceLoader.js
,
// /loaders/replaceLoader.js
module.exports = function (source) {
source = source.replace(/webpack/gi, 'world'); // 将源文件中的webpack替换成world
return source;
};
- новый
/src/index.js
// /src/index.js
console.log('hello webpack');
- настроить веб-пакет
Далее необходимо понять, как настроить вышеуказанный загрузчик. Как правило, сторонний загрузчик устанавливается вnode_modules
Далее webpack будет искать его здесь, но теперь загрузчик находится в/loaders/testLoader.js
, поэтому необходимо выполнить некоторую обработку следующими способами:
- Непосредственно требуется соответствующий загрузчик (применимо к одному загрузчику)
// webpack.config.js
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
loader: path.resolve('./loaders/replaceLoader.js')
}
]
}
- использовать
resolveLoader
Элемент конфигурации (применимо к нескольким загрузчикам)
// webpack.config.js
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use:'replaceLoader'
}
]
},
resolveLoader: {
modules: ['node_modules', path.resolver(__dirname, 'loaders')] // 查找优先级从左到右
}
- Если вы собираетесь опубликовать загрузчик в npm, вы также можете использоватьnpm-link.
Здесь, используя второй способ, полная конфигурация выглядит следующим образом:
// webpack.config.js
const path = require('path');
module.exports = {
mode:'development',
entry: './src/index.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist')
},
resolveLoader: {
modules: ['node_modules', path.resolve(__dirname, 'loaders')]
},
module: {
rules: [{
test: /\.js$/,
use:'replaceLoader'
}]
}
};
Запустите его, пакетная командаnpx webpack
, вы можете видеть, что webpack в выходном файле заменен на world.
Как правило, загрузчик предоставляет элементы конфигурации (параметры), что удобно для пользователей, чтобы сделать персонализированную конфигурацию.this.query
Получите содержимое конфигурации, но обычно используйте официально рекомендованныеloader-utilsИнструменты удобнее.
// webpack.config.js
const path = require('path');
module.exports = {
mode:'development',
entry: './src/index.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist')
},
resolveLoader: {
modules: ['node_modules', path.resolve(__dirname, 'loaders')]
},
module: {
rules: [{
test: /\.js$/,
use: {
loader:'replaceLoader',
options: {
src: /webpack/ig, // 配置要替换的内容
to: 'world!' // 配置替换的目标内容
}
}
}]
}
};
немного отремонтироватьreplaceLoader
:
// /loaders/replaceLoader.js
const loaderUtils = require('loader-utils');
module.exports = function (source) {
const options = loaderUtils.getOptions(this);
source = source.replace(options.src, options.to);
return source;
};
**Примечание: **Это используется здесь.Это дает много полезной информации, поэтому стрелочные функции нельзя использовать (что изменит указатель this). Если вам нужно проверить параметры, вы можете использоватьschema-utils.
сложная ситуация
Пока реализован простой загрузчик, конечно иногда может быть и посложнее.
Фактически функция загрузчика получит три параметра: контент, карта, мета.
- content: содержимое модуля, которое может быть строкой или буфером
- карта: объект исходной карты
- мета: некоторая информация о метаданных
Если вы просто возвращаете результат обработки, вы можете напрямую вернуть контент. Но если вам нужно сгенерировать объекты исходной карты, метаданные или генерировать исключения, вам нужно заменить return наthis.callback(err, content, map, meta)
передавать данные.
this.callback(
// 当无法转换原内容时,给 Webpack 返回一个 Error
err: Error | null,
// 原内容转换后的内容
content: string | Buffer,
// 用于把转换后的内容得出原内容的 Source Map,方便调试
sourceMap?: SourceMap,
// 如果本次转换为原内容生成了 AST 语法树,可以把这个 AST 返回,
// 以方便之后需要 AST 的 Loader 复用该 AST,以避免重复生成 AST,提升性能
abstractSyntaxTree?: AST
);
Кроме того, Loader также поддерживает асинхронные задачи, которые можно использовать.this.async()
выполнить.
// /loaders/replaceLoader.js
const loaderUtils = require('loader-utils');
module.exports = function (source) {
const options = loaderUtils.getOptions(this);
const asyncfunc = this.async() // 调用异步func
setTimeout(() => {
source = source.replace(options.src, options.to);
asyncfunc(null, source) // 传递结果
}, 200)
};
Что касается разработки загрузчика, официально предоставлено множество API, читатели могут обратиться кloader interfaceК пониманию.
Меры предосторожности
- Загрузчик должен следовать принципу единой ответственности.Загрузчик выполняет только одно преобразование.Если исходный файл необходимо преобразовать несколько раз, для его реализации необходимо несколько загрузчиков.
- Когда для обработки файла вызывается несколько загрузчиков, по умолчанию загрузчики будут выполняться в цепочке от конца к началу.Последний загрузчик получит исходное содержимое файла, а после обработки результат будет передан в следующий загрузчик для продолжения обработки до тех пор, пока самый передний загрузчик не будет обработан и возвращен в webpack.
плагин разработки
Плагин является очень важной частью веб-пакета, он дает веб-пакету мощные возможности расширения. В отличие от загрузчика, загрузчик в основном позволяет веб-пакету анализировать больше типов файлов, в то время как плагин может участвовать на каждом этапе процесса упаковки. По тому, как мы его используем, мы видим, что плагин на самом деле является классом, и параметры конфигурации передаются при вызове конструктора.
**Примечание.** Ниже используется версия webpack 4.0, которая отличается от API 3.0.
Разработать простой плагин
По сравнению с загрузчиком, способ введения пользовательского плагина очень прост:
// webpack.config.js
const path = require('path')
const TestWebpackPlugin = require('./test-webpack-plugin')
module.exports = {
mode: 'development',
entry: './src/index.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name]_[hash].js'
},
plugins: [
new TestWebpackPlugin({
// ...options
})
]
}
Имя плагина для веб-пакета должно следовать
插件名字-webpack-plugin
формат.
Внутренняя реализация плагина также относительно проста и состоит из следующих частей:
- Функция класса, которую можно вызвать с помощью new
- в этой функции
prototype
определитьapply
метод, параметрыcompiler
- существует
apply
Зарегистрируйте имя хука, имя плагина и функцию обратного вызова, которые будут отслеживаться в методе. - Чтение или управление внутренними данными экземпляра Webpack с помощью введенных параметров в функции обратного вызова.
- Асинхронный обработчик событий, который необходимо вызывать при обработке плагина.
callback
или вернутьсяpromise
// test-webpack-plugin.js
class TestWebpackPlugin {
constructor (options) {
// 在这里获取插件配置
}
// Webpack 会调用此 apply 方法并传入 compiler 对象
apply (compiler) {
// 这里可以在compiler的钩子(hook)上注册一些方法, 当webpack执行到特定的钩子时就会执行该阶段注册的方法
compiler.hooks.done.tap('TestWebpackPlugin', (stats) => {
console.log('Hello TestWebpackPlugin!');
// ...
})
// 在emit钩子上注册一个处理函数,因为该钩子为异步的,所以需要使用tapAsync
// emit钩子执行时机在资源输出到output之前
compiler.hooks.emit.tapAsync('TestWebpackPlugin', (compilation, cb) => {
// 在输出文件中增加一个文件test.js
compilation.assets['test.js'] = {
source() { // 文件内容
return 'console.log("hello world")'
},
size() { // 文件的长度
return 27;
}
};
cb(); // 处理完成调用cb
})
}
}
module.exports = TestWebpackPlugin
Процесс реализации
Ядром плагина является применение.При использовании плагина веб-пакет автоматически вызывает метод применения экземпляра плагина и передает компилятору в качестве параметра. существуетapply
Внутри мы можемcompiler
Различные функции слушателя (режим публикации и подписки) регистрируются на определенных хуках в хуках.Когда веб-пакет выполняет эти хуки, он вызывает соответствующие функции слушателя для обработки процесса построения.
Давайте возьмем предыдущий пример, чтобы понять конкретный процесс выполнения плагина:
-
пройти через
<instance>.hooks.<hook name>.<tap API>('<plugin name>', callback )
способ регистрации событий ловушек.-
instance
:compiler
илиcompilation
пример -
hook name
: имя целевого хука монтирования - Хук обработки может быть синхронным или асинхронным, и вам нужно использовать тот, который вы выбираете в зависимости от различных ситуаций.
tap API
,tap API
Есть три вида:-
tap
Используется для монтирования синхронного обратного вызова, подходящего для любого типа обработчика событий, и использования обратного вызова для возврата результата. -
tapAsync
Он используется для монтирования асинхронного обратного вызова, его нельзя использовать для хуков синхронного типа, он будет внедрен в обратный вызов.callback
Параметры для вызова плагина после обработки операции, если он не вызываетсяcallback
Возврат управления процессом и последующие операции будут невозможны. -
tapPromise
а такжеtapAsync
Функция и ограничение аналогичны, разница в том, что требуется вернутьPromise
экземпляр, и этоPromise
должен быть разрешен (независимо от разрешения или отклонения )
-
-
// 同步
compiler.hooks.done.tap('MyPlugin', (stats, callback) => {
// ...
callback()
})
// 异步promise
compiler.hooks.emit.tapPromise('MyPlugin', (compilation) => {
return new Promise((resolve, reject) => {
// ...
})
})
// 异步async function
compiler.hooks.emit.tapPromise('MyPlugin', async (compilation) => {
await new Promise((resolve, reject) => {
// ...
})
})
// 异步回调
compiler.hooks.emit.tapAsync('MyPlugin', (compilation, callback) => {
// ...
callback()
})
-
Функция, зарегистрированная на хуке, получает два параметра, первый — это имя плагина, а второй — функция обратного вызова.
- Эта функция обратного вызова является основным содержанием функции обработки, а полученные параметры определяются хуком, где
emit
Хук получает два параметра (соответственно компиляция, cb), компиляция записывает соответствующее содержимое этой упаковки, и вызывается функция обратного вызова cb для возврата управления после завершения обработки.
- Эта функция обратного вызова является основным содержанием функции обработки, а полученные параметры определяются хуком, где
-
compilation.assets
Сохраните всю информацию о файле, упакованную на этот раз, приведенный выше пример будетassets
добавилtest.js
Файл (при условии, что перед его добавлением в активах нет файла с таким же именем), источник и размер соответственно задают содержимое и длину файла. Если файл обрабатывается, непосредственноassets
Вы можете работать с файлами в формате . -
Хуки также могут быть зарегистрированы при компиляции, т.е.Compiler Hooksа такжеCompilation Hooksдва вида.
compiler.hooks.compilation.tap('MyPlugin', (compilation) => {
compilation.hooks.optimizeChunkAssets.tapAsync(
'MyPlugin',
(chunks, callback) => {
// 处理chunks
// 结束调用callback方法
callback();
}
);
})
hooks
Помимо вышеперечисленногоemit
Хуки, webpack также предоставляет множество других хуков, охватывающих различные этапы упаковки.
entryOption
существуетentry
Выполняется после обработки элемента конфигурации, ловушка синхронизации.
compiler.hooks.entryOption.tap('MyPlugin', (context, entry) => {
// context保存了当前目录信息,entry保存了入口文件信息
});
afterEmit
генерировать ресурсы дляoutput
Асинхронный хук после выполнения.
done
Компиляция завершает выполнение, хук синхронизации.
compiler.hooks.done.tap('TestWebpackPlugin', (stats) => {
// stats保存了生成文件的内容
})
Другие хуки и использование можно посмотретьPlugin-Compiler Hooks.
Реализация этих хуков основана наtapableЭта библиотека, эта библиотека предоставляет массу хуков. Эти крючки можно условно разделить на следующие категории:
- Параллельно: Имена с
parallel
, функции этого класса будут вызываться параллельно после регистрации. - Последовательно: имена с
bail
, функции этого класса будут вызываться последовательно после регистрации. - Потоковое: Имена с
waterfall
, после того как функция этого класса будет зарегистрирована, она будет стримиться при вызове, а возвращаемый результат предыдущей функции будет использоваться как параметр следующей функции. - Комбинированные: есть также хуки, в которых объединены три вышеуказанных правила.
С помощью этих крючков разработчики подключаемых модулей могут вмешиваться во весь процесс создания, чтобы контролировать процесс создания и содержимое, например поиск зависимостей, мониторинг файлов, изменение содержимого файлов и т. д., поэтому ключ к написанию подключаемого модуля заключается в использовании различных функций крючков.
Здесь у нас есть только краткое представление о загрузчике и плагине. Реальная разработка может быть намного сложнее, и здесь невозможно исчерпать все сценарии и способы использования. Это только руководство. В реальной разработке вам необходимо ознакомиться с соответствующими документами. для глубокого понимания.
Справочная документация:официальный сайт webpack - китайский,На языке непрофессионала webpack,Глубокое погружение в Webpack
Эпилог
Каждая версия веб-пакета имеет кодовое имя, а версия 4.0 называетсяLegato
, что означает «легато», подразумевая, что веб-пакет постоянно развивается. Точки развития веб-пакета определяются участниками и пользователями. В версии 3.0 в weback 4.0 были значительно улучшены пользовательский интерфейс с наибольшим количеством голосов, производительность сборки и т. д.
В настоящее время веб-пакет прочно закрепился в индустрии передовых строительных инструментов. Это действительно отличный инструмент для построения. Он не только обладает высокой масштабируемостью, но также имеет группу отличных разработчиков и активное сообщество. Дайте ему, чтобы обеспечить стабильную поток жизненной силы, это основная конкурентоспособность webpack.
В будущем, с популярностью спецификации ESModule, возможно, нам больше не понадобятся инструменты для упаковки модулей, и вебпак также может быть заменен, но я считаю, что инструменты сборки не сойдут со сцены, а будут делать только все больше и больше вещей. , потому что концепция инженерии глубоко укоренилась в сердцах людей.
Тем не менее, изучение инструментов построения, таких как веб-пакет, не является нашей конечной целью, потому что в конечном счете они всего лишь инструмент, а инструменты будут обновляться и заменяться, а то, как использовать инструменты для высвобождения производительности, должно быть конечной целью внешнего интерфейса. инженерия . Фронтенд-инжиниринг — это скорее идея, а не конкретный инструмент, это просто средство. На работе вы должны осознанно передать какую-то сложную или трудоемкую работу программе, а также можете поделиться используемыми вами инструментами и методами с другими, чтобы вы могли не только повысить эффективность разработки себя и своей команды, но и увеличить свое влияние Сила, повышение по службе и повышение зарплаты, женитьба на Бай Фумэй... Думая об этом, вы чувствуете себя немного осторожным?