Life After Moment.js

Life After Moment.js
As of September 2020, Moment.js — probably the most popular JavaScript library for handling dates became a legacy project in maintenance mode. As the Moment team explained:

It is not dead, but it is indeed done

Major issues (like security concerns) will get fixed, but the project will no longer be developed. There will be no new features, no changes to the API, and the complaints about the library (especially its performance) won't be addressed. It also means that, with time, Moment will gradually become obsolete and unsuited for modern apps.

But don't panic just yet, and don't cry over your beautiful moments with Moment, as we still have plenty of time to adjust to the post-Moment reality. In this blog post I'll briefly talk about the reasons behind the decision to stop developing Moment.js, and what we should do about it.

Why was Moment stopped?

In order to understand why Moment development was stopped, it's best to read what the creators themselves have to say about it. Among the reasons for stopping it, they list two most important ones: bundle size and mutability of objects.

Bundle size

Moment has been criticised for the bundle size (eg. you-dont-need). The creators admit that Moment doesn't work well with modern "tree shaking" algorithms, and it tends to increase the size of web application bundles. One can quickly see the magnitude of Moment's size on a simple example. To illustrate it, I've created three basic react apps with create-react-app:

The table below compares the bundle sizes:

Application type bundle size
with moment 386.02 KB
with luxon 207.59 KB
with day.js 142.04 KB
with neither 135.82 KB

In order to check the sizes yourself, clone the repos and run npm install, npm run build, npm run analyze.

As you can see, the bundle with Moment is almost twice as big as the one with Luxon, while the bundle with day.js is only slightly bigger than the bundle without any of the libraries! For slower Internet connections and mobile users, the difference in bundle size might be noticeable.

Even Lighthouse in Chrome Dev Tools recommends replacing Moment with a smaller library.

Mutability of the objects

Another reason why people were unhappy with Moment was the mutability of objects.

let a = moment('2016-01-01'); 
let b = a.add(1, 'week'); 
a.format('YYYY-MM-DD');
"2016-01-08"

It came as a surprise to many that one needs to use clone() on the Moment objects—it's an annoyance, and not the best practice. More modern libraries take a different approach and secure immutability of objects.

What now?

As mentioned at the beginning, Moment isn't a dead library and you can continue to use it in your project. But one should avoid adding new dependencies that rely on Moment, as it would add to the code you will have to change in due time. It might not be obvious that a library depends on Moment—for example, jsonwebtoken has it as a dependency—so check it before you kick off to save yourself time down the line.

If you've come to the conclusion that you have some spare time on your hands and you want to start looking into replacing Moment as you'll have to do it at some point anyway, there are a few things to consider or do before you start making changes in the code.

1. Dependencies by other libraries

Replacing Moment library entirely might take much more effort than swapping its applications in the code with a solution from another library or pure JavaScript. As mentioned above, some of your libraries might also depend on Moment, which makes pruning it entirely out of your app a much harder task.

In order to determine how deeply your application depends on Moment, check your package-lock.json for any mentioning of Moment.

Thankfully, many libraries and tools are working toward making themselves independent of Moment—Cypress removed Cypress.moment() in April (release 7.0.0), and Ant Design provides a workaround for Moment (although Moment is still a dependency of antd).

It might not be worth your time to jump into replacing all dependencies with a Moment-free alternative. Seeing libraries already working on making themselves independent from Moment is reason for optimism, so calmly waiting for a release might be the best approach here.

2. Browser support

Moment works well on IE 8 and higher, while alternative tools might not be compatible with older browsers—for example, Luxon supports IE 10 and higher.

Legends say that there are still clients who expect to support dead browsers... But jokes aside, you should check the types of browsers the users of your apps are using before making the decision to sail away from Moment.

3. Write your tests

It goes without saying that handling the dates correctly is extremely important to any project, and replacing a dates library with another solution can seem to be a scary task. It won't be, if you write your tests and can quickly and surely confirm that the app works in the same way after the changes.

When I was swapping Moment with Luxon, I was relying on two types of unit tests.

Firstly, I made sure that all functions that handle date manipulations are unit tested. If the coverage wasn't complete, I've added the missing cases before I made any changes to the code. Secondly, I made sure I have correct and complete tests for rendering the components, which are checking if the dates are being rendered in the correct way.

The first type of test made it very easy for me to swap Moment with Luxon, as I could clearly see from the test output what changes my function needs. Once the unit test for function was passing, the tests for component should pass too.

Here is some example code for function unit tests:


// implementation
import { DateTime } from "luxon"

export function formatIso8601String(date: string): string {
		return DateTime.fromISO(date, {zone: "America/Chicago"}).toFormat("MM/dd/yyyy H:mma")
}

// test
describe("converts ISO8601 string into MM/DD/YYY H:mmA in Chicago timezone", () => {
		it("in the summer - CDT", () => {
				expect(formatIso8601String("2021-05-27T12:39:41+00:00")).toEqual("05/27/2021 7:39AM")
		})

		it("in the winter - CST", () => {
				expect(formatIso8601String("2021-11-27T12:39:41+00:00")).toEqual("11/27/2021 6:39AM")
		})
})

4. Research possible replacements

There are several libraries out there that can potentially replace Moment in your app. The creators of Moment recommend looking into Luxon, Day.js, date-fns, js-Joda, or even replacing Moment with native JS.

One's choice of replacement should depend not only on their personal coding style and readability of the documentation, but one should also make sure that the new library will be compatible with other libraries that are used in your app. For example, by default the DatePicker from antd accepts only Moment objects, and even though there are official ways to use another library by implementing another level of abstraction, they make the code smellier and tests trickier, and the dependency on Moment remains in package-lock.json.