В традиционном режиме одноблочной упаковки, когда код проекта становится все больше и больше, это в конечном итоге приводит к тому, что браузер загружает огромный файл. С точки зрения производительности загрузки страницы это в основном вызывает две проблемы:
Давайте сначала поговорим о первом вопросе. Вообще говоря, на главной странице. JS Код можно разделить на две части: Initital Chunk
иAsync Chunk
,Первое относится к тому, что требуется на первом экране страницы.из JS код, и последний не обязательно требуется для текущей страницы. Типичный пример. компонент маршрутизации
,Это не имеет никакого отношения к текущему маршруту и компонент не нужно загружать. После упаковки проекта в единый пакет,ИлиInitial Chunk
все ещеAsync Chunk
,Все упаковано в один и тот же продукт,То есть,Когда браузер загружает код продукта из,Будут ли Воля оба загружены вместе?,В результате возникает множество дублирующих процессов загрузки.,что влияет на производительность страницы。И пройтиCode Splitting
мы можем Воля Загрузка по программный код разделен на отдельные части chunk,Подать заявку вот таксуществовать Загружать нужно только при загрузке первого экранаInitial Chunk
Вот и все: избегаются избыточные процессы загрузки и повышается производительность страницы.
Во-вторых,онлайнизКонец жизнисередина Ставка
даважныйизпоказатели производительности。дляонлайнсайтс точки внимание, сервер обычно добавляет немного при ответе на ресурсы HTTP Заголовок ответа,наиболее распространенныйиз Один из заголовков ответаcache-control
,Он может указать браузеризСильное кэширование,Например, установите его следующим образом:
cache-control: max-age=31536000
Указывает, что срок действия ресурса составляет один год.,существоватьдо истечения срока действия,доступТо же, что и URL-адрес ресурса,Браузер напрямую использует локальный кеш,Нет необходимости отправлять запрос на сервер,Это значительно снижает загрузку страниц и нагрузку на сеть. но,существоватьодин chunk В режиме упаковки при изменении строки кода весь chunk из url Адрес изменится.
Поскольку инструмент сборки обычно генерирует хеш-значение на основе содержимого продукта, как только содержимое изменяется, вся chunk продуктиз Сильное кэширование недействительно, поэтому одиночное chunk В режиме упаковкииз Конец жизнисередина Ставкачрезвычайно низкий,В основном ноль.
и продолжайтеCode Splitting
после,Изменения кода повлияют только на часть чанка:
Вход Ссылки на файлыA
、B
、C
、D
четыре компонента,когда мы изменяем A из Код после изменения из Chunk Только A
киЗависит от A из чанка
Средний, А Переписка chunk встречаизменять,Это легко понять,Последний такжевстречаизменятьда Потому что соответствующийизпредставлятьзаявлениевстречаизменять,Как здесьиз Входдокументвстречапроисходитьнравиться下内容изменять:
import CompA from './A.d3e2f17a.js'
// возобновлять import заявление
import CompA from './A.a5d2f82b.js'
Другими словами, изменение A
изпосле кода,B
、C
、D
из chunk продукт url не изменилось, что позволяет браузеру повторно использовать локальное из Сильное кэширование, значительно улучшающее скорость загрузки онлайн-приложений.
Мы только что говорили о том, зачем нам нужно распаковывать вещи. Vite В него уже встроена стратегия распаковки, давайте посмотрим на нее дальше. Vite Какой режим распаковки установлен по умолчанию?
в производственной среде Vite полностью использован Rollup для сборки, поэтому распаковка также основана на Rollup завершить из, но Rollup Это фокус сам по себе JS Инструментам упаковки библиотек по-прежнему не хватает возможностей создания приложений Vite. Это просто компенсирует это. Rollup Возможности создания приложений и существующие возможности распаковки хорошо отражены в этом расширении.
Давайте сначала испытаем это на конкретных проектах. Vite Распаковывая, я положил образец проекта в буклет. Gihub На складе у него можно поучиться.
существоватьпроектсерединаосуществлятьnpm run build
,Затем в терминале появится следующая информация о сборке:
Пример использования проекта из is Vite 2.9 Предыдущая версия,Нажмите, чтобы войти в проект。Vite 2.9 Стратегия распаковки будущих версий будет другой, о чем будет сказано позже.
Здесь я объясню структуру продукта:
.
├── assets
│ ├── Dynamic.3df51f7a.js // Async Chunk
│ ├── Dynamic.f2cbf023.css // Async Chunk (CSS)
│ ├── favicon.17e50649.svg // Статические ресурсы
│ ├── index.1e236845.css // Initial Chunk (CSS)
│ ├── index.6773c114.js // Initial Chunk
│ └── vendor.ab4b9e1f.js // Сторонние пакеты Chunk
└── index.html // Вход HTML
для Vite из возможности распаковки,отпродуктструктурасередина Это очевидно。
с одной стороны Vite Реализовано автоматическое Разделение CSS-кодаизспособность,То есть реализовать чанк. Соответствует одному CSS-файлу.,Например, вышепродуктсерединаindex.js
Соответствует одному экземпляруindex.css
,и Загрузка по требованиюиз chunk Danamic.js
Также соответствуетодинодинизодна порцияDanamic.css
документ,и JS Разделение файлов код Аналогично, это также может улучшить CSS Файл из Частота повторного использования кэша。
И еще одной стороны, Vite на основе Rollup изmanualChunks
API выполнить ПонятноРаспаковка приложения
из Стратегия:
Initital Chunk
с точки вопрос, бизнес-код и код стороннего пакета упаковываются отдельно из chunk,существоватьвышеизпримерсерединасоответствующий соответственноindex.js
иvendor.js
。Нужно объяснениеизда,этотда Vite 2.9 Версия до практики и существования Vite 2.9 и более поздних версиях стратегия упаковки по умолчанию проще и грубее, сочетая в себе все js Все коды упакованы в index.js
середина.Async Chunk
с точки зрения , динамичный import код будет разбит на отдельные chunk,нравитьсявышеизDynacmic
компоненты。Подводя итог, Вите Реализована распаковка по умолчанию из существующего преимущества. CSS Разделение кода и бизнес-код, код сторонних библиотек, динамика import Код модуля триизразделение,Но недостатки также более интуитивны.,Упаковка сторонней библиотеки может легко раздуться.,вышепримерсерединаизvendor.js
изразмер достигнут 500 KB Выше явно есть место для дальнейшей распаковки и оптимизации, и нам нужно его использовать в данный момент. Rollup Распаковка API ——manualChunks
.
Для более детальной распаковки используйте Vite из Базовый механизм упаковки Rollup предоставилmanualChunks
,Давайте Пользовательская стратегия распаковки, принадлежит Vite Конфигурацияизчасть,Примеры следующие:
// vite.config.ts
export default {
build: {
rollupOptions: {
output: {
// manualChunks Конфигурация
manualChunks: {},
},
}
},
}
manualChunks
Существует две основные формы Конфигурации.,Может Конфигурациядля объекта или функции。Давайте сначала посмотрим на объектыиз Конфигурация,такжеда Самый простойодиниз Конфигурация Способ,ты Можетсуществоватьвышеиз Примерпроектсередина Добавьте следующееизmanualChunks
Конфигурациякод:
// vite.config.ts
{
build: {
rollupOptions: {
output: {
// manualChunks Конфигурация
manualChunks: {
// Воля React Сопутствующие библиотеки упакованы отдельно. chunk середина
'react-vendor': ['react', 'react-dom'],
// Воля Lodash Код библиотеки упакован отдельно
'lodash': ['lodash-es'],
// Волякомпоненты Библиотекаизкод Пакет
'library': ['antd', '@arco-design/web-react'],
},
},
}
},
}
существоватьформат объектаиз Конфигурациясередина,key
представлять chunk изимя,value
это массив строк,Каждый товар представляет собой сторонний пакет.изимя пакета。существовать Действовало, как указано вышеиз Конфигурацияпосле,мы можемосуществлятьnpm run build
Попробуйте упаковку
Вы можете посмотреть оригинал vendor Большой файл разбивается на несколько маленьких файлов, которые мы указываем вручную. кусок, каждый chunk возможно 200 KB О, это более идеальный из chunk объем。так,Когда сторонние пакеты возобновляютсяиз,также只встречавозобновлять Чтосерединаодин chunk из url,и Нетвстреча Вся суммавозобновлять,отиулучшать Понятно Сторонние пакетыиз Конец жизнисередина Ставка。
кроме объектаиз Конфигурация Способснаружи,Мы также Можетпроходитьфункция Будьте более гибкимииз Конфигурация,и Vite серединаиз Стратегия распаковки по умолчаниютакжедапроходитьфункцияиз Способвыполнять Конфигурацияиз,мы можемсуществовать Vite извыполнитьсередина Взгляните:
// Vite Часть исходного кода
function createMoveToVendorChunkFn(config: ResolvedConfig): GetManualChunk {
const cache = new Map<string, boolean>()
// Возвращаемое значение manualChunks из Конфигурация
return (id, { getModuleInfo }) => {
// Vite по умолчаниюиз Конфигурациялогика Что Это действительно простоодин
// В основном ставить Initial Chunk серединаиз Сторонний пакеткододинодин Пакет成`vendor.[hash].js`
if (
id.includes('node_modules') &&
!isCSSRequest(id) &&
// Определите, является ли это Initial Chunk
staticImportedByEntry(id, getModuleInfo, cache)
) {
return 'vendor'
}
}
}
Rollup будет вызываться для каждого модуля manualChunks функция, в manualChunks извходные параметры функциисерединаты Можетполучатьидентификатор модуля
иДетали модуля
,Через определенное времяиз Возврат после обработки имя файла фрагмента
,таккогдавперед id Представитель модуля будет упакован в указанный вами размер. chunk в файле.
Давайте теперь попробуем реализовать логику распаковки с помощью функций:
manualChunks(id) {
if (id.includes('antd') || id.includes('@arco-design/web-react')) {
return 'library';
}
if (id.includes('lodash')) {
return 'lodash';
}
if (id.includes('react')) {
return 'react';
}
}
Похоже, что различные сторонние пакеты chunk (нравитьсяlodash
、react
и т. д.)Его можно разделить,但实际上ты Можетбегать npx vite preview
Предварительный просмотрпродукт,Вы обнаружите, что продукт вообще невозможно запустить.,На странице появляется белый экран,При этом на консоли появляется сообщение об ошибке
этоттакже就дафункция Конфигурацияиз Подводные камнисуществовать Понятно,Хоть и гибкий и удобный,Но если вы не обратите внимания, вы столкнетесь с такой проблемой. Так в чем же причина вышеуказанной ошибки?
от Сообщить об ошибкеотслеживаемая информацияпродуктсередина,Можетволосысейчасreact-vendor.js
иindex.js
происходить Понятноциклическая ссылка:
// react-vendor.e2c4883f.js
import { q as objectAssign } from "./index.37a7b2eb.js";
// index.37a7b2eb.js
import { R as React } from "./react-vendor.e2c4883f.js";
Это очень типичный сценарий циклической ссылки модуля ES. Мы можем использовать самый простой пример для восстановления этого сценария:
// a.js
import { funcB } from './b.js';
funcB();
export var funcA = () => {
console.log('a');
}
// b.js
import { funcA } from './a.js';
funcA();
export var funcB = () => {
console.log('b')
}
затеммы можемосуществлятьодин разa.js
документ:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<script type="module" src="/a.js"></script>
</body>
</html>
существовать Браузерсередина Открытьвстречавнесейчаспохожийиз Сообщить об ошибке
Принцип выполнения кода следующий:
a.js
Когда выяснилось, что введение b.js
,Вдаидтиосуществлять b.js
b.js
,волосысейчасвпредставлять Понятноa.js
(Возникает циклическая ссылка),думатьa.js
Загрузка завершена,Продолжить выполнениеfuncA()
заявлениеобнаружил, когда funcA Определения нет, поэтому сообщается об ошибке.Процесс выполнения упакованного продукта такой же, как указано выше:
Возможно, у вас есть вопросы: react-vendor
Зачем нужно цитированиеindex.js
изкод Шерстяная ткань?Что实также很好理解,нас ДосуществоватьmunaulChunks
серединатолько Воляпуть содержит react
из Модули упакованы вreact-vendor
середина,Как все знают,картинаobject-assign
Этот вид react самиз Зависимости не упакованыreact-vendor
середина,ида Пакетприезжать另外из chunk когдасередина,Это приводит к следующим циклическим зависимостям:
Так можем ли мы избежать этой проблемы? Конечно, вы можете, прежде чем из manualChunks
логика过В简одингрубый,толькопроходитьпуть id решить, какой из них взять с собой chunk середина,искучать Понятнокосвенная зависимостьиз Состояние。нравиться Целевойкартинаobject-assign
Этот видкосвенная зависимость,Мы также можем определить, что это относится к реакции из зависимости.,Воля Что自动Пакетприезжатьreact-vendor
середина,Таким образом, вы можете избежать проблем с циклическими ссылками.
Разберем решения:
react-vendor
середина.Далее приступим к фактической реализации кода:
// Конечно react Связанные пакетыиз Входпуть
const chunkGroups = {
'react-vendor': [
require.resolve('react'),
require.resolve('react-dom')
],
}
// Vite серединаиз manualChunks Конфигурация
function manualChunks(id, { getModuleInfo }) {
for (const group of Object.keys(chunkGroups)) {
const deps = chunkGroups[group];
if (
id.includes('node_modules') &&
// Рекурсивный поиск реферера,исследоватьдаотрицать жизньсередина chunkGroups Выписка из упаковки
isDepInclude(id, deps, [], getModuleInfo)
) {
return group;
}
}
}
Фактически, основная логика содержитсуществоватьisDepInclude
функция,Используется для рекурсивного поиска модуля реферера:
// Кэшировать объект
const cache = new Map();
function isDepInclude (id: string, depPaths: string[], importChain: string[], getModuleInfo): boolean | undefined {
const key = `${id}-${depPaths.join('|')}`;
// Циклические зависимости возникают и не учитываются
if (importChain.includes(id)) {
cache.set(key, false);
return false;
}
// Проверить кеш
if (cache.has(key)) {
return cache.get(key);
}
// Список зависимостей команды середина
if (depPaths.includes(id)) {
// Ссылочная цепочкасерединаиздокумент都记录приезжать缓存середина
importChain.forEach(item => cache.set(`${item}-${depPaths.join('|')}`, true));
return true;
}
const moduleInfo = getModuleInfo(id);
if (!moduleInfo || !moduleInfo.importers) {
cache.set(key, false);
return false;
}
// Основная логика, рекурсивный поиск рефереров верхнего уровня.
const isInclude = moduleInfo.importers.some(
importer => isDepInclude(importer, depPaths, importChain.concat(id), getModuleInfo)
);
// Настроить кеш
cache.set(key, isInclude);
return isInclude;
};
При реализации функции дляиз необходимо обратить внимание на две вещи:
getModuleInfo
чтобы получить модульиз ПодробностиmoduleInfo
,тогда пройдиmoduleInfo.importers
получить модульиз Цитируется,Этот процесс может выполняться рекурсивно для каждого реферера.,Для получения информации о ссылочной цепочке.ЗаканчиватьвышеmanualChunks
из После полной логики,сейчассуществоватьнас来осуществлятьnpm run build
упаковать:
Можетволосысейчасreact-vendor
Может正常拆分вне来,Посмотреть его содержимое
отсерединаты Может看внеreact
из一些косвенная зависимость已经成功Пакетприезжать Понятноreact-vendor
когдасередина,осуществлятьnpx view preview
Предварительный просмотрпродукт页面также能正常渲染Понятно
Это показывает, что проблема циклической зависимости нами решена.
Хотя приведенные выше решения уже могут помочь нам нормально распаковать продукт.,Но с точки зрения реализации,Все равно кажется немного громоздким,Так есть ли какое-нибудь готовое решение для распаковки?,能让нас直接用приезжатьпроектсередина Шерстяная ткань?
Ответ — да, я познакомлю вас с этим дальше. Vite Заказная распаковка изокончательное решение——vite-plugin-chunk-split
。
Сначала установите этот плагин:
pnpm i vite-plugin-chunk-split -D
Затем вы можете представить и использовать его в своем проекте:
// vite.config.ts
import { chunkSplitPlugin } from 'vite-plugin-chunk-split';
export default {
chunkSplitPlugin({
// Укажите стратегию распаковки
customSplitting: {
// 1. Поддержка заполнения имени пакета. `реагировать` и `react-dom` будет упакован в файл с именем `render-vendor`iz chunk внутри (включая их зависимости, такие как object-assign)
'react-vendor': ['react', 'react-dom'],
// 2. Поддержка заполнения регулярных выражений. источник середина components и utils Все файлы в папке будут упакованы как `comment-util`iz. chunk середина
'components-util': [/src\/components/, /src\/utils/]
}
})
}
По сравнению с зависимостями, работающими вручную, использование плагина можно завершить всего несколькими строками конфигурации, что очень удобно. Конечно, этот плагин также может поддерживать различные стратегии упаковки, в том числе unbundle Выкройка упаковки, вы можете перейти на Используйте документацию Узнайте больше об использовании.
Я участвую в третьем этапе специального тренировочного лагеря Tencent Technology Creation 2023 с эссе, получившими приз, и сформирую команду, которая разделит приз!