Javascript にて、 importとexportを一行で書く
1. 概要
複数のモジュールのインポートだけを定義した中間ファイルを書きたい場面に遭遇しました。
これに伴い、以下の処理を一行で書く方法を調査しました。
- 他のファイルにて
export
されている複数のモジュールをインポートする - 上記インポートしたモジュールをそのまま
export
する
調査結果をまとめます。
2. 結論
util.js
で export
している複数モジュールについて、 import_layer.js
内で import
した上で export
したいとする。
以下のように書く。
export {
モジュール1,
モジュール2,
...
} from "./util.js";
定義してある全モジュールを import
した上で export
したい場合、ワイルドカードを使用できる。
export * from "./util.js";
これにより、 import_layer.js
を import することで、 util.js
内で export
している全モジュールを import
できる。
import {
モジュール1,
モジュール2,
...
} from 'import_layer.js'
3. 動作確認
3.1. ファイル構成
.
├── import_layer.js
├── index.js
└── util.js
import_layer.js
内で、下位コンポーネントである util.js
と component.js
の import & export をまとめる。
メインとなる index.js
では、 imoprt_layer.js
を 介して、util.js
と component.js
に定義した モジュールにアクセスする。
3.2. サンプルコード
export const hello = () => {
console.log("hello!");
};
export const nice = () => {
console.log("nice!");
};
export const great = () => {
console.log("great!")
}
export * from "./util.js";
export * from "./component.js"
import { hello, nice, great } from "./import_layer.js";
hello();
nice();
great();
3.3. 確認
$ node index.js
hello!
nice!
great!
4. 調査するに至った背景
本記事に記載した手法を調査するに至った背景について、補足する。
4.1. 従来の定義
AWS Lambda 関数について、以下の運用をしていた。
- メインのコードを Lambda に書く
- 複数の Lambda 関数で共有したいユーティリティを LambdaLayer に定義して使いまわす
この「複数の Lambda 関数で共有したいユーティリティ」を、一つのソースファイルに全部定義していた。
開発が進むにつれて、 util.js
が肥大化し、見通しが悪くなった。
そこから、関連する処理でソースファイル毎に分割しようという運びとなった。
4.2. 変更が困難に
util.js
は、複数の Lambda アプリケーションから参照されている状態だった。
ファイル分割の過程で util.js
を削除したり、 export
するモジュール名に手を加えると、既存の Lambda アプリケーションが動作しなくなる可能性が高い。
そのため、以下の戦略を取った。
-
util.js
は Lambda アプリケーション側からのアクセスを一手に引き受ける I/F として存続させる - 分割したソースファイルは
util.js
でインポートした上で、Lambda アプリケーションからアクセスできるようexport
も行う
実際にコードを書いてみると、一つの面倒な問題に直面した。
4.3. 二行に分けて import と export を書く面倒さ
当初は import
と export
を二行に分けて書くことを考えていた。
Lambda アプリケーションの依存関係から、 export
する際の変数名は変更できない。
そのため、何も考えずにそのまま書くと変数の二重定義となってしまう。
import { hello } from './great.js';
export const hello = hello; // SyntaxError: Identifier 'hello' has already been declared
as
句を使って別名を付与すれば解決するが、別名をいちいち考えるのが面倒。
import { hello as hello_module } from './great.js';
export const hello = hello;
4.4. そして本題へ
上記の問題 から、 import
と export
を一行で書く方法はないか?と試行錯誤した結果、本手法にたどり着いた。
5. 副産物
5.1. まとめて Mock
中間 I/F (本文中の例だと import_layer.js
)と同じ要領で、外部 API を呼び出す処理を集約した中間 I/F を作成し、 Lambda アプリケーション側に定義する。
テストの際は、 中間 I/F を まるまる Mock に置き換えると、外部 API を呼び出す処理を一括して Mock 関数に差し替えることができる。
5.2. ワイルドカード import で編集の手間を減らす
ワイルドカードを使用して一括 import & export した場合について、本文中の例を再掲。
export * from "./util.js";
この時、 util.js
に、新たに export
モジュールを追加した場合、 import_layer.js
に手を加える必要なく、クライアント側(本文中の例だと index.js
)から インポートできる。
言い換えると、util.js
を編集するだけで、 クライアント側から使えるようになる。
6. 最後に
このアプローチが正しいのか、またはもっと良い方法があるかは不明。
◆2024/03/01 追記
本手法には、 バレルファイル という名称が存在した。
バレルファイルというアプローチが正しいかについて、以下のWebページに、バレルファイルのメリット/デメリットが紹介されている。
また、上記記事にも記載されているが、一部のフレームワークでは、バレルファイルがエラーの原因となり得る。
例)nestjs 公式ドキュメントより、warning部分を引用
A circular dependency might also be caused when using "barrel files"/index.ts files to group imports.
Barrel files should be omitted when it comes to module/provider classes.
For example, barrel files should not be used when importing files within the same directory as the barrel file, i.e. cats/cats.controller should not import cats to import the cats/cats.service file.
For more details please also see this github issue.
使用の際には注意が必要。
なお、なぜ "バレル(barrel)" という名称なのかは終ぞ分からなかった。
Discussion