date-fns v3 发布——这个由 200 个函数组成的 JavaScript 日期处理套件已经在 TypeScript 中重写,重新引入了 String 日期参数,在 Node 上支持 ESM,并且所有函数现在都可以通过命名导出导出。
经过几个月的开发,v3 终于出来了!
对于大多数开发人员来说,升级不需要做任何改动。对某些人来说,升级也是轻而易举的事。
v3 更新概览
然而,这个版本带来了很多好东西,所以对于初学者来说,这里是一些最值得注意的改变:
- 现在,该库是 100% 的 TypeScript,具有全新的 handcrafted types。
- 从所有函数中移除了参数检查和转换代码,除了格式化和解析,这导致了更小的最小构建大小。
- 字符串日期参数现在又回来了!
- 支持 Date 类扩展,如 UTCDate。
- 支持 Node.js 的 ESM。
- 所有函数现在都通过命名导出来导出,从而提高了与不同设置的兼容性。
- 新的扁平化库结构改善了 ESM/Deno 的 DX(
node_modules/date-fns/add.mjs
)。 - 不再支持 IE。
如果你想直接进入它,请参阅 v3.0.0 变更日志。
在这篇博文中,我将更详细地解释这些变化,并为我们在 v3 上所做的选择提供背景。
TypeScript 支持
此版本最初是对 TypeScript 的重写。当我 2014 年开始开发这个库时,TypeScript 似乎是一个小众项目。
从那以后,世界发生了变化,大多数开发人员已经意识到类型的力量,并在很大程度上采用了这种语言。
在 date-fns 中,类型是 JSDoc 生成的,我并不真正理解它是如何工作的,多年来,生成的类型变成了一个 23K LOC 怪物,没有希望轻松修复。
为了确保 date-fns 提供一流的 TypeScript 支持,我们重写了数百个函数到 TypeScript 中,我们仔细地手工制作了每个类型和接口,我们导出了所有东西,包括每个函数的选项接口,所以你可以轻松访问它们。
这花了一段时间,但是在社区的帮助下,我为这个结果感到非常自豪。
移除参数检查
在迁移到 TypeScript 时,我重新评估了我们在 v2 中引入的参数检查的价值。
它们背后的思想是为运行时提供尽可能多的类型安全,检查参数的数量和类型似乎是一个好主意。
现在,有了一流的 TypeScript 支持,它似乎是多余的。
因此,我们尽可能地删除了检查,只保留了格式和解析函数。
将类型安全委托给 TypeScript 可以使源代码更清晰,并提高最小构建大小。
以前,我乐于将最小构建大小降至 300 字节,现在只需要 200 字节!
字符串作为参数
在 v2 中,我们删除了字符串作为参数,现在它们又回来了。
我删除的原因是传递无效字符串并期望它得到正确解析是一个常见的错误。另一个普遍存在的问题是在传递日期的同时不传递时间,结果得到的是 UTC 午夜而不是本地时区,从而导致用户代码出现错误。在这两种情况下,开发人员都会将责任归咎于 date-fns,从而将支持工作推给了我们。
然而,这并没有解决用户的问题。他们在将字符串传递给函数库之前,会用new Date
对字符串进行包装,结果还是出现了错误。但是,当从版本 1 迁移到版本 2 时,成千上万的开发人员不得不修改每一个涉及字符串的函数调用,在没有类型的情况下工作时经常会遗漏一些地方,从而导致错误。此外,新开发人员在使用函数库时还会遇到额外的摩擦。
我承认我的错误,现在字符串已经回来了,这可能会导致新的问题涌入,但我会单独处理它们。
支持 Date 扩展
时区支持是我多年来一直巧妙地避免的一个最古老的功能请求,我不想重新实现 Moment.js 的 IANA 功能,因为发布一个巨大的时区数据库与为 JavaScript 创建最轻量级数据库的目标相矛盾。
然而,我最终决定做点什么,并发布了 @date-fns/utc
,它提供了 UTCDate
类,这是一个扩展,将常规函数(如 getHours
)映射到 UTC 版本(如 getUTCHours
)。
这使得 date-fns 可以在不改变代码的情况下执行所有 UTC 计算。然而,实现它需要彻底检查如何处理参数,所以我把它推迟到 v3。
另外,得益于 TypeScript 泛型,如果您将 UTCDate
日期作为参数传递并期望返回日期,您将获得 UTCDate
。
ESM 支持
自 v2 版本以来发生的另一件大事是 Node.js 获得了 ESM 支持。表面上的一个简单问题变成了一项需要彻底检修构建系统的艰巨任务。
在 v3 中,我几乎完全重写了构建系统,所以我也解决了 Node.js ESM。
date-fns 仍然是一个包含 CommonJS 和 ESM 的双重软件包,但考虑到需要花费大量精力才能使其正确,v4 版本将仅使用 ESM。
不再有默认的导出
所有函数都曾导出默认值,但在为 Node.js 开发 ESM 支持并测试该库时,我发现由于导出默认值的特性,它不可能在所有可能的设置中都正常工作。由于 export default
的行为性质,我发现不可能让它在所有可能的设置中都正常工作。
所以我决定折衷一下,改用命名导出,这样你就可以从子模块导入函数了,代码也需要改一下:
import addDays from "date-fns/addDays";
// ->
import { addDays } from "date-fns/addDays";
让我惊讶的是,它破坏了 Next.js,所以,在经过一番努力之后,我决定添加一个后备方案,这样 Next.js 用户就不会来找我抱怨了,这很糟糕,但我无能为力。
顺便说一下,Next.js 团队从来没有回复过我。
扁平结构
由于我正在重新设计构建系统,我决定解决浏览器 ESM 和 Deno 用户不得不面对的烦恼。
之前,导入代码如下: https://unpkg.com/date-fns/addDays/index.mjs
现在,库的结构是扁平的,所以不再有丑陋的索引文件了: https://unpkg.com/date-fns/addDays.mjs
很整洁,对吧?
IE 再见!
我四年前发布了 v2,IE 仍然健在,现在是时候说再见了!
IE 从来都不是一个好孩子。
一些历史
主要 date-fns 版本的大主题是修复我们在之前版本中犯的错误。
在开发 v1 API 时,我们从很多地方汲取灵感,从 Moment.js 到 Ruby on Rails。它导致 API 和处理边缘情况的方法不一致。因此,当时机成熟时,我们决定从头开始重新设计该库,审查一百个函数的每一个部分。我决定在开发人员体验和 API 防错之间采取最保守的路线,总是选择后者。我们将使用现有标准,尽可能替换熟悉的 JS 模式。
在过去,JavaScript 疲劳是一个大话题。新的库版本往往互不兼容,每隔几个月就会发布一次,这使得用户不得不无休止地重构他们的工作,以保持他们的依赖关系正常工作。由于我们必须做出重大的突破性更改,因此我们将几个主要版本的更改打包到一个版本中。
当然,这一切都适得其反。一切都始于漫长的开发周期和无休止的“是否有更新”、“ETA 是什么”(以及其他不太礼貌的评论)。许多破坏性的改动,尤其是那些为了追求“最佳”而降低开发者体验的改动,引起了大量(合理的)批评。另一个副作用是,由于设置了许多防护栏,库的构建规模增大了。
虽然这是值得的,因为最终我们得到了高度一致的 API,在 v3 中,我决定退后一步,使 API 更适合开发人员。
此外,虽然有很多改进库的想法需要进行突破性更改,但我决定专注于少数几个,并采取更小的迭代步骤。
而且,从我宣布迁移到 TypeScript 到现在,已经过去了三年。
V4及以上版本
为了保持相关性,date-fns 必须进化并适应不断变化的 JavaScript 世界。
虽然现在开始使用 Temporal 还为时过早,但现在是时候完全接受 Intl 了。
format
仍然是 date-fns 中最流行但最重要的函数,大多数开发人员不需要它。通常,人们使用它来格式化 ISO 日期,而不是使用内置函数:
new Date().toISOString().slice(0, 10);
//=> '2023-12-18'
其余的可以用 Intl
解决问题。
因此,我对 v4 的目标是将 I18n 提取到单独的包(如 @date-fns/es
和 @date-fns/de
),并用 Intl 替代品 intlFormat
、intlFormatDistance
替换 format
、formatDistance
等。
我希望这能让人们迁移到更轻量级的函数,并显著减少构建和包的大小。