🐡

Javascript にて、 importとexportを一行で書く

2024/02/19に公開

1. 概要

複数のモジュールのインポートだけを定義した中間ファイルを書きたい場面に遭遇しました。

ファイル構成

これに伴い、以下の処理を一行で書く方法を調査しました。

  • 他のファイルにて export されている複数のモジュールをインポートする
  • 上記インポートしたモジュールをそのまま export する

調査結果をまとめます。

2. 結論

util.jsexport している複数モジュールについて、 import_layer.js 内で import した上で export したいとする。

以下のように書く。

import_layer.js
export {
  モジュール1, 
  モジュール2,
  ... 
} from "./util.js";

定義してある全モジュールを import した上で export したい場合、ワイルドカードを使用できる。

import_layer.js
export * from "./util.js";

これにより、 import_layer.js を import することで、 util.js 内で export している全モジュールを import できる。

index.js
import {
  モジュール1,
  モジュール2,
  ...
} from 'import_layer.js'

3. 動作確認

3.1. ファイル構成

.
├── import_layer.js
├── index.js
└── util.js

ファイル構成

import_layer.js 内で、下位コンポーネントである util.jscomponent.js の import & export をまとめる。

メインとなる index.js では、 imoprt_layer.js を 介して、util.jscomponent.js に定義した モジュールにアクセスする。

3.2. サンプルコード

util.js
export const hello = () => {
  console.log("hello!");
};

export const nice = () => {
  console.log("nice!");
};
component.js
export const great = () => {
  console.log("great!")
}
import_layer.js
export * from "./util.js";
export * from "./component.js"
index.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 を書く面倒さ

当初は importexport を二行に分けて書くことを考えていた。

Lambda アプリケーションの依存関係から、 export する際の変数名は変更できない。

そのため、何も考えずにそのまま書くと変数の二重定義となってしまう。

util.js
import { hello } from './great.js';
export const hello = hello; // SyntaxError: Identifier 'hello' has already been declared

as 句を使って別名を付与すれば解決するが、別名をいちいち考えるのが面倒。

util.js
import { hello as hello_module } from './great.js';
export const hello = hello;

4.4. そして本題へ

上記の問題 から、 importexport を一行で書く方法はないか?と試行錯誤した結果、本手法にたどり着いた。

5. 副産物

5.1. まとめて Mock

中間 I/F (本文中の例だと import_layer.js )と同じ要領で、外部 API を呼び出す処理を集約した中間 I/F を作成し、 Lambda アプリケーション側に定義する。

テストの際は、 中間 I/F を まるまる Mock に置き換えると、外部 API を呼び出す処理を一括して Mock 関数に差し替えることができる。

考えた戦略

5.2. ワイルドカード import で編集の手間を減らす

ワイルドカードを使用して一括 import & export した場合について、本文中の例を再掲。

import_layer.js
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