How to optimize moment.js with webpack

(1) Cleaning moment locale file

By default, when you write var moment = require('moment') code and pack it with webpack , the size of the bundled file becomes heavy because webpack bundles all Moment.js all locale files (in Moment.js 2.18.1 , compressed KB as 160 ).

To strip unnecessary locales and bundle only used locales, add moment-locales-webpack-plugin :

 // webpack.config.js
const MomentLocalesPlugin = require('moment-locales-webpack-plugin');

module.exports = {
    plugins: [
        // To strip all locales except “en”
        new MomentLocalesPlugin(),

        // Or: To strip all locales except “en”, “es-us” and “ru”
        // (“en” is built into Moment and can’t be removed)
        new MomentLocalesPlugin({
            localesToKeep: ['es-us', 'ru'],
        }),
    ],
};

To optimize size, two webpack plugin portals can also be used:

  1. IgnorePlugin
  2. ContextReplacementPlugin
IgnorePlugin

You can use IgnorePlugin .

 const webpack = require('webpack');
module.exports = {
  //...
  plugins: [
    // Ignore all locale files of moment.js
    new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/),
  ],
};

And you can still load some locales in your code.

 const moment = require('moment');
require('moment/locale/ja');

moment.locale('ja');
...

Create React App and Next.js use this solution.

ContextReplacementPlugin

If you want to specify the include locale file in the webpack configuration file, you can use ContextReplacementPlugin .

 const webpack = require('webpack');
module.exports = {
  //...
  plugins: [
    // load `moment/locale/ja.js` and `moment/locale/it.js`
    new webpack.ContextReplacementPlugin(/moment[/\\]locale$/, /ja|it/),
  ],
};

In this case you don’t need to load the locale file in your code.

 const moment = require('moment');
moment.locale('ja');
...
Measurement
  • webpack: v3.10.0
  • moment.js: v2.20.1
File sizeGzipped
Default266 kB69 kB
w/ IgnorePlugin68.1 kB22.6 kB
w/ ContextReplacementPlugin68.3 kB22.6 kB

How to optimize moment.js with webpack

(2) unused code

In our actual project, moment is integrated in bricks base component library, bricks component library has cleaned the locale files at build time , but the actual use of moment in the process of writing business code is very rare, and only a few pieces of api moment.js used. price in the product. At this point you may ask if we have tree shaking ? It can help us to automatically clear invalid codes, but it backfires, moment is highly based on OOP API (prototype chain-oriented programming), all api are mounted on the prototype chain , resulting in the inability to use Webpack the newly introduced Tree-shaking code optimization technology, which cannot identify which codes are dead code .

Moment still has the following problems
  • It is highly based on OOP API which makes it unusable tree-shaking causing huge packet size and performance issues;
  • Its variability will cause some moment calculation problems;
  • The complex OOP API makes Moment the variability problem even worse, here is an example https://github.com/moment/moment/blob/develop/src/test/moment/add_subtract .js#L244-L286 ;
  • Moment The performance is average, due to the complex API making Moment compared with the native Date has a huge performance overhead;

Moment.js has some issues

Moment variability

When I started using moment , I assumed it followed the FP principle and would return the same value every time the function was called:

var now = moment();
var yesterday = now.subtract(1, 'days');
var dayBeforeYesterday = now.subtract(2, 'days');

Of course, I didn’t get the results I expected, which caught me off guard.

Considering this pseudocode, I would expect the code to behave as follows:

 var now = now;
var yesterday = now - 1day;
var dayBeforeYesterday = now - 2days;

But backfired, it ended up working like this, which strikes me as weird:

 var now = now;
var yesterday = now = now - 1day;
var dayBeforeYesterday = now = now - 2days;

Moment The variability of objects makes me use only .clone() with caution.

 var now = moment();
var yesterday = now.clone().subtract(1, 'days');
var dayBeforeYesterday = now.clone().subtract(2, 'days');

Moment It’s easy to make these subtle mistakes with the process, and I think the FP principle helps to minimize similar mistakes.

refer to:
How to make moment object immutable
Problems caused by moment object mutability
How to resolve mutability in moment.js?

Alternative

If you don’t use timezones, but only use some simple functions in moment.js , this will lead to your application being introduced with many unused methods, which is extremely wasteful of performance and memory. Here it is recommended to use dayjs , dayjs very small volume, ultra-small compressed volume, only 2kb or so, all changes Day.js objects的API操作都将返回一个新的实例(不可变性), Moment.js API , moment Smooth transition to day.js . date-fns support Tree-shaking code optimization technology, provide friendly functional programming ( FP ) functions (all pure functions), support function currying typescript support– typescript ,它的不可变性能很好的弥补moment 59f3d384060a71bf6921c6425bd3a7d3—带来的问题, React,Sinon.js和—0994891e9570de49600bff20808ad59a webpack好基friends to use together.

moment.js , day.js , date-fns Simple comparison:

namesize ( gzip )Support Tree-shakingfameapi Number of methodsmodeltime zone supportNumber of languages supported
Moment.js329K(69.6K)No38khighOOvery good ( moment-timezone )123
date-fns78.4k(13.4k) without tree-shakingYes13khighFunctionalnot yet supported32
dayjs6.5k(2.6k) without pluginsNo14kmiddleOOnot yet supportedtwenty three

Alternatives to Moment.js

Project Practice

First check the application dependency Moment situation, found Moment integrated in bricks basic component library, currently only the date component uses moment The application does not use the date component, and there are no other dependencies Moment is installed, and the business code uses Moment There are few scenarios, only use calendar and format api ,程序对moment e5bee237a0ba841927cb59016685b50c—依赖程度极低,因此完全可以将—f35d9b145f66465acdbbb3f24b901b16—从业务代码中移出, Moment Zero lightweight day.js , or the —a6bf9a3378b9e41f640d1e14ac966cce52— method that supports Tree-Shaking date-fns , in or natively implements the format method.

综合考虑, date-fns支持FP 、 Tree-Shaking ,具有不可变性,跟React变性、 FP原则的思想完美吻合, date-fns 2d62dcb86d86366e706ddc68c3aeee9a—替换掉bricks集成的moment模块, momentformat , calendar method

(1) Because bricks integrates moment , it is necessary to exclude it from being packaged into the final product

 // config-overrides.js

/** 清除bricks组件集成的moment,不让其参与打包 */
const eliminateMomentOfBrs = (config) => {
  config.plugins.push(new webpack.IgnorePlugin({ resourceRegExp: /moment/ }));
}

module.exports = function override(config, env) {
    eliminateMomentOfBrs(config);
}

(2) Replace the format and calendar methods in the business code moment fdecb7846e47fca780b1fea4979db324—

 // moment.js
moment(time).format('MM月DD日'); // 09月02日

// date-fns
import { format } from 'date-fns';
format(time, 'MM月dd日'); // 09月02日

// moment.js
moment(time).calendar(null, {
  sameDay: '[今日]HH:mm',
  nextDay: '[明日]HH:mm',
  nextWeek: 'M月D日 HH:mm',
  lastDay: 'M月D日 HH:mm',
  lastWeek: 'M月D日 HH:mm',
  sameElse: 'M月D日 HH:mm',
}); // // 8月27日 09:23

// date-fns
import { format, formatRelative } from "date-fns";
import { zhCN } from "date-fns/esm/locale";

const formatRelativeLocale = {
  lastWeek: "M月d日 HH:mm",
  yesterday: "M月d日 HH:mm",
  today: "[今日]HH:mm",
  tomorrow: "[明日]HH:mm",
  nextWeek: "M月d日 HH:mm",
  other: "M月d日 HH:mm"
};

const locale = {
  ...zhCN,
  formatRelative: (token) => formatRelativeLocale[token]
};

formatRelative(time, new Date(), { locale }); // 8月27日 09:23

If you are using ESLint , you can install a plugin plugin to help you identify places in the codebase that you don’t have (may not need) Moment.js Inadvertent installation introduced moment .

Install this plugin…

 npm install --save-dev eslint-plugin-you-dont-need-momentjs

…then update your config

 "extends" : ["plugin:you-dont-need-momentjs/recommended"],

Comparison before and after optimization:

moment build\static\js\2.7fde9c2a.chunk.js 17.52KB , date-fns build\static\js\3.4a62a5b9.chunk.js 8.76KB , the overall volume reduction is close to 10KB , the effect is not very obvious

Further analysis of the visual tree diagram found that the program has introduced dayjs , but the program did not install it separately, execute npm list dayjs , and found that only the packaged large-value business component library @casstime/mall-components used it

Based on the above observations, it seems unnecessary to introduce date-fns . It is better to use dayjs directly. After long-term consideration, it is better to replace it with date-fns .

Comparison before and after optimization:

moment模块,并且没有添加其他模块,和业务组件@casstime/mall-components共用dayjs ,产物体积17.5KB (greater than last optimized 10KB )

Day.js