Drop 20K from your production Angular app

Replacing core-js with a modern reflect-metadata implementation will shave 20K off of your production Angular bundle.

Lets start with generating a new Angular app using the @angular/cli. The version we are using is 6.1.0.

$ ng --version

[snip]

Angular CLI: 6.1.0
Node: 10.7.0
OS: darwin x64
Angular:
...

Package                      Version
------------------------------------------------------
@angular-devkit/architect    0.7.0
@angular-devkit/core         0.7.0
@angular-devkit/schematics   0.7.0
@schematics/angular          0.7.0
@schematics/update           0.7.0
rxjs                         6.2.2
typescript                   2.7.2

Generate a new app ng-reflection.

$ ng new ng-reflection

[snip]

added 1170 packages from 1290 contributors and audited 24195 packages in 38.542s
found 13 vulnerabilities (9 low, 4 high)
  run `npm audit fix` to fix them, or `npm audit` for details
    Successfully initialized git.

From within the ng-reflection directory install the @abraham/reflection dependency. (If you're on an older version of npm you'll have to add --save).

$ npm install @abraham/reflection

+ @abraham/reflection@0.4.0
added 1 package from 1 contributor and audited 24196 packages in 9.428s
found 13 vulnerabilities (9 low, 4 high)
  run `npm audit fix` to fix them, or `npm audit` for details

Now let's get our benchmark production build size. (-- tells npm to pass the following --prod to the binary).

$ npm run build -- --prod

> ng-reflection@0.0.0 build /Users/abraham/dev/sandbox/ng-reflection
> ng build "--prod"

Date: 2018-07-26T15:16:08.577Z
Hash: 5195e37ef7e0f497ed61
Time: 23106ms
chunk {0} runtime.a66f828dca56eeb90e02.js (runtime) 1.05 kB [entry] [rendered]
chunk {1} styles.34c57ab7888ec1573f9c.css (styles) 0 bytes [initial] [rendered]
chunk {2} polyfills.2f4a59095805af02bd79.js (polyfills) 59.6 kB [initial] [rendered]
chunk {3} main.81ad5e275fee95455d5e.js (main) 172 kB [initial] [rendered]

Note the 59.6 kB size of the "polyfills" bundle.

Let's go into src/polyfills.ts file and replace the core-js dependency with @abraham/reflection.

-import 'core-js/es7/reflect'
+import '@abraham/reflection';

Run the production build again to see how this changes the build size.

$ npm run build -- --prod

> ng-reflection@0.0.0 build /Users/abraham/dev/sandbox/ng-reflection
> ng build "--prod"

Date: 2018-07-26T15:19:55.624Z
Hash: 73d84e088f5330b2c3c2
Time: 15921ms
chunk {0} runtime.a66f828dca56eeb90e02.js (runtime) 1.05 kB [entry] [rendered]
chunk {1} styles.34c57ab7888ec1573f9c.css (styles) 0 bytes [initial] [rendered]
chunk {2} polyfills.ab3b721569e8573cc20c.js (polyfills) 39.4 kB [initial] [rendered]
chunk {3} main.81ad5e275fee95455d5e.js (main) 172 kB [initial] [rendered]

Neat! The "polyfills" bundle is now only 39.4 kB, a savings of 20 kB.

What even is this?

reflect-metadata

Reflect-metadata was a JavaScript decorator proposal that TypeScript experimentally implemented. Note that a different decorators proposal is currently at stage 2 at tc39.

Using decorators you can write encapsulated functionality to be shared among a lot of of code. Sitepoint has a good intro to decorators. Basically they let you define functions like log, immutable, and time which you can apply to classes, functions, and properties.

@log()
@immutable()
class Example {
  @time('demo')
  doSomething() {
    //
  }
}

core-js

Angular makes use of TypeScript decorators through another metadata implementation, core-js. Core-js is awesome and provides a huge number of other JavaScript polyfills. If you're supporting older browsers and using other core-js polyfills it might make sense to stick with it.

One example is Component that lets us define a class as an Angular Component and configure it's behavior.

@Component({
  selector: 'app-bank-account',
  inputs: ['bankName', 'id: account-id'],
  template: `
    Bank Name: {{ bankName }}
    Account Id: {{ id }}
  `
})
export class BankAccountComponent {
  bankName: string;
  id: string;

  // this property is not bound, and won't be automatically updated by Angular
  normalizedBankName: string;
}

@abraham/reflection

@abraham/reflection is a modern, lightweight, ES module rewrite of reflect-metadata. For most use cases you should be able to replace reflect-metadata or core-js/es7/reflect without issue.

Give @abraham/reflection a try and see if it reduces your bundle size. Keep in mind that it's a new library and might have some kinks to work out. It is running in production on pwa.ng though. If you have any problems please file an issue.