resolve: {
alias: {
'rc-picker/es/generate/moment': 'rc-picker/es/generate/dayjs'
}
}
温馨提示:虽然使用前面的配置成功引入了dayjs(或者你使用了官方推荐的方式引入了dayjs),但是如果你想将dayjs类型的默认值传递给antd的时间相关组件,请不一定保证dayjs默认值。 对应的dayjs版本与rc-picker的package.json中配置的dayjs版本一致。 rc-picker 是 antd 的依赖项。 看看下面的原因。
本文针对的antd版本是:4.18.9
总长DR
替换时刻的官方文档为:ant.design/docs/react/...
文档主要介绍了两种方法,一种是自己定义组件,一种是使用webpack的插件。 这份文档对我来说有以下几个不解之处:
关于这些,文档中没有答案。 这里不得不再次表扬尤玉溪写文档的方式。 在教你如何使用它之前,我先告诉你原因。
我没有研究antd为什么默认使用moment,研究了为什么moment可以按照文档的形式进行替换。 对原理感兴趣的朋友请继续阅读下文。
原因
我在项目中使用react+antd已经有半年多了。 我在使用antd的table组件时,发现文档中的使用方法并没有达到效果。 我发现文档的版本和我使用的antd版本不一致。 而且我查了老版本的文档,发现没有关于如何使用的介绍。 最后不得不选择升级antd。
依赖第三方库进行盲目升级并不是一个好的选择。 许多第三方库不保证向后兼容性。 即使这样做了,也有可能因为遗漏而没有测试到位,可能会在升级中带来新的问题。
升级后进行了防火测试,发现一切正常。 我心里突然对antd刮目相看了。 大厂就是大厂,稳定性有保障。
直到我在项目中使用了三天的DatePicker,发现每当点击时间选择框时,都会报错clone.weekdayisnotafunction:
我立即查看了webpack的配置,发现webpack使用了antd-dayjs-webpack-plugin插件,将moment替换为dayjs。 而且我清楚的记得,使用这个插件的时候没有报错。 很有可能是升级antd导致的bug。
这个时候我就非常信任antd和antd-dayjs-webpack-plugin了。 我相信这不仅仅是我的使用问题。
然后我做了以下事情:
阅读antd如何替换moment的文档,看看是否有更新。 推断是没有更新,仍然是替换方法。
我查看了antd-dayjs-webpack-plugin的github,看是否有更新说明。 我发现这个插件已经几个月没有更新了,但是我没有发现任何与我在问题中提到的类似的内容。 即使我找到了,这个问题也已经存在很长时间了。 官方方面暂未回应。
此时我开始对antd产生一些怀疑。
最终我选择了解决npm问题最好的办法,那就是重装是个好主意! 果然,重新安装后,问题解决了,错误消失了!
如果是别的库,故事可能到这里就结束了,但是它是antdwebpack 下载报错,它是Ant出品的,我又这么信任它,它怎么会出问题呢?
追根溯源
既然我在antd文档中找不到为什么可以按照它说的方法替换成moment,那就自己看一下吧。
首先我看了一下antd-dayjs-webpack-plugin的源码。 通过它,我大致明白了为什么可以替代moment。
该插件主要做了以下几件事:
源码地址:github.com/ant-design/...
// set dayjs alias
if (this.replaceMoment) {
const { alias } = compiler.options.resolve
if (alias) {
alias.moment = 'dayjs'
} else {
compiler.options.resolve.alias = {
moment: 'dayjs'
}
}
}
这样,当webpack遇到引用moment的模块时,它就会转而引用dayjs。
if (this.plugins) {
const { entry, module } = compiler.options;
const initLoaderRule = {
test: /init-dayjs-webpack-plugin-entry.js$/,
use: [
{
loader: path.resolve(__dirname, "./init-loader.js"),
options: {
plugins: this.plugins,
},
},
],
};
if (module.rules) {
module.rules.push(initLoaderRule);
} else {
compiler.options.module.rules = [initLoaderRule];
}
const initFilePath = path.resolve(
__dirname,
"init-dayjs-webpack-plugin-entry.js"
);
const initEntry = require.resolve(initFilePath);
compiler.options.entry = makeEntry(entry, initEntry);
}
上面代码的意思是在webpack的入口中添加一个“init-dayjs-webpack-plugin-entry.js”,这样webpack就会加载这个js文件,然后插件就会写一个特殊的叫init-loader.js 加载器来解析“init-dayjs-webpack-plugin-entry.js”。 init-loader.js主要做的事情就是注入js代码:
源代码位于:github.com/ant-design/...
const { getOptions } = require("loader-utils");
module.exports = function loader(source) {
const options = getOptions(this);
options.plugins.forEach((plugin) => {
source += `var ${plugin} = require('dayjs/plugin/${plugin}');`;
});
options.plugins.forEach((plugin) => {
source += `dayjs.extend(${plugin});`;
});
// special plugin
source += `var antdPlugin = require('antd-dayjs-webpack-plugin/src/antd-plugin');dayjs.extend(antdPlugin);`;
return source;
};
至此,插件的工作就完成了。 它产生了以下治疗效果。 这种治疗效果非常重要。 请务必了解:
webpack编译出来的boundle包会包含以下代码(有些省略,主要是引入dayjs后为dayjs安装各种插件):
var dayjs = __webpack_require__(/*! dayjs/dayjs.min */ "./node_modules/.pnpm/registry.npmmirror.com+dayjs@1.10.7/node_modules/dayjs/dayjs.min.js");
var isSameOrBefore = __webpack_require__(/*! dayjs/plugin/isSameOrBefore */ "./node_modules/.pnpm/registry.npmmirror.com+dayjs@1.10.7/node_modules/dayjs/plugin/isSameOrBefore.js");
dayjs.extend(isSameOrBefore);
//...省略部分安装其他插件的代码
这个插件的作用就是注入一段代码,然后使用alias将moment的引用改为dayjs。 然后我们就可以愉快的使用dayjs了。
似乎没有问题。 antd曾经引用的那一刻,现在被webpack引用到了dayjs,但是插件也安装了。 为什么会报错? 等等,好像有什么不对劲?
webpack只引入项目根目录下package.json中配置的dayjs指向的版本,并安装该版本的插件。 如果antd没有引用这个版本怎么办?
是的,我们升级 antd 后,antd 引用的 dayjs 版本与 antd-dayjs-webpack-plugin 插件引入的 dayjs 版本不一致。 事实上,通过antd插件,确实引用了dayjs而不是moment,而且引用的并不是同一个版本。 antd引用的dayjs只是一段核心代码,没有安装任何插件,从而导致错误。
antd如何指代时刻
具体来说,antd中引用moment是通过antd引用rc-picker,然后rc-picker引用moment。 虽然我们的项目没有在package.json中配置对dayjs的依赖,但是antd引用的rc-picker也会安装对dayjs的依赖。 然而,这个依赖只能被rc-picker引用。 这就提出了一个严肃的问题:
一旦 rc-picker 引用的 dayjs 和 antd-dayjs-webpack-plugin 引用的 dayjs 不是同一个版本,antd-dayjs-webpack-plugin 插件会导致 antd 中使用 dayjs 的组件报错。
我的项目中,第一次配置时使用了antd-dayjs-webpack-plugin引入了dayjs的1.10.7版本,而antd此时也引用了1.10.7版本(由于在package.json中的配对) rc-picker dayjs 的依赖配置为:“^1.8.30”,初始化项目时,dayjs 最高版本为 1.10.7)。 因为版本一致,所以webpack编译后antd引用的dayjs就是antd-dayjs-webpack-plugin处理的dayjs,所以不会有问题。
升级antd后,antd引用的dayjs升级到1.10.8。 此时插件处理的是dayjs版本1.10.7。 结果antd引用的dayjs只有核心代码,没有任何插件,所以报错。
看看antd是怎么指moment的,
源代码位于:github.com/ant-design/...
import momentGenerateConfig from 'rc-picker/lib/generate/moment';
打开rc-picker的源码,发现rc-picker/lib/generate目录下有对应的dayjs实现webpack 下载报错,即rc-picker/es/generate/dayjs模块。
那么直接让antd引用这个现成的dayjs模块不就可以了吗,于是就有了文章开头的配置。
总结一下对antd的一些期望