babel+webpackな環境でESM(import構文)とCJS(module.exports構文)を混在させると『Cannot assign to read only property ‘exports’ of object ‘#<Object>’』と怒られる

前提

  • babel6以降
    • babel-plugin-transform-runtime をつかってポリフィルしている
  • webpack2以降

自分がこういう構成なので…ほかにもなにか条件下あるかもしれない

エラー内容

Cannot assign to read only property ‘exports’ of object ‘#<Object>’

webpackのコンパイルはちゃんと通って、ファイルも生成される。されるのだけど、そのJSファイルをブラウザで実行するとうまく動かなくて、Chromeのdevツールとかでコンソールエラーを見ると上記のようなエラーメッセージが出ている。

状況下

babel module(策定されたESMっぽいけどESMではないbabel独自のimport/export構文)を使ってモジュールを用いている状況下で、CJS(CommonJS)形式のexports構文を使うと、いわゆるmixed moduleな状態ということになって解釈できなくなるっぽい。

// foo.js
const sample = (num = 1) => {
  const a = 1
  return a + num
}

module.exports = sample


// script.js
import foo from 'foo'

foo()

みたいな状況。
この記事はちょっと思い出しながら書いているので、上記サンプルコードではPromiseオブジェクトを返すような関数ではないけど、もしかするとこの記事のエラーは『Promiseオブジェクトを返すモジュール』のときだけ起こるのだったかもしれない。ちょっと記憶が曖昧。

解決方法

ともあれこのエラーの解決方法としては

  • CJS形式のモジュールを使わない(export構文でモジュール化する)
  • transform-es2015-modules-commonjs プラグインを使う

どちらかがある。

www.npmjs.com

こいつを使うと、どうやらすべてをCJS形式にして解釈するような挙動になる?みたいで、うまくいく。
ただtransform-es2015-modules-commonjsを使うとESM形式もCJS形式に変換されるっぽいのでwebpackのtree-shakingが効かなくなる。一長一短かもしれない。tree-shakingにはESM形式である必要がある。

issue情報

実は今件、webpackのリポジトリにissueが上がっている。webpack2からmodule.exportsはRead-onlyプロパティになったらしく、それが原因っぽい。がメインコントリビューターの人がバッサリ「importmodule.exportsの混在はNG」と言ってるのでそういうものかもしれない。

github.com

結局のところ

今件はこういうことかもしれない.
以下は想像で書いている&過去にメモ書きしたやつなので解釈が大きく間違っているかもしれない…が一応書いておく。

webpackでTree-shakingするようにconfigを書いている

  • Tree-shakingのためにbabelrcに module: false 記述している
    • import/export構文がbebelでトランスパイル対象にされない
  • babelが解釈しなくてもwebpack2はimport/exportの解釈が可能になっているので module: false してるとモジュール解釈はwebpackが行う
    • でもwebpackで importmodule.exports が混在しているプロジェクトはそもそも取り扱えない
      • require/module.exportsimport/require/exportimport/export のどれかの組み合わせのファイルに変換しないと解釈できない
  • module.exportsするオブジェクトでpromise使ってるとtransform-runtimeの対象外になってしまう
    • transform-es2015-modules-commonjs を使うと importを全部require形式に変換してCJSの世界 にしてくれる
      • 全てがなんとかなり解釈できるようになる = しかしESM構文(import/export構文)じゃないのでTree-shakingが効かなくなる

なんていうか疲れますね。

babel moduleの世界にCJS moduleを突っ込もうとしているのがそもそも悪いのかもしれない、という気持ちがある。

ESM策定されたのでCommonJSはCommonJS形式で持つ、babel moduleは捨てて来たる.mjsに備えるというときが将来来そうなのだけど、こんなに疲れるのは困る、嫌すぎる。その頃にはwebpackもbabelもそもそもないかもしれないけど、それにしても既に現状のこれら構成が負債になりつつある気がする。

こちらからは以上です。