Ускорение работы приложения Angular с помощью esbuild
Компания Cardiologs создает приложения, которые позволяют медицинским работникам экономить время на анализ электрокардиограммы (ЭКГ) благодаря искусственному интеллекту.
Мы разрабатываем SaaS-предложения, построенные на следующем технологическом стеке:
Недавно мы создали новый продукт с использованием React и Vite, и мы были потрясены тем, насколько Vite улучшает работу разработчиков, ускоряя время сборки.
Наше приложение на Angular довольно большое: 259 компонентов, 47 модулей, 800 тысяч строк кода. По сравнению с нашим стеком React оно кажется медленным на старте, и это как раз то, что мы давно хотели улучшить.
Начиная с v17 команда Angular добавила поддержку esbuild: https://angular.io/guide/esbuild.
Давайте попробуем перейти с Webpack на esbuild и посмотрим, что это изменит для нас в плане производительности.
Переведено с помощью DeepL.com (бесплатная версия)
Что нужно было изменить
Мы используем Nx, и включение esbuild начинается с изменения исполнителя сборки в файле project.json
:
"build": {
- "executor": "@angular-builders/custom-webpack:browser",
+ "executor": "@angular-devkit/build-angular:browser-esbuild",
}
Это нарушит некоторые моменты, вот что нам пришлось изменить:
Импорт ESM по умолчанию
Мы импортируем несколько модулей CommonJS, которые не поддерживают синтаксис модулей ES6, например momentjs
:
▲ [WARNING] Calling "moment" will crash at run-time because it's an import namespace object, not a function [call-import-namespace]
Consider changing "moment" to a default import instead:
libs/help/src/lib/help/manual/manual.component.ts:5:7:
5 │ import * as moment from 'moment';
│ ~~~~~~~~~~~
╵ moment
Эту ошибку можно исправить, добавив этот флаг в tsconfig.json
:
{
"compilerOptions": {
+ "esModuleInterop": true,
}
}
А затем измените все стандартные импорты на что-то вроде этого:
- import * as moment from 'moment';
+ import moment from 'moment';
Мы также постепенно отказываемся от moment
в пользу date-fns
в нашей кодовой базе, чтобы решить проблемы с размером пакета в долгосрочной перспективе.
См. https://angular.io/guide/esbuild#esm-default-imports-vs-namespace-imports
Переменные окружения Node с Webpack
Наша сборка Webpack использовала конструктор @angular-builders/custom-webpack
, чтобы получить переменные окружения во время сборки и передать их браузеру:
function getClientEnvironment() {
// Grab NX_* environment variables and prepare them to be injected
// into the application via DefinePlugin in webpack configuration.
return {
'process.env': Object.keys(process.env)
.reduce((env, key) => {
if (/^NX_/i.test(key)) {
env[key] = JSON.stringify(process.env[key]);
}
return env;
}, {});
};
}
Затем во время сборки мы передадим переменные: NX_TAG=pr-123 npx nx build
. Приведенная выше пользовательская конфигурация webpack позволит переменным окружения быть доступными в браузере в виде process.env
, как в приложении node.
В esbuild у нас больше нет прямого способа получить эти переменные окружения. В результате, вместо того чтобы пытаться внедрить process.env
в браузер, мы просто пишем JSON-файл во время сборки в процессе работы с Github Actions:
- name: Pass the build tag into the app without using process.env
run: printf '{"tag":"%s"}\n' "${{ inputs.tag }}" > env.json
+ import env from './env.json';
export default {
APP_ENV: getEnv(),
- APP_VERSION: process.env.NX_TAG ?? 'dev',
+ APP_VERSION: env.tag,
};
Относительные импорты SCSS
Многие SCSS @import
перестали работать:
✘ [ERROR] Can't find stylesheet to import.
╷
1 │ @import 'libs/heartbeat/src/lib/assets/scss/hb-foundations';
│ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
╵
Это относительные импорты из одной библиотеки Nx
в другую, папка libs
находится в корне репозитория.
Одно из решений - использовать корректный относительный путь, но это требует обновления многих файлов и делает код более громоздким.
+ @import '../../../../heartbeat/src/lib/assets/scss/hb-foundations';
- @import 'libs/heartbeat/src/lib/assets/scss/hb-foundations';
Другое решение - добавить корень хранилища в качестве пути, который может быть использован для импорта SCSS, используя project.json
:
"build": {
"executor": "@angular-devkit/build-angular:browser-esbuild",
"options": {
"stylePreprocessorOptions": {
"includePaths": [
"node_modules",
+ ""
]
}
Эталон производительности
Теперь, когда всё готово, давайте проверим, как это повлияло на производительность сборки:
Наша сборка занимает довольно много времени на Github actions, сэкономить 47 секунд здесь приятно, но это мало что изменит. Большую часть времени в нашем CI занимают тесты E2E.
Здесь мы начинаем видеть действительно хорошие результаты. Ждать 1 минуту перед компьютером, пока завершится nx build
, очень мучительно.
Большинство людей на самом деле используют не build
, а serve
, так что давайте посмотрим, как улучшится dev-сервер.
Сервер разработчиков здесь использует Vite за кулисами, хотя он и не раскрывает свою конфигурацию или внутреннее устройство.
Прирост здесь действительно заметен. Экономия более 20 секунд на времени запуска nx serve
станет приятным улучшением качества жизни для всех разработчиков проекта.
В процессе разработки мы собираемся один раз запустить dev-сервер, а затем десятки раз редактировать исходный код, заставляя dev-сервер перестраивать наш код.
Здесь ничего не меняется с помощью Vite:
- Мы тратим около 1 секунды на перестройку кода при изменении фрагмента кода.
- Затем сервер разработчиков отправляет браузеру полную перезагрузку страницы. Перезагрузка может занять некоторое время в зависимости от приложения. Большая часть времени уходит на это.
Заключение
Обновить наше приложение для использования нового билдера было довольно просто, и мы быстро получили неплохой выигрыш во времени сборки и начальном запуске nx serve
.
Если у вас есть приложение Angular, но вы еще не перешли на esbuild, вы упускаете возможность. Здорово видеть, что в Angular появляются такие улучшения!
Однако нам все еще чего-то не хватает, если сравнивать с тем, что предлагает Vite: на React+Vite dev-сервере изменение кода не вызывает полной перезагрузки страницы и мгновенно отражается в браузере. В Angular по-прежнему нужно перезагружать всю страницу.
Надеемся, что в будущем мы увидим в Angular+Vite новые дополнения, такие как Hot Module Replacement (HMR)!
Благодарю за прочтение! Счастливого кодинга!
Чтобы перейти к источнику, нажмите здесь. Требуется VPN.