Use MomentJS/Moment Timezone in Angular efficiently

It seems like every web project I do involves interacting with time, which inevitably evolves into dealing with multiple timezones.  I always go down the road of trying to use the native Javascript Date object, but it never fails that it is not robust enough – even with the new’ish Intl.DateTimeFormat

MomentJS to the rescue

MomentJS is great, Moment Timezone is even better. It does everything you should ever need when working with dates and/or time zones.

Moment and Moment timezone can be bloated

Moment supports TONS of locales.  Awesome, yes but also large as it includes all them by default.  Moment timezone supports TONS of timezones, and all their changes over the years.  Awesome, yes but also large as it includes this large matrix of timezones and their idiosyncrasies over time (and in the future).

Here is an example of an Angular production build of a simple web-application that uses the default setup of Moment and Moment timezone.  Lets build with:

ng build --stats-json --prod --build-optimizer --delete-output-path --aot --output-path=dist
ec2944dd8b20ec099bf3.js (runtime) 1.41 kB [entry] [rendered]
chunk {1} main.a669168c0ae7b41b368c.js (main) 2.07 MB [initial] [rendered]
chunk {2} polyfills.c6871e56cb80756a5498.js (polyfills) 37.5 kB [initial] [rendered]
chunk {3} styles.d121423864e1eb24cdc1.css (styles) 63.1 kB [initial] [rendered]

WARNING in budgets, maximum exceeded for initial. Budget 2 MB was exceeded by 170 kB.

See that main is 2.07 MB? Yikes!  That will gzip down, but still way bigger then we want for a simple web application.

Figuring out what’s wrong

Noice the --stats-json param in the ng build above? It allows us to use an awesome tool called webpack-bundle-analyzer Simply run the following after running your build:

webpack-bundle-analyzer ./dist/stats.json

This will open up a web browser with a graph that looks like this

moment is everywhere

All the big boxes above (bigger means more bytes) are moment related.  But we already kinda figured that…

Including only what we need

The moment docs and googling moment typescript moment angular will tell you to include moment like so:

import * as _moment from 'moment-timezone';

Note here I only include moment-timezone, as it has moment bundled inside of it.  By default when I build this ALL timezones and ALL locales will be bundled in my build (as reflected in the bundle-analyzer graph above).

Step 1 is to not include ALL the timezone data.  Change the import to:

import * as _moment from 'moment-timezone/builds/moment-timezone-with-data-2012-2022.min';

This will include all the timezone data for 2012-2022.  Of course if your requirements don’t fit inside these dates you are stuck, but if they do it will save you lots of bytes.

Step 2 is to only include the locale(s) you care about.  In my case I’m lucky enough to only need en-us for my simple web application.  To do this we will use @angular-builders/custom-webpack to allow customization of the ng build webpack’ing.

yarn/npm install @angular-builders/custom-webpack, then update your angular.json architect.build.builder attribute to be @angular-builders/custom-webpack:browser Then add the following object to the options object:

"customWebpackConfig": {
  "path": "./extra-webpack.config.js",
  "replaceDuplicatePlugins": true,
  "mergeStrategies": {
   "externals": "prepend"
}

Now create a extra-webpack.config.js at the root of your Angular project with the following contents:

'use strict';

const webpack = require('webpack');

// https://webpack.js.org/plugins/context-replacement-plugin/
module.exports = {
  plugins: [new webpack.ContextReplacementPlugin(/moment[/\\]locale$/, /en-us/)]
};

Lets see if it paid off

ng build --stats-json --prod --build-optimizer --delete-output-path --aot --output-path=dist

Produces:

chunk {0} runtime.ec2944dd8b20ec099bf3.js (runtime) 1.41 kB [entry] [rendered]
chunk {1} main.4ec69d3753f8d4c43048.js (main) 962 kB [initial] [rendered]
chunk {2} polyfills.c6871e56cb80756a5498.js (polyfills) 37.5 kB [initial] [rendered]
chunk {3} styles.d121423864e1eb24cdc1.css (styles) 63.1 kB [initial] [rendered]

962 kB? Very Nice!

PS – don’t forget to gzip those ng build generated static resources!

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s