Аналіз прычын спадзеючага часу запуску Webpack Monorepo пры прабоўце выкарыстаць новы інструмент для збірання коду
Як аналіз процеса складання мікро-фронтэнда на Webpack працэю, яка трывала 20 хвілін, паказаў на викорыстанне Terser, Babel, процесы інсталяцій і кэшавання ў Docker, а таксама якія змены дапамоглі скорачыць час складання да апошніх двух хвілін.
Калі час запуску фронтэнду становіцца дужа дзьвялым, перша рэакція — апрацаваць віну на інструмент для зборкі коду і пачаць планаваць перайшчы на іншы. Аднак такая рэакція часта ўскладнена. У цым аналізе рассказваецца пра платформу з мікро-фронтэндам, дзе час запуску за дапамою Webpack перавышаў 20 хвілін; аналіз показаў, што сам інструмент не быў прычыной проблемы, і што серыя ціляваных змян дазволіла скорачыць час запуску да праўяко 2 хвілін, не заменяючы Webpack. Вы пазнаеце, як вывучыць справжню прычыну затрымкі, якія інструменты на базе Rust заменілі певныя етапы процесу, і чаму установка залежнасцей, токены реестру і шары Docker мелі такое ж значэнне, як і будь-які іншы інструмент для зборкі.
Калі час запуску становіцца проблемай платформы
Указаная платформа ў месяц прымала аддаўна 600 запросаў пра змяны ад падаць дзесяці інжынераў, што прыводзіла да аддаўна 5 000 запускаў процедуры CI. Працаваючы 20 хвілін на кожны запуск, це станавіла аддаўна 1 600 гадзін часу CI ў месяц. Пры такой колькасі запускаў повалка процедуры стае не проста дратуючай проблемай, а бутылочным горлам для всей організацыі: адпаведныя звёсткі прыходзяць пазнела, чергі запускаў CI растуць, а трыбутныя практычныя правкі чакаюць у черзі.
Структура монорепа толькі пагаршала ситуацыю:
20 production apps: React and Next.js applications
7 shared libraries
1 centralized E2E test suite
~3,950 TSX/JSX files and 6,600+ TypeScript source files
~350 reusable component modules
144 npm dependencies (74 production, 70 development)
Workspace: single hoisted monorepo
Team: 10+ engineers, 600+ PRs/month
CI: 5,000+ build runs/month
Двадцать прыкладнікаў, якія дзелюць семь бібліятэкаў у аднай працоўнай суперактуальнасі, значыць, кожная змяна у інструментах паўтараецца для всіх адразу.
Чаму спачатку была дыягностика, а потым міграцыя
Замена інструмента для складання кода ў 20 прыкладных дапрацоўках, 7 спільных бібліятэках і 144 залежнасцях — гэта проект з высокім рызыкам. Блакберг у адной з спільных бібліятэкаў можа пашкодзіць кожную дапрацоўку, а перапраналізаванне всіх рэзультатаў складання можа займаць тыяжкія тыдні без гарантій успеху. Таму команда спачатку аналізавала весь процес. У ходзе гэтага аналізу былі выявлены значныя витраты, не relacionаваныя з самым інструментам для складання кода, у такіх аспектах, як перакантрольванне типаў, організаванне тэстаў і рашэнне залежнасцяў, пры чым ні новы інструмент для складання кода, ні якія-іншы заходы не маглі гэта паспрабаваць пакращыць.
Шырэйшы урок знайомы з любай роботы па падвышэнню карыстнасці: оптымізацыя перад мераваннем прыводзіць да таго, што кожная змяна стае простаю спадчынай. Процес складання з Webpack праходзіць через калькі разлічных фаз, перш чым ствараецца готовы продукт:
- кампіляванне модуляў
- экспанацыя ладераў
- стварэнне чакаў
- оптымізацыя ресурсаў
- мініфікацыя
- выдача ресурсаў
Без часовых показань для кожнага этапу немагчыма з’ясаваць, які з іх патрабуе ўвагі, таму нічога ў налаштаваннях ладера чы плагіна не было зменяна, пакуль не былі вказаны ці фігуры.
Пошук актуальнага месца з ProgressPlugin
Webpack поставляецца з ProgressPlugin, які не патрабуе додатковага інсталлявання. У файле налаштавань неабходна ўстановка Webpack:
const webpack = require('webpack');
і трэба дадаць плагін у масэвы plugins:
plugins: [
new webpack.ProgressPlugin()
]
Калі быў увёрнуты выхід дадзеных пра профілюванне, адразу ж выдзелілася адна лінія:
[webpack] 92% sealing > asset processing
TerserPlugin took 825.31s
Адзіны плагін займаў больш чым 13 хвілін. Порашчы з іншымі плагінамі на стадії оптымацыі, гэта неравенства ўсё бол выражаныя:
copy-webpack-plugin 30ms
WriteIndexHtmlPlugin 22ms
RealContentHashPlugin 58ms
CompressionPlugin 70ms
LicenseWebpackPlugin 1.15s
TerserPlugin 825.31s
Кожны другі плагін завершаў свою роботу за каліксекунды, а плагін з ліцензіям — прыбліжна за секунду. Сам збірник коду не быў повольным; проблемай была мініфікацыя JavaScript за дапамогою Terser. Гэтыя спостерэння змусілі зменіць падход да роботы.
Время виконання на рэвеніі ладера з плагіном Speed Measure
ProgressPlugin паказвае, яка стадія є повольной, але не вказывае, яка ланцюг ладера пад час компіляцыі ёсць прычыной. Для гэтага команда дадала speed-measure-webpack-plugin, які обгортае экспортаваную настройку:
const SpeedMeasurePlugin = require('speed-measure-webpack-plugin');
const smp = new SpeedMeasurePlugin();
module.exports = smp.wrap(config);
SMP адаптава інфармацыю пра тое, сколькі часу кожны лоадер выкарыстоўвае на обробку модуляў, што дазволіла парабяліць процес роботы прыемнікай да і пасля введэння SWC. Ён таксама даваў чыстую рэальную інфармацыю. Ланцоўка CSS, якая складаецца з mini-css-extract-plugin, css-loader і postcss-loader, не стала быстрэйшай; час ў яе вырас з 8,66 да 9,36 секунд. CSS і модулі, якія не обрабляюцца жадным лоадерам, займалі майже 16 з застаўшыхся 20,78 секунд загальнага часу выходу. Узамест канфліктавання па праблеме выбору інструментоў, команда мела конкрэтныя цыфры, якія паказвалі, дзе трэба здзейсніць наступныя змены.
Є адна застерэжніца, яку варта знати: SMP абгортвае плагіны і лоадеры, каб змерыць час их роботы, і вядома, што ён можа стварыць канфлікты з дзеянымі новейшымі плагінамі, таму яго трэба викорыстоўваць толькі для діагностики, а не залишаць у конфігурацыі для працы.
Цялі, якія абмежвалі кожнае рашэнне
Перад тым, як чаго-небудзь зменіць, каманда запісала цялевыя пункты ў троха груп.
Эфективнасць стварэння
- Зменшыць час чакання пад час стварэння праекту.
- Прывычнае стварэння праекта.
- Менш часу на транспіляцыю і мініфікацыю.
- Кращая аддача ад наяўных ядроў CPU.
Эфектывасць CI
- Частэйшая пераўтамовка шароў кешу Docker.
- Корачэйшая установка залежнасцей.
- Больша надзеямасць у процесах обробкі.
Стабільнасць платформы
- Зберагчы стабільнасць інструментаў у масштабе падпрыемства.
- Ухіліцца ад рызыкаваных міграцый.
- Зберагчы сумеснасць з існуючай экасистемай.
- Спраўдзіць первісную швальнасць проты дагоўнае падтрымкі.
Чаму Vite і Rspack былі адкладзены
Vite быў серйозна адначытан. Це высокаяя якосцю програма, якая часта ўтварае правы выбор для новых або простэйшых проектаў. Ключовая разліка закладвалася у тым, што яго працэздатнасць адначылі з рэальным практычным варыянтом, а не з маленькімі дэмантамі, якія ўжо є у багатых вядарах па міграцыі. Вывад быў такім, што галоўныя витраты, звязаныя з скорачэнням розмеру коду, не залежаць ад інструменту для збірання коду: пераход на Vite займе апошней чатыры тыдзены, і каманда будзе сталкавацца з падобными прыгнічэннямі, а таксама з неабходнасцю рашыць проблемы, звязаныя з новым форматам налашчэння і несумеснасцю плагінам. У журнале рашэнняў Vite быў зазначаны як варыянт, які трэба будзе пераглядзець, якщо складанне коду калі-небудзь стане галоўным прыгнічэнням.
Справедліва нюанс: Vite за замовчэнням мініфікуе JavaScript за дапомогою esbuild, а не Terser, таму міграцыя на практыцы таксама зменіла б інструмент мініфікацыі. Це падкрепляе справжню ідею, а не паслабляе яе. Ключовым факторам была выбор мініфікатора, і Webpack можа выкарыстоўваць той жа шырокі мініфікатор без неабяжной міграцыі.
Rspack — інструмент для зборкі коду на базе Rust з конфігурацыяй, сумесной з Webpack — таксама быў рассмотраны і выглядаў перспективным. У той час няштоўнія інцыдэты з безпекай і ще розвіваючыся екосістэма змусілі команду быць абережнымі, таму ён застаўся у списку на пазнейшую перэцэнку. Перад тым, як робіць власныя выводы, пераканайцеся ў його нынешнім стане.
Перабудова самых повольных этапаў
Калі стратэгія была визначана, робота перайшла да поступовага замены самых повольных компанентоў.
SWC замест Babel для транспіляцыі
Транспіляцыя JavaScript і TypeScript была аднай з найбольшых застаўшыхся витатак. Babel быў заменены на SWC — кампайляр на базе Rust, прызначаны для шыракага спектру трансформацый JS і TS, які включаецца за дапамой swc-loader, а яму прадшычы thread-loader:
{
test: /\.(jsx?|tsx?)$/,
use: [
'thread-loader',
{
loader: 'swc-loader'
}
]
}
Публічныя тэсты паказваюць, што SWC транспіляе значна быстрэй за Babel, і ў гэтым ланцугу пераход на SWC прынёс болей шырокую транспіляцыю, можлівасць паралельной роботы за дапамой thread-loader, меньшую блокаванне галоўнага процесу і корачэ выконанне тестав CI. swc-loader у гэтым разе выкарыстоўвае файл .swcrc або вбудованыя настройкі для парсера і JSX; без іх синтаксіс TypeScript і JSX не будзе парсавацца.
esbuild заместо Terser для мініфікацыі
У аналізе вялікія прычыны вядомы былі ўжо раней, таму мініфікацыя JavaScript перайшла на esbuild за дапамой плагіна esbuild-loader:
new EsbuildPlugin({
target: 'es2015',
minify: true
})
Рашынак быў адбіты на аснове проекта minification-benchmarks, які парабялеў esbuild, terser, swc, uglify-js і іншыя за розмерам выкаананага коду пасля мініфікавання, розмерам у формате gzip і часам выкаанення. У гэтых дадзенах:
- Расмер выкаананага коду esbuild пасля мініфікавання і запаковвання ў gzip быў конкурэнтным: на адзін календарны момент ён быў на адзін календарны момент большы за найкращы рэзультат, але залягаў у межах прыблізна 5–8 процэнта ад яго;
- esbuild выкаанаў задачу за адзін календарны момент 295 мс, тады як terser патрэбаваў адзін календарны момент 6,7 секунды на той самы вхідны дадзенні; гэта расхожанне паўтарылася ў монорепо;
@swc/coreіoxc-minifyстваралі меншы розмер выкаананага коду, але ўслядкі іх медленейшага выкаанення і сложнасці інтеграцыі рашынак быў здабыты на корысть esbuild.
Настройка target: 'es2015' паведамляе esbuild, якую сынтаксіс ён можа выдаваць; пераканайцеся, што яна адпавядае рэальнай падтрымцы браузера, адколькі новейшы цэльны параметр дазволяе ствараць болей коракты код.
LightningCSS для мініфікацыі CSS
Для мініфікаціі CSS выкарыстоўваецца LightningCSS ад команды Parcel, які ўключаецца ў css-minimizer-webpack-plugin як функцыя мініфікацыі:
new CssMinimizerPlugin({
minify: CssMinimizerPlugin.lightningCssMinify,
})
Бенчмаркі LightningCSS параграфуе яго з інструментамі cssnano та esbuild для мініфікаціі CSS. У кожным пераглядзеным сцэнарыі яго паказуюць як найшырэйшы, з завжды меньшым розмерам выходных файлаў, а таксама з значным падвышэнням карыстоўнасці для вялікіх стайл-шітов, такіх як проекты Tailwind. Для CI-пайплайнаў, дзе оптымізацыя CSS безпасебна вплывае на аб’ём задач, кращая ступень стыснення разам з меншым часам адпрацоўкі зробілі яго явным выборам. Унаследке змены параметраў мініфікаціі, швыдкасць адпрацоўкі JavaScript зросла прыблізна у 10 разоў, а CSS — у 6 разоў.
Іспытанне всіх ядоў CPU
Агенты CI зазвычай маюць калькі ядоў, але багатыя пайплайны залишаюць большасць з іх незанятымі. Адключэнне thread-loader перед дорогімі лоадерамі дазволяе распаўсюдзіць цяперашнюю роботу між разнымі працоўнікамі:
use: ['thread-loader', 'swc-loader']
Это скасавала проблемы затрохкання падчас стварэння проекта, павысіла эфектываўнае выкарыстоўванне рэсурсаў і падняла шырокаспектнасць процеса CI. Памятайце, што кожны працоўнік мае додатковыя витраты на запуск і перадачу паведамленняў; для вяліка праекта, дзе вжо існуе шырокаспектны ладчык у дзеўялік SWC, працоўнікі могу стварыць больш витак, чым заўсёды зарабіць, таму неабходна ацэніць рэзультаты з іх вжыванням і без яго.
Шырэйшыя та болей надзеяныя процесы інсталляціі з pnpm
Компіляцыя не была ўсім виткам; інсталляцыя залежнасцей таксама спрацоўвала хвілінкі часу CI. Команда паўпоручыла npm, yarn, pnpm і bun, выкарыстоўваючы публічныя тэсты, якіе ацэнювалі шырокаспектнасць інсталляціі, выкарыстоўванне дыска і механізмы рашэння проблем. Bun показаў хорашыя результаты у сінтэтычных тэстах, але pnpm выграў за рахунак зрэласці екосыстэмы, сумеснасці з Node.js і дасвядчэнняў у вялікіх прыменных системах.
Калі npm копіюе пакеты ў кожны node_modules, pnpm выкорыстоўвае глобальны хранільнік з адресаванням кантэнту: кожная версія пакета завантажваецца толькі раз і ствараецца жорсткая лінкаванне ў проекты. Це дае кальколька прыямоў:
- інсталяцыя ўскораная, таму што пакеты запрашваюцца толькі раз і перадаюцца знову;
- зменшываецца выкарыстоўванне диска, таму што працовыя прасторы больш не дублююць тыя ж пакеты;
- строгая працэс разв’язкі блакуе фантомныя залежнасці, пакеты, якія ваш код імпортуе без ўказвання іх, што робіць процесы будавання болей дэтерміністычнымі;
- паспрабоўкі кэшавання шара Docker падбягаюць, таму што хранільнік можна перадаць знову межы процесамі будавання.
У Docker кэш BuildKit дазволяе зберагаць хранільнік pnpm межы процесамі будавання, тады як параметр --frozen-lockfile спрычыняе аббіёкцію інсталяцыі, якщо файл з локамі ўстарэў:
RUN --mount=type=cache,target=/root/.local/share/pnpm/store \
pnpm install --frozen-lockfile
Дзеянне аутэнтыфікаціі, якое спрычыніла проблему з кэшаванням
Адзін з найэфектывейшых спосабаў адлагоджэння не мяў нічынага з інструментамі фронтэнду. Пакеты прыходзілі з AWS CodeArtifact, чыя токены автарызацыі выгораюць пасля 12 гадзін. Павтаральна запрашэнне токенав у рамках пайплайна спрычыняла анвалідацыю кэшу, павтаральную роботу з автарызацыяй і паспаленне шароў Docker, таму што змяненае значэнне, якое падае ў шар, зміняе ключ кэшу таго шара.
Рашэнням было запрашваць токен аднае раз пры кожнай задачы Jenkins і вяртаць яго для кожнага крока:
export CODEARTIFACT_AUTH_TOKEN=$(aws codeartifact get-authorization-token \
--domain <domain> \
--domain-owner <owner> \
--query authorizationToken \
--output text)
Гэта прынесла лепшую перадаўнае шароў, менш чыстых неабавязковых інсталяцый і стабільнейшыя пайплайны. Якщо вы перадаеце такі токен у процес будовы Docker, лепш выкарыстоўваць секрэтны маунт замест аргумента будовы, каб ён не патрапіў у історыю абраза і не анвалідаваў кэш.
Структураванне Dockerfiles для эфектывае работы кэшу
На завершанне Dockerfiles былі пераканструльваны ў шары, распараджаны ад тых, якія змінююцца рэдка, да тых, якія змінююцца часта:
- Базавы час адканалення;
- Інсталяцыя залежнасцей;
- Копіюванне выхаднага коду;
- Складанне пакета за дапамою Webpack.
У поўнай з’еднанасці з параметрамі --mount=type=cache для хранення пакетаў і --mount=type=secret для аутентыкацыйных даных, такія парады значэнняяў абяцвалі, што праз змены ў кодзе будуць пераскладаны толькі два апошнія ўзоракі, чым значна падвышаецца вярнэ іспользоўванне кеша.
Галоўныя выводы
- Скорасць складання трэба спрыяваць як проблему системы. У даным случае найбольшыя практычныя рэзультаты былі атрыманы за дапамою інструментаў для скарычвання коду, процэсаў інсталляцыі, аутентыкацыйных даных і структурызаціі ўзоракоў у Docker, а не за дапамою самага інструмента для збірання коду.
- Спачатку аналізуйце ситуацыю.
ProgressPluginзнайшоў, што крок скарычвання коду за дапамою Terser займае 13 хвілін; спекулятивная міграцыя зайняла бы недзеўкі. - Інструменты на базе Rust, такія як SWC, esbuild і LightningCSS, даўаюць сумарны эфект: кожны з іх усуне частку затрымкі.