概要

恐れながら言わせてもらうけれど、 CommonJS のエクスポートの理解に苦労したのはひとえにネットの記事の書き方が悪いせいだな。
いやその、ネットの記事にはいつも大変お世話になっているよ? ただし CommonJS のエクスポートの説明についてはヒドいな。ぼくの説明が世界でいちばん分かりやすいと思うね!
“CommonJS の2通りのエクスポート” っつーのは
// まずは module.exports。
// よくある説明: これだとインポート (require) する側が
//             自由に名前を変更できてしまう。
module.exports = (i) => i + 1;
// よくある説明: 自由な名前変更を避けたいときはこの exports を使う。
exports.increment = (i) => i + 1;
この、 "module.exports ではなく exports を使う" みたいな言い方よ。 exports は module.exports のショートカットなので同じものなのに、まるで別のものであるかのような誤解を招くのだよ。
緑さんの理解では CommonJS のエクスポートはこうだ
- CommonJS の実行環境では、ファイルひとつにつき “モジュールスコープ変数” たちが自動でつくられる。 Python でいうところのビルトイン変数のことだ。
- その一覧はここ (https://nodejs.org/api/modules.html#the-module-scope) にあるぜ。
 
 - この件に関わるモジュールスコープ変数は以下の3つ。
module: これはオブジェクトで、中にexportsというプロパティ (中身は空っぽのオブジェクト) を持つ。exports: これはmodule.exports↑への参照。require: これはまあ基本的には関数。別ファイルのmodule.exportsを参照する。
 

“CommonJS の実行環境では、 require() を使って別のファイルの module.exports へアクセスできる”。 ぼくの理解では、 CommonJS のエクスポート、インポートの仕組みはこれがすべてだ。2種類の方法なんて、ない。
以下、実験ノート
以上の理解に至った実験ノートを残しておこう。
node -v
# v20.3.0
# これは Python でいうところの python -c みたいなもの。
node --eval 'console.info({ module })'
# {
#   module: Module {
#     id: '[eval]',
#     path: '.',
#     exports: {},
#     filename: '/path/to/here/[eval]',
#     loaded: false,
#     children: [],
#     paths: [
#       ...
#       '/node_modules'
#     ]
#   }
# }
このとおり、 Node.js (CommonJS) の実行環境では、モジュールスコープ変数の module が自動で定義されている。まあ Python でいうところの __name__ みたいなものだよ。
さて、事をややこしくしているのは exports というモジュールスコープ変数の存在だよな。ただ、コイツは module.exports への参照でしかない。それを見てみようぜ。
node --eval 'module.exports.foo = 1; exports.bar = 2; console.info({ exports })'
# { exports: { foo: 1, bar: 2 } }
ほらね。個人的には、ぜーんぶ module.exports を使うことにして、 exports は封印したほうがいいと思うよ。
そして、 require() は別ファイルの module.exports の内容をそのまま返してくれる。
echo 'exports.increment = (i) => i + 1; module.exports.sunday = "Sunday";' > foo.js
node --eval 'console.info(require("./foo.js"))'
# { increment: [Function (anonymous)], sunday: 'Sunday' }
そういうわけで、 CommonJS には2種類のエクスポート方法があるというのは世迷い言だ。デフォで用意されている require 関数が、別ファイルの中の、これまたデフォで用意されている module オブジェクトの module.exports プロパティにアクセスすることができる。これがすべて。