Build a smaller production runtime
Problem
The production JavaScript bundle is larger than its measured budget even though the application uses only a small part of AngularTS. Importing the full runtime ships directives and services that this page never uses.
Before you start
Optimize the server response, images, fonts, and application code before maintaining a custom runtime. Use this recipe only after a production bundle report shows that unused AngularTS features are material.
createAngular accepts an
AngularComposition and
returns an AngularRuntime. Its
core contains compilation, scopes, controllers, expressions, interpolation, and
exception handling. You explicitly add every directive, service, filter, and
optional RuntimeModule used by
your application.
Create one runtime entry
Create src/runtime.ts. Import package subpaths directly so the bundler can
discard modules that are not reachable from this entry.
import { createAngular } from '@angular-wave/angular.ts/runtime';
import { ngControllerDirective } from '@angular-wave/angular.ts/directive/controller';
import { ngEventDirectives } from '@angular-wave/angular.ts/directive/events';
const angular = createAngular({
directives: {
ngController: ngControllerDirective,
ngClick: ngEventDirectives.ngClick,
},
});
class CounterController {
count = 0;
increase() {
this.count += 1;
}
}
angular
.createModule('counterApp', [])
.controller('CounterController', CounterController);
const root = document.querySelector('#counter-app');
if (!root) throw new Error('Missing counter application root');
angular.bootstrap(root, ['counterApp']);
export { angular };
Register the exact directive names used by the HTML. A custom runtime does not
discover ng-click from a template and add it automatically.
<section id="counter-app" ng-controller="CounterController as counter">
<strong>{{ counter.count }}</strong>
<button type="button" ng-click="counter.increase()">Increase</button>
</section>
Import optional modules only when the application uses them. For example, add
routerModule from @angular-wave/angular.ts/runtime/router for browser-owned
routing. Import sseModule, websocketModule, or webTransportModule from
@angular-wave/angular.ts/runtime/realtime when only one live transport is
needed. Use machineModule or workflowModule from
@angular-wave/angular.ts/runtime/orchestration instead of their aggregate when
only one orchestration feature is needed. Do not import the package root from
this entry; it restores the general runtime and its auto-bootstrap behavior.
Build it with Vite
Point the production build at the runtime entry. Keep property names intact: HTML expressions and dependency-injection tokens refer to them by name.
import { defineConfig } from 'vite';
export default defineConfig({
build: {
target: 'es2022',
minify: 'esbuild',
sourcemap: 'hidden',
rollupOptions: {
input: 'src/runtime.ts',
output: {
entryFileNames: 'assets/runtime-[hash].js',
},
},
},
});
Run npm exec vite build. Serve the hashed file with a long-lived immutable
cache policy and deploy the generated HTML or manifest that references its new
hash. Upload the hidden source map to error monitoring, but do not serve it as a
public asset unless that is intentional.
Vite’s default minifier does not mangle object properties. If a different
minifier is introduced, do not property-mangle controller members, model
members, directive attributes, injection tokens, or names read by server HTML.
Use static $inject or array injection for every dependency-bearing function.
Measure the artifact
Compare the custom build with the existing production build under the same
target, minifier, and compression settings. Record raw, gzip, and Brotli sizes;
the compressed transfer size is the useful network budget. For example, run
gzip -9 -c dist/assets/runtime-*.js | wc -c after the build.
Set a CI budget only after recording a stable baseline. Fail on unexpected growth, but allow an intentional increase when it removes more application code or improves runtime behavior. Bundle size alone is not a performance result; also measure startup and the interaction that motivated this work.
Failure path
Server-rendered and remotely loaded templates are not visible to the bundler. Inventory their directives explicitly and compile representative responses in tests. If templates or plugins can introduce unknown AngularTS features, ship the standard runtime for that application instead of discovering missing directives in production.
Do not create several nearly identical runtime entries. Share one composition per application architecture and add a feature deliberately when a tested template needs it. A few saved bytes are not worth inconsistent behavior across pages.
Apply it now
Build the current application and record its compressed runtime size. List the directives, filters, services, and optional modules used by production HTML. Create one explicit runtime entry, remove one unused feature group, and compare both size and startup measurements before removing anything else.
Verify
Compile every shipped server fragment and route template with the production runtime. Exercise bootstrap, forms, errors, lazy content, and teardown from the minified artifact. Confirm omitted services fail in a test, source maps resolve in monitoring, the server sends compressed hashed assets, and the CI size budget detects an intentional temporary increase.
Feedback
Was this page helpful?
Glad to hear it! Please tell us how we can improve.
Sorry to hear that. Please tell us how we can improve.