关于moment打包的那些事

5,152 阅读1分钟

在项目中经常用到moment库,有没有发现引入moment之后,项目build完的文件大小明显大了不少,下面就来分析一下原因, 以及如何做优化。

一、问题定位

首先看下代码结构

import React from 'react'
import { render } from 'react-dom'
import moment from 'moment'

const App = () => {
  const date = new Date()
  return (
    <div>
      <h1>{date.toLocaleDateString()}</h1>
      <h1>{moment().format('YYYY-MM-DD')}</h1>
    </div>
  )
}

render(<App/>, document.getElementById('app'))

  • 上面两张图片分别是没用moment和用了moment编译的结果

  • 只有几行代码, 编译出来的文件相差那么大。网上搜了一波才知道, 打包时把所有的locale都打包进去了, 初步解决方案是用webpack.IgnorePlugin来处理。

  • IgnorePlugin又是做了什么操作?

二、 IgnorePlugin 原理分析

// webpack.config.js
...
plugins: [
    new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/) // 配置忽略规则
]

原理:在webpack编译阶段, 如果引入的文件路径匹配/^\.\/locale$/,则会忽略这个文件, 也就不会被打包进去。

  • 搜索moment包编译后的文件并未找到完全匹配/^\.\/locale$/这个正则的引入语句,只有aliasedRequire('./locale/' + name)这条语句和locale相关, 却又和正则匹配不上, 倒是在moment的src源文件中有import ... from './locale'。 但是在momentpackage.json中main是指向编译后的文件并不是src文件,这就奇了怪了, 于是debug IgnorePlugin看了一下。

  • 卧槽, 图中request真是./locale, 眼瞎了还是webpack的问题?按照dependencies的位置1853行查看moment编译后的文件, 定位到了确实是 aliasedRequire('./locale/' + name), 怎么回事?

  • 原来webpack在编译时,遇到require('./locale/' + name)此类表达式时,webpack 会查找目录 './locale/' 下符合正则表达式 /^.*\.$/的文件。由于 name 在编译时还是未知的,webpack 会将每个文件都作为模块引入到 bundle 中, 这就是为什么引入moment之后, 编译完的文件为什么会那么大的原因

三、添加IgnorePlugin后, 需要设置locale怎么办?

  1. 在添加webpack.IgnorePlugin之后, 文件大小是减小了, 但是在设置moment.locale('zh-cn')之后, format之后的日期仍然是英文的,语言没有切换过来。
  2. 功能缺失肯定是不能接受的, 怎么办?怎么办?
  3. 在moment文档上也提供了解决方案, moment-locales-webpack-plugin
new MomentLocalesPlugin({
  localesToKeep: ['zh-cn'],
})
  1. moment默认locale是en,它必然会被打包进去, 如果需要配置其他语言,可以通过localesToKeep来配置, 其他没用到的语言包也就不会被打包进去了。

四、 moment-locales-webpack-plugin原理分析

  1. 如果没有配置option, 用IgnorePlugin忽略所有语言包(en除外)
  2. 如果设置option, 用 ContextReplacementPlugin插件设置webpack在编译阶段的查找规则,即查找指定的locale。
...
if (localesToKeep.length > 0) {
    var regExpPatterns = localesToKeep.map(function(localeName) {
        return localeName + '(\\.js)?';
    });
    return new ContextReplacementPlugin(
        /moment[\/\\]locale/,
        new RegExp('(' + regExpPatterns.join('|') + ')$') // 配置webpack编译阶段的查找规则, 即指定语言包
    );
} else {
    return new IgnorePlugin(/^\.\/locale$/, /moment$/);
}
...

到此结束。

水平有限,文中有错误之处,还望大佬指正。