Changing from moment to Day.js — how, why and fixing vue-chartjs

tzar
Level Up Coding
Published in
4 min readJan 12, 2021

--

moment.js is an awesome date/time library that I have used in essentially every project I’ve worked on for the last decade. Sadly, it’s showing its age these days with the creators discouraging its use for new projects and the project being put into maintenance only mode.

I can understand why — the tooling and structure of JavaScript libraries have evolved rapidly in recent years and older libraries often can’t keep up without making massive, backwards incompatible changes. The web itself has evolved as well — the scourge that was IE has all but vanished and almost everything has slowly become powered by Webkit and its variants instead. There’s a lot of backwards compatibility that simply isn’t needed any more and at some point it becomes more sensible to just rewrite.

So why should this bother you — and should you bother switching? If you’re trying to cut down on your bundle size, I’d say it’s worth considering as moment is pretty heavy compared to the new generation of lightweight date manipulation libraries. It’s also fairly easy to do even with a complex existing codebase thanks to Day.js which attempts to keep an almost identical API to moment.js.

There are really only two things you need to know when swapping between the two — Day.js uses a core + plugin system, so depending on the format strings and features you’re currently using, you’ll need to register the appropriate plugins. For example, I need .weekday() so I register the weekday plugin:

import dayjs from 'dayjs'
import weekday from 'dayjs/plugin/weekday'
dayjs.extend(weekday)

You may end up with a few of these plugins by the time you’re done! The second thing you need to know is that Day.js objects are immutable — so anything like this:

var foo = moment()
foo.add(1, 'day')

Will instead change to this:

var foo = moment()
foo = foo.add(1, 'day')

Once you have the right plugins registered and you’re used to immutable objects, it’s essentially identical. A few find and replaces and you can give yourself a pat on the back! Unless you’re using Chart.js or vue-chartjs anyway, in which case…

Forcing Chart.js to use Day.js as well

So where does vue-chartjs come in? As it turns out, Chart.js actually bundles moment.js by default, and of course vue-chartjs uses Chart.js as well. So we need to do the following:

  • Tell Chart.js to stop bundling moment.js
  • Add a date/time adapter so that Chart.js uses Day.js instead
  • Make sure that we do the above without interfering with code splitting

That last part can be a little tricky but is crucial — after all, we started this process looking for a way to reduce our bundles! If you simply import dayjs and Chart.js in your entry file and start messing with plugins and adapters, the code splitting system isn’t going to be able to pull it out into its own chunk any more. This won’t matter much for Day.js as you’re probably going to use it everywhere and it’s pretty small anyway — but Chart.js is another matter entirely.

The approach that has worked the best for me is to wrap the underlying libraries and pass through exports. So for Day.js I have a file — src/dayjs.js — which looks like this:

It’s very simple — import the library, configure it, then export it again. Now throughout my codebase, instead of import dayjs from 'dayjs' I simply import dayjs from '@/dayjs'. This ensures that everywhere gets the same plugins and code can still be split into chunks as needed.

We also need to wrap vue-chartjs to tell it how to use Day.js instead of moment.js. There are some libraries on npm which claim to do this but I had better luck adapting (hah!) the adapter from here to also use my wrapped Day.js instead. We’ll be wrapping vue-chartjs this time as that’s what we ultimately end up importing (and as such is what the code splitting is going to use):

Finally, we need to stop the default bundling of moment.js into Chart.js. You can do this by updating your webpack config, e.g in vue.config.js:

module.exports = {
configureWebpack: {
externals: {
moment: 'moment'
}
}
}

This lets us lie to webpack that moment will already exist on the page externally so it won’t try to import it. Of course, it won’t actually exist, but nothing will use it anyway, so.. mission accomplished?

Once all this is done — depending on the number of Day.js plugins you use and whether you previously set up rules to remove moment.js locales — you should see an anywhere between a 45–250kB reduction in the parsed size of your codebase. Not too shabby!

--

--