Node.jsのrequireとかexportsって?
はじめに
これは最近Node.jsを使い始めた筆者がつまづいた箇所の話です。
動作環境
| 使ったもの | バージョン |
|---|---|
| macOS | Ventura 13.3.1 |
| Visual Studio Code | 1.77.2 |
| npm | 9.5.0 |
| Node.js | v18.14.2 |
経緯
ぼく「Node.jsで遊んでたら1ファイルのコード量が大きくなってしまった」
ぼく「流石に読みづらすぎるから関数を他ファイルで管理するかあ」
ぼく「なんか他のファイルを読み込む際にはrequire?ってのを使うらしい」
const sayHello = () => {
return 'Hello!!';
};
const func = require('./functions');
console.log(func.sayHello());
TypeError: func.sayHello is not a function
ぼく「ファッ!?」
ぼく「なんでエラー起きてんねん調べたろ」
ぼく「....なんか調べたらモジュール?export?とか出てきた。一体なんなんだこれは...」
ぼく「俺は他ファイルの関数を使いたいだけなのに...」
モジュール
Node.jsで他ファイルの関数を使うやり方を説明する前に、Node.jsにおける「モジュール」の概念を軽く解説します。
機能ごとにjsファイルを分けて管理したいのが今回のケースです。
メインとなるファイルから各ファイルで必要に応じて関数や変数を読み込んで使用するイメージですね。機能ごとに分割されたjsファイルが「モジュール」になります。
今回の場合、図のようにindexモジュールと、sayHello()関数を持つfunctionsモジュールが存在するイメージです。

Node.jsではCommonJSモジュールというJavaScriptのモジュール化の仕組みが使われています。
そのCommonJSフォーマットによって、Node.jsではモジュールとその依存ファイルの定義にrequireやexportsを用います。
require
requireは、モジュールをやファイルをインポートする関数になります。他ファイルのモジュールやnode_moduleディレクトリにあるモジュール、ビルトインモジュールを読み込みたい場合に使用します。
今回のケースでは、同一ディレクトリ配下のfunctions.jsを読み込みたいので、以下のようにindex.jsファイルの最初に記述します。
const func = require('./functions');
図で表すと、以下のようなイメージです。

exports
Node.jsが使用しているCommonJSでは、各モジュールが独立しているため、他のモジュールからインポートされる前に、使用したい関数などを明示的にエキスポートする必要があります。モジュール内の関数・変数・オブジェクトを他のモジュールでエキスポートする際に使う関数がexportsになります。
今回はfunctionsモジュール内の関数をエキスポートします。
const sayHello = () => {
return 'Hello!!';
};
exports.sayHello = sayHello;
こんな感じでまずはexportsのプロパティであるsayHelloに定義したsayHello関数を代入します。

図のように、使用する関数や変数をexportsによって明示的に宣言することによって、他モジュールでも使用することができるようになります。
ではでは早速index.js内でfunctionsモジュール内の関数sayHelloを使ってみましょう!
// functions.jsを、変数funcにインポート。
const func = require('./functions');
// funcのsayHello関数を使用
console.log(func.sayHello());
Hello!!
表示されました!
最初自分はエキスポートをせずに、ただfunctionsモジュールを読み込んだだけだったからエラーが起きてたってワケです。
また、関数の定義と同時にexportsプロパティに関数を代入することができます。
下記はsayGoodBy関数の定義と同時にexportsのプロパティとして設定した例です。
exports.sayGoodBy = () => {
return 'Good By!!';
};
おわりに
普段Pythonを使っていて、importするだけで他Pythonモジュールの関数を使えることに慣れてるのでうっかりハマっちゃいました。また今回学習した所感ですが、インポートだけでなく、エキスポートを明示することでコードの保守性や可読性はかなり上がりそうだと感じました。
参考記事
Discussion