乐趣区

关于moment.js:如何使用-webpack-优化-momentjs

(1)荡涤 moment 语言环境文件

默认状况下,当您编写 var moment = require('moment') 代码并应用 webpack 打包时,捆绑文件的大小会变得很重,因为 webpack 会捆绑 所有Moment.js 所有语言环境文件(在 Moment.js 2.18.1 中,压缩后的 KB160)。

要去除不必要的语言环境并仅捆绑应用的语言环境,请增加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'],
        }),
    ],
};

为了优化大小,还能够应用两个 webpack 插件传送门:

  1. IgnorePlugin
  2. ContextReplacementPlugin
IgnorePlugin

您能够应用IgnorePlugin.

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

而且您依然能够在代码中加载一些语言环境。

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

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

Create React App 和 Next.js 应用这个解决方案。

ContextReplacementPlugin

如果要在 webpack 配置文件中指定蕴含语言环境文件,能够应用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/),
  ],
};

在这种状况下,您不须要在代码中加载语言环境文件。

const moment = require('moment');
moment.locale('ja');
...
测量
  • webpack: v3.10.0
  • moment.js: v2.20.1
File size Gzipped
Default 266 kB 69 kB
w/ IgnorePlugin 68.1 kB 22.6 kB
w/ ContextReplacementPlugin 68.3 kB 22.6 kB

How to optimize moment.js with webpack

(2)unused code

在咱们的理论我的项目中,moment被集成在 bricks 根底组件库中,bricks组件库在构建时曾经对语言环境文件做了荡涤解决,然而在业务代码编写过程中理论应用 moment 的场景特地少,只用到了几个系统的 api,却要承当引入整个moment.js 构建到产物中的代价。这时你可能要问到咱们不是有 tree shaking 吗?它能够帮咱们主动革除有效代码,但大失所望,moment高度基于 OOP API(面向原型链编程),所有api 都挂载到原型链上,导致无奈应用 Webpack 新引入的 Tree-shaking 代码优化技术,无奈辨认哪些代码是dead code

Moment 还存在如下一些问题
  • 它高度基于OOP API,这使得它无奈应用 tree-shaking,从而导致微小的包大小和性能问题;
  • 它的可变性将导致一些时刻计算问题;
  • 简单的 OOP API 使得 Moment 可变性问题更加重大,这儿有个例子 https://github.com/moment/mom…;
  • Moment性能个别,因为简单的 API 使得 Moment 与原生 Date 相比有着微小的性能开销;

Moment.js 领有一些问题

Moment 可变性

当我开始应用 moment 时,我假如它遵循 FP 准则,并且每次调用函数时都会返回雷同的值:

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

当然,我没有失去我冀望的后果,这让我措手不及。

思考这个伪代码,我冀望如下代码行为形式:

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

但大失所望,它最终像这样工作,这让我感觉很奇怪:

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

Moment对象的可变性使得我只能小心翼翼应用.clone()

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

Moment应用过程很容易呈现这些轻微的谬误,我认为 FP 准则有助于最大限度地缩小相似谬误。

参考:
怎么使 moment 对象不可变
moment 对象可变性造成的问题
如何解决 moment.js 中的可变性?

替换计划

如果您没有应用时区,而只应用了 moment.js 中的一些简略函数,这会导致你的应用程序被引入了很多没应用的办法,这是极其节约性能和内存的。在这里举荐应用 dayjs, dayjs 体积十分小,超小的压缩体积,仅仅有 2kb 左右,所有更改 Day.js 对象的 API 操作都将返回一个新的实例(不可变性),和 Moment.js 有着雷同的 API 和模式,因而很容易从 moment 平滑过渡到 day.jsdate-fns 反对 Tree-shaking 代码优化技术,提供敌对的 functional programming (FP) 函数(都是纯函数),反对函数柯里化,反对 typescript,它的不可变性能很好的补救moment 带来的问题,因而它很适宜与 React,Sinon.jswebpack等好基友一起应用。

moment.jsday.jsdate-fns简略比拟:

名字 大小(gzip) 反对Tree-shaking 名气 api办法数 模式 时区反对 反对的语言数
Moment.js 329K(69.6K) No 38k OO 十分好(moment-timezone) 123
date-fns 78.4k(13.4k) without tree-shaking Yes 13k Functional 还不反对 32
dayjs 6.5k(2.6k) without plugins No 14k OO 还不反对 23

Moment.js 的替换计划

我的项目实际

首先排查应用程序依赖 Moment 状况,发现 Moment 集成在 bricks 根底组件库中,只有日期组件应用了 moment,以后应用程序没有应用日期组件,而且也没有其余依赖Moment 的模块被装置,业务代码应用 Moment 的场景也很少,仅应用 calendarformat两个 api,程序对moment 依赖水平极低,因而齐全能够将 Moment 从业务代码中移出,引入更加零轻量级 day.js,或者反对Tree-Shakingdate-fns,在或者原生实现 format 办法。

综合思考,鉴于 date-fns 反对 FPTree-Shaking,具备不可变性,跟React 状态不可变性、FP准则的思维完满吻合,抉择应用 date-fns 替换掉 bricks 集成的 moment 模块,替换 momentformatcalendar办法

(1)因为 bricks 集成了moment,须要先排除其被打包到最终产物中

// 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)替换业务代码中 momentformatcalendar办法

// 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

如果你正在应用 ESLint, 你能够装置一个插件 plugin 来帮忙你辨认代码库中你没有(可能不须要)Moment.js 的中央,避免同学不经意装置引入moment

装置这个插件 …

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

… 而后更新你的配置

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

优化前后比照:

移出 moment 使得 build\static\js\2.7fde9c2a.chunk.js 体积缩小 17.52KB,但增加date-fns 使得 build\static\js\3.4a62a5b9.chunk.js 减少8.76KB,整体体积缩小靠近10KB,成果不是很显著

对可视化树状图进一步剖析发现程序中曾经引入了 dayjs,但程序并没有独自装置它,执行npm list dayjs,发现只有封装大额业务组件库@casstime/mall-components 应用了它

基于以上察看,引入 date-fns 看来是没有必要的,间接应用 dayjs 即可,短暂思考,对立换成 date-fns 比拟好。

优化前后比照:

再次调整优化后干掉了整个 moment 模块,并且没有增加其余模块,和业务组件 @casstime/mall-components 共用dayjs,产物体积整体缩小17.5KB(大于上次优化的10KB

Day.js

退出移动版