タイトルの通りです、プロダクトのフロントエンドで使っているRecoil
を、0.0.8系をつかっていたのですが、0.0.10にアップデートしたところ、webpack + babelの環境でエラーにつながり、調査しながら対応したので小さなメモです。
環境
"@babel/core": "^7.9.6", "recoil": "^0.0.10",
おこった事
RecoilをimportしていたReactのコンポーネントがエラーになって読みこめなくなってしまいました。
エラーを確認してみるとReferenceError: exports is not defined
というエラーがrecoilを読みこんでいる箇所で発生していました。
原因
Recoil 0.0.9でasync/awaitを利用した機能が入った
コードを探ってみたところ、recoilは0.0.9になったタイミングでasync/awaitを利用した機能が追加されていました。
そのため、babelで処理をするにあたって、babel側から"@babel/runtime/regenerator"
を追加する必要が発生していました。
RecoilはCommonJSでビルドされてしまっている
一方で、Recoilのコード自体は既にCommonJS形式でコンパイルされて配布されているため"@babel/runtime/regenerator"
はrequireで追加する必要があります。
この仕様についてはESMに移行しないのか? というIssueは立っていましたが今のところFacebookの内部のコードをベースとしているため、 そちらがCommonJSを前提としているのでGithub上のコードもCommonJSビルドされているという事でした。 これは、今後ライブラリが安定してきてGithub側のコードを主にしたタイミングでESMにできればとの事でした。
これは、複数の形式でビルドして配布してくれるようにすれば対応できる気がするのでそうなってほしいなという思いがありますが、 実際そのあたりどうなんでしょう..
BabelはコードはESMで書かれている前提になっている
しかし、babelの設定ではデフォルトの設定ではコードはESMのモジュール形式で書かれているとしているため、import文で追加されてしまいます。
そのため、結果的にはimport文が追加されたcommonjsモジュールという形式になってしまいます。
結果として、webpackがコードを生成する時に、importがはいっているESMのように見えるために、exportsがmodule処理時に未定義になってしまいエラーが発生するという事になってしまいます。
対応
BabelのsourceTypeを"unambiguous"
に設定する事にしました。
このように設定しておくとmodule前提ではなくコードをパースしてmoduleなのかそうではないscript形式で書かれているのかをビルド時に判定してくれます。
一方で公式にも書かれていますが、判定は確実な物ではないので誤った判定がされる可能性もあるのでできればプロジェクト内ではEMSで統一したいところです。
追記: 2020/06/28
ESMのモジュールビルドもCommonJSと並行して実施してくれないかどうかPRを投げてみています。 これならば、CommonJSをつかっている側はそのまま継続してつかえるはずなので互換性をこわさなくて済むのでは...