March 5, 2022
Angular is huge, don't make it worse.
Optimizing your applications is critical for gaining and keeping your users.
I recommend the following for keeping your angular apps in check:
Analyze
Code Split
Tree Shake
Configure
Before starting to address any issues with the size of your app bundles, analyze your production builds and pinpoint where to spend your effort. Aim for the lowest hanging fruits first, and then repeat the "optimization cycle" until your goals are met.
A note on deminishing returns: it is important to be pragmatic, as shedding size off of an existing application can be time consuming and difficult to really make progress after all the easy misses are resolved. That is OK. As long as you are avoiding major pitfalls, you are doing good.
Run your build with --sourceMap=true
to generate the appropriate data for analysis.
npm run build -- --configuration=pipeline --sourceMap=true
You can then analyze your bundles using source-map-explorer
Windows:
npx source-map-explorer .\dist\my-app\main.*.js
Mac:
npx source-map-explorer dist/my-app/main.*.js
Note: Webpack bundle analyzer is no longer the recommended way to analyze your angular bundles. Instead,
source-map-explorer
provides more accurate analysis. https://angular.io/guide/deployment#inspect-the-bundles
Angular allows applications to be code split by their modules. For example a URL such as /dashboard
may be contained within DashboardModule
and /admin
may be contained within AdminModule
. By configuring these components properly using angular's recommended mechanism, the compiler will create separate bundles, splitting your code apart.
const routes: Routes = [
{
path: "items",
loadChildren: () =>
import("./items/items.module").then((m) => m.ItemsModule),
},
];
Do not use shared modules. Always use a separate module per route section, and allow the compiler to generate more granular chunks.
A future angular release will make this easier and lighter-weight:
Proper imports can improve code splitting. Improper imports can bloat your bundles.
For instance, while it can be convenvient to expose exports as an object, compilers such as webpack cannot properly tree-shake unused values from that shared model.
[Bad]
import { ClientModel } from "./backend/models";
import { ClientModel } from "./backend/models/index.ts";
[Good]
import { ClientModel } from "./backend/models/client";
import { ClientModel } from "./backend/models/client.ts";
Do not export objects or classes if you can help it. Instead, export simple variables and standalone functions. This way, the compiler can properly detect used vs unused code.
Server and build configurations can make all the difference. These include:
HTTP/2 will make our applications faster, simpler, and more robust — a rare combination — by allowing us to undo many of the HTTP/1.1 workarounds previously done within our applications and address these concerns within the transport layer itself. Even better, it also opens up a number of entirely new opportunities to optimize our applications and improve performance!
https://developers.google.com/web/fundamentals/performance/http2
enableProdMode()
improves application performance by disabling development-only safety checks and debugging utilities, such as the expression-changed-after-checked detection. Building your application with the production configuration automatically enables Angular's runtime production mode.
Text-based resources should be served with compression to minimize total network bytes.
The server can return a Cache-Control
directive to specify how, and for how long, the browser and other intermediate caches should cache the individual response.
Adding <link rel=prefetch>
to a web page tells the browser to download entire pages, or some of the resources (like scripts or CSS files), that the user might need in the future. This can improve metrics like First Contentful Paint and Time to Interactive and can often make subsequent navigations appear to load instantly.
Further reading...