56. Оптимизация сборки
59. Оптимизация сборки 📦
Заголовок раздела «59. Оптимизация сборки 📦»Привет! Яша здесь. Размер бандла напрямую влияет на время загрузки — каждые 100KB JavaScript добавляют ~1 секунду на мобильных устройствах. Сегодня разберём как сделать Angular-приложение максимально компактным 🚀
Что происходит при ng build —prod
Заголовок раздела «Что происходит при ng build —prod»ng build --configuration=productionAngular выполняет следующие оптимизации:
- AOT компиляция — шаблоны компилируются в TypeScript
- Tree shaking — удаление неиспользуемого кода
- Minification — сжатие JavaScript и CSS
- Dead code elimination — удаление мёртвого кода
- Scope hoisting — объединение модулей
- Differential loading — отдельные бандлы для ES5/ES2015+
AOT vs JIT компиляция
Заголовок раздела «AOT vs JIT компиляция»// ❌ JIT (Just-in-Time) — компиляция в браузере// angular.json: "aot": false// Браузер получает: Angular компилятор + шаблоны (строки) + код// Размер: большой, компиляция при каждом запуске
// ✅ AOT (Ahead-of-Time) — компиляция при сборке// angular.json: "aot": true (по умолчанию с Angular 9)// Браузер получает: скомпилированный код, без компилятора// Преимущества:// - Меньший бандл (нет компилятора ~300KB)// - Быстрый старт// - Ошибки шаблонов найдены на этапе сборки// - Безопаснее (нет eval())
@Component({ // AOT проверяет это при компиляции: template: ` <h1>{{ titl }}</h1> // ← Ошибка TypeScript: Property 'titl' does not exist `})export class AppComponent { title = 'Hello';}Бюджеты сборки
Заголовок раздела «Бюджеты сборки»// angular.json — строгий контроль размера{ "configurations": { "production": { "budgets": [ { "type": "initial", "maximumWarning": "500kb", "maximumError": "1mb" }, { "type": "anyComponentStyle", "maximumWarning": "2kb", "maximumError": "4kb" }, { "type": "allScript", "maximumWarning": "1mb", "maximumError": "2mb" }, { "type": "any", "maximumWarning": "500kb", "maximumError": "1mb" } ] } }}Анализ бандла: webpack-bundle-analyzer
Заголовок раздела «Анализ бандла: webpack-bundle-analyzer»ng build --stats-json --configuration=production
# 2. Анализnpx webpack-bundle-analyzer dist/my-app/browser/stats.json
# Скрипт в package.json:# "analyze": "ng build --stats-json && npx webpack-bundle-analyzer dist/my-app/browser/stats.json"Что искать в анализаторе:
- 🔴
moment.js— 65KB gzip → заменить наdate-fnsилиTemporal - 🔴 Полный
lodash→ импортировать только нужные функции - 🔴 Дублирующиеся библиотеки (несколько версий)
- 🟡 Большие компоненты, которые должны быть lazy loaded
Tree Shaking: правильные импорты
Заголовок раздела «Tree Shaking: правильные импорты»// ❌ Импорт всего lodash — 71KB gzipimport _ from 'lodash';const arr = _.uniq([1, 2, 2, 3]);
// ✅ Импорт только нужной функции — 2KBimport uniq from 'lodash/uniq';const arr = uniq([1, 2, 2, 3]);
// ✅ Ещё лучше — нативный JavaScriptconst arr = [...new Set([1, 2, 2, 3])];
// ❌ moment.js — огромный (не tree-shakeable)import moment from 'moment';const date = moment().format('YYYY-MM-DD');
// ✅ date-fns — полностью tree-shakeableimport { format } from 'date-fns';const date = format(new Date(), 'yyyy-MM-dd');esbuild: новый строитель Angular (v17+)
Заголовок раздела «esbuild: новый строитель Angular (v17+)»// angular.json — переход на esbuild{ "projects": { "my-app": { "architect": { "build": { "builder": "@angular-devkit/build-angular:application", "options": { "browser": "src/main.ts" } }, "serve": { "builder": "@angular-devkit/build-angular:dev-server" } } } }}# Результаты с esbuild (Angular 17+):# ng build: 15 секунд → 4 секунды (3-4x быстрее!)# ng serve hot reload: 2 секунды → 0.3 секундыDifferential Loading (устаревает в Angular 17)
Заголовок раздела «Differential Loading (устаревает в Angular 17)»<!-- index.html — Angular генерирует автоматически --><!-- Современные браузеры (ES2017): type="module" --><script type="module" src="main-es2017.js"></script>
<!-- Старые браузеры (ES5): nomodule --><script nomodule src="main-es5.js"></script>// tsconfig.json — настройка target{ "compilerOptions": { "target": "ES2022", // ← Angular 17+ рекомендует ES2022 "useDefineForClassFields": false }}Lazy Loading: влияние на бандл
Заголовок раздела «Lazy Loading: влияние на бандл»// ❌ Всё в одном бандле — initial chunk 2MB@NgModule({ imports: [ AdminModule, // 500KB ReportsModule, // 400KB SettingsModule, // 200KB ]})export class AppModule {}
// ✅ Lazy loading — initial chunk 200KBconst routes: Routes = [ { path: 'admin', loadChildren: () => import('./admin/admin.module').then(m => m.AdminModule), // → отдельный chunk: admin.chunk.js (500KB) загружается только при /admin }, { path: 'reports', loadChildren: () => import('./reports/reports.module').then(m => m.ReportsModule), // → отдельный chunk: reports.chunk.js },];
// Результат:// Было: initial 2MB (все пользователи ждут 2MB)// Стало: initial 200KB + chunks по требованию@defer: ленивая загрузка компонентов
Заголовок раздела «@defer: ленивая загрузка компонентов»<!-- Angular 17+ — встроенная ленивая загрузка -->
<!-- Загрузить когда элемент появился во viewport -->@defer (on viewport) { <app-heavy-chart [data]="chartData" />} @placeholder { <div class="chart-skeleton" style="height: 300px;"> Загружаю график... </div>} @loading (minimum 500ms) { <app-spinner />} @error { <p>Не удалось загрузить компонент</p>}
<!-- Загрузить когда браузер idle -->@defer (on idle) { <app-recommendations />}
<!-- Загрузить по условию -->@defer (when isLoggedIn) { <app-user-dashboard />}
<!-- Предзагрузить (hover) -->@defer (on hover; prefetch on idle) { <app-tooltip />}Source Maps в production
Заголовок раздела «Source Maps в production»{ "configurations": { "production": { "sourceMap": false, // ← не публикуй source maps в production!
// Или скрытые source maps (для Sentry без публичного доступа) "sourceMap": { "scripts": true, "styles": false, "hidden": true // ← файлы создаются, но не ссылаются из бандла } } }}Оптимизация зависимостей
Заголовок раздела «Оптимизация зависимостей»// ngx-translate — только нужные части// ❌import { TranslateModule } from '@ngx-translate/core'; // весь модуль
// ✅ Standalone import — tree-shaking работает@Component({ imports: [TranslatePipe], // только pipe})
// RxJS — правильные импорты// ❌ Старый стильimport { Observable } from 'rxjs/Observable';import { map } from 'rxjs/operators/map';
// ✅ Современный — tree-shakeableimport { Observable, map } from 'rxjs';
// Angular Material — только нужные модули// ❌import { MaterialModule } from './material.module'; // всё сразу
// ✅import { MatButtonModule } from '@angular/material/button';import { MatInputModule } from '@angular/material/input';Профилирование производительности сборки
Заголовок раздела «Профилирование производительности сборки»# Время сборки с деталямиng build --verbose
# Профилирование webpackng build --stats-json# Открой stats.json на https://webpack.github.io/analyse/
# Проверить размеры чанковls -lh dist/my-app/browser/*.js
# ng build с таймингомtime ng build --configuration=productionPlayground 🎮
Заголовок раздела «Playground 🎮»Визуализатор размера бандла с анализом оптимизаций: