BASEプロダクトチームブログ

ネットショップ作成サービス「BASE ( https://thebase.in )」、ショッピングアプリ「BASE ( https://thebase.in/sp )」のプロダクトチームによるブログです。

BASEの日付ライブラリについて

自己紹介

こんにちは。BASE株式会社のフロントエンドチームの谷口です。
本日は、BASEのフロントエンドで使用している日付ライブラリについてお話しします。

BASEの日付ライブラリについて

BASEでは、frontendという領域が出来始めた当初、最もメジャーな日付ライブラリであるmoment.jsを使用していました。 その後、デザインコンポーネントの開発など、frontend領域が成長していく中で より使い勝手の良い別の日付ライブラリが検討され、date-fnsが採用されました。 現時点で、ほぼ全てのコードがdate-fnsに移行済みです。

date-fnsについて

date-fnsについて少し説明すると、公式にもありますが下記のような特徴が上げられます。

  • moment.jsやday.jsがDateオブジェクトをラップして扱うのに対し、純粋な関数を必要な分だけ読み込んで使用することが出来ます。 こちらのディレクトリを見ると、180以上の機能が用意されており、全て関数をexportしている事がわかります。

  • date-fnsは値を不変に保つことが可能です。 これはmoment.jsと比較すると下記のような違いが見られます。

//moment.jsの場合、addを呼び出す度に元の値が変更され、同じ結果が得られません。
//コンソールに警告こそ表示されますが、この動作が予期せぬバグを生み出す可能性があります。
const today = new Date();

const momentToday = moment(today);
momentToday.add("day", 3);
console.log(momentToday.toDate());
// Sat Nov 07 2020 11:17:47 GMT+0900 (日本標準時)
momentToday.add("day", 3);
console.log(momentToday.toDate());
// Tue Nov 10 2020 11:17:47 GMT+0900 (日本標準時)

//date-fnsの場合、元の値を変更することはありません。

const threeDaysTime = addDays(today, 3);
console.log(threeDaysTime); 
// Sat Nov 07 2020 11:17:47 GMT+0900 (日本標準時)
const sixDaysTime = addDays(threeDaysTime, 3);
console.log(sixDaysTime);
// Tue Nov 10 2020 11:17:47 GMT+0900 (日本標準時)

また他のライブラリと依存しておらず、関数を呼び出す度に新しいDateオブジェクトが返ります。 関数自体にも副作用が無いため、テストツールや他のライブラリに組み込むことも容易です。

  • bundleサイズも使用する言語やwebpackの設定により異なりますが、比較的軽量です。

f:id:buttaoth:20201104161629p:plain

出典:https://bundlephobia.com/

  • TreeShakingをサポートしています。

  • TypeScriptとFlowどちらにも対応しています。

  • ドキュメントも充実しており、サンプルコードも豊富なので、使い方に困るということも少ないでしょう。

また近頃、moment.jsがメンテナンスモードになるという主旨の記事が公開されました。 その移行先候補の1つとしてdate-fnsも記載されており知名度も高いライブラリです。

f:id:buttaoth:20201104161738p:plain 出典: https://www.npmtrends.com/

date-fnsのバージョンアップ

上記の経緯で採用されたdate-fnsですが、BASEでは、実装当初のv1.30.1 からアップデートされていなかったため、今年の3月にv2.0.0にメジャーアップデートしました。 v2.0.0の開発にはおよそ2年の歳月がかけられており、 中には破壊的変更も含まれていたため、いくつか修正する必要がありました。

v1.30.1 -> v2.0.0の主な破壊的変更点

下記が関数名の変更などを除いた主な変更箇所です。 (サンプルコードは公式より抜粋しています)

  • 主要な関数は文字列を許可していたが、日付のみ指定になった。 文字列を使用したい場合はparseISOで変換して使用する必要があります。
    // Before v2.0.0
    addDays('2016-01-01', 1)

    // v2.0.0 onward
    addDays(parseISO('2016-01-01'), 1)
  • Unicodeのフォーマットが変わりました。 以前まではmoment.jsの仕様を模倣していた経緯がありましたが、その他の多くの言語に習って、より普遍的な物に変更されました。 後方互換用のオプションが用意されていますが、今後このオプションが削除される可能性も高いので、可能な限り変更した方が良いでしょう。
    // Before v2.0.0
    format(new Date(), 'YYYY-MM-DD')
    //=> 2018-10-283

    // v2.0.0 onward
    format(new Date(), 'yyyy-MM-dd')
    //=> 2018-10-10
     format(Date.now(), 'YY-MM-dd', { awareOfUnicodeTokens: true })
     //=> '86-04-04'
  • format関数は明示的にフォーマット形式を指定する必要があります。
    // Before v2.0.0
    format(new Date(2016, 0, 1))

    // v2.0.0 onward
    format(new Date(2016, 0, 1), "yyyy-MM-dd'T'HH:mm:ss.SSSxxx")
  • 日数の変換はparse関数に全て委任していましたが、parseは引数を必須とし、それぞれの用途に合わせた関数が用意されました。
    // Before v2.0.0
    parse('2016-01-01')
    parse(1547005581366)
    parse(new Date()) // Clone the date

    // v2.0.0 onward
    parse('2016-01-01', 'yyyy-MM-dd', new Date())
    parseISO('2016-01-01')
    toDate(1547005581366)
    toDate(new Date()) // Clone the date

最後に

日付という性質上、変更箇所は多岐に渡りましたが、破壊的変更があったにも関わらず アップデートの難度はそこまで難しいものではありませんでした。 これは、date-fnsがDateを引数として渡すだけ、というシンプルな使い方であるため、修正すべきコードも比較的読みやすかったおかげだと感じています。

moment.jsがメンテナンスモードになる中、移行候補の1つとしてdate-fnsを検討してみていかがでしょうか。