トップへ戻る
BLOGS

JavaScript モジュールの基礎

JavaScript モジュールの基礎

モジュールとは

JavaScriptでは意味のある一つの機能のまとまりをモジュールと言う単位で作成し、必要に応じて読み込んで使用したり、することでメンテナンス性やコードの保守性などを保っています

一般的にモジュールはインターフェイス(機能の呼び出し方の定義)が明確であり、開発者はこのインターフェイスを上手く使用するだけで開発が捗るようになっています
モジュールのインターフェイスはシステム開発の場合、主にクラスや関数を表します、モジュールから特定の機能(クラスや関数)のみをインターフェイスとして露出(エクスポート)することで、他のモジュールから機能の読み込み(インポート)を行って使えるようになります

ブラウザのESモジュールの有効化

ES ModulesはNode.jsとブラウザのどちらでも使用可能でここではブラウザ版の扱い方についてみていきます

ブラウザでES Modulesの機能を使うにはscriptタグにtype="module"属性を付与します

<script type="module">
 //ES Modulesが有効化されています
</script>
もしくは
<script type="module" src="JSファイルまでのパス">

これによってES Modulesの仕様に沿ってコードが実行されるようになります。

ES Modulesの仕様は通常のJavaScriptコード以下のような違いがあります

  • ES Modulesの仕様に沿って記述された他のJavaScriptファイルの関数やクラスをインポート、エクスポート出来るようになる
  • モジュールのトップレベルのコードのスコープがモジュール内に限定される
  • モジュールのトップレベルのコードは一度だけ実行される
  • スクリプト(JavaScriptコード)の実行タイミングがDOMツリーの構築後になる
  • Strictモードが自動的に有効になる

エクスポートの方法

ES Modulesのエクスポート、つまり外部に機能を露出する方法は以下の3つ方法があるようです

  • 名前付きエクスポート
  • デフォルトエクスポート
  • モジュールの集約

一つづつ見ていきましょう

名前付きエクスポート

名前付きエクスポートでは、基本的に変数名や関数名がそのまま、外部から呼び出すときの識別子となります。
名前の重複を避ける場合や外部から呼び出すときの識別子を変更したい場合には、asキーワードを使って別名をつけることも出来ます。

// 変数のエクスポート
export let variable = "変数宣言の前にexportキーワードを付けます。";
export const constant = "定数もエクスポート可能です。";
// 複数の変数を一括でエクスポート
export let val1 = "値1",
  val2 = "値2";
// 関数、ジェネレータ関数、クラスのエクスポート
export function exportedFunction() {}
export function* exportedGenerator() {}
export class ExportedClass {}
// モジュール内で定義した変数、関数、クラスのエクスポート
let normalVariable = "モジュール内で宣言した変数";
function normalFunction() {}
class NormalClass {}
export { normalVariable, normalFunction, NormalClass };
// 別名を指定してエクスポート(asで別名を付ける)
export {
  normalVariable as publicVariable,
  normalFunction as publicVariable,
  NormalClass as PublicVariable,
};
// 分割代入しながらエクスポート(オブジェクトから分割代入でエクスポート)
const normalObject = {
  normalVal: normalVariable,
  normalFn: normalFunction,
  NormalCls: NormalClass,
};
export const { normalVal, normalFn, NormalCls } = normalObject;

このように名前付きエクスポートでは、変数名や関数名、クラス名がそのまま、外部からアクセスする時の識別子になりますが、識別子が重複するとエラーになるため、注意が必要です。

デフォルトエクスポート

モジュールには一つだけデフォルトエクスポートが定義できます。
デフォルトエクスポートで露出した機能は、モジュールの利用者がインポートする際に任意の名前を付けることが出来ます
そのため、デフォルトエクスポートで露出する関数やクラス名は無視されることに注意してください

デフォルトエクスポートを使用するためにはexport defaultを先頭に付けます

// 無名関数をデフォルトエクスポート
export default function () { }

// アロー関数をデフォルトエクスポート
export default () => { }

// 名前を付けてもimportの際には任意の名前で使うことが可能
export default function exportedFunction() { }

// クラスのエクスポート
export default class { }

// 名前を付けてもimportの際には任意の名前で使うことが可能
export default class ExportedClass { }

// defaultという名前を付けるとデフォルトエクスポートとしてエクスポートされる
function normalFunction() { }
export { normalFunction as default };

上記は一例ですが、デフォルトエクスポート出来るのはモジュール単位で一つのみなので注意してください

モジュールの集約

コード量が多くなってくると、一つの機能を複数ファイルに細分化して管理します。そのような時に使うのがモジュールの集約のためのエクスポートです’他のファイルで実装した関数やクラスを一つのファイルから呼び出せるようにする(集約する)ことで利用者に利便性を提供できます。

例えばsub.module.jsの機能をparent.module.jsというフィアルに集約する場合には以下のようにします

sub.module.js

export const subVariable = "値";
export function subFunction() {
  console.log("subFunction is called");
}
export class SubClass {
  constructor() {
    console.log("SubClass is newed");
  }
}
export default () => console.log("default is called");

parent.module.js

// デフォルトエクスポートを含むすべての機能をエクスポート
export * from "./sub.module.js";
// デフォルトエクスポートを含むすべての機能をsubObjectオブジェクトのプロパティとしてエクスポート
export * as subObject from "./sub.module.js";
// 特定の機能だけエクスポート
export { subVariable, subFunction, SubClass } from "./sub.module.js";
// 別名を付けてエクスポート
export {
  subVariable as exportedVariable,
  subFunction as exportedFunction,
  SubClass as ExportedClass,
} from "./sub.module.js";
// デフォルトエクスポート
export { default } from "./sub.module.js";

インポートの方法

次にモジュールのインポートの方法を見ていきます

exportを使って外部に露出した機能を使うにはまずインポートを行います。
インポートの方法には静的インポートと動的インポートの2種類の方法があります

静的インポートの場合はコードを読み込んだ時点でインポート先のモジュールはすでに決定されています。一方動的インポートの場合にはコードを実行する段階で初めてどのモジュールを読み込むかが決定されます

静的インポート

JavaScriptでインポートと言ったら基本的に静的インポートのことを指します指します
静的インポートではコードが読み込まれた時点でインポート先のモジュールのトップレベルのコードの実行までを行います

静的インポートの解説のサンプルコードですが、読み込み対象のファイルを"/path/to/module.js"としていますここ部分は適所相対パス、もしくは絶対パスにて指定してください

// 名前付きエクスポートをインポート
import {
  exportedVariable,
  exportedFunction,
  ExportedClass,
} from "/path/to/module.js";

// 別名を付けてインポート
import { exportedName as importedName } from "/path/to/module.js";

// デフォルトエクスポートと名前付きエクスポートをオブジェクト(moduleObject)のプロパティとしてインポート
// デフォルトエクスポートはdefaultプロパティに格納される
import * as moduleObject from "/path/to/module.js";

// デフォルトエクスポート(defaultExport)を読み込む
import defaultExport from "/path/to/module.js";

// デフォルトエクスポート(defaultExport)と名前付きエクスポート(namedExport1, namedExport2)をそれぞれインポート
import defaultExport, { namedExport1, namedExport2 } from "/path/to/module.js";

// デフォルトエクスポート(defaultExport)と名前付きエクスポートをオブジェクト(moduleObject)のプロパティとしてそれぞれインポート
import defaultExport, * as moduleObject from "/path/to/module.js";

// インポートなしにモジュール(module.js)内のコードを一度だけ実行
import "/path/to/module.js";

静的インポートには以下2つの特徴があります

formに続くモジュール名に変数は使用不可

静的インポートではあらかじめ読み込むモジュールが決まっている必要があるため、変数に格納した後インポート定義しても、構文エラーになってしまいます

読み込み時点でのモジュールのトップレベルのコードが実行される

静的インポートの場合はモジュールの読み込み時点でのトップレベルのコードまでが行われます。
また、静的インポートによるモジュールの読み込みは静的インポート以外のコードの実行前に行われます

静的インポートを使った場合には、ブラウザがモジュールを読み込んだ時点でモジュールのトップレベルコードの実行までを行う分、画面の表示速度が遅くなる傾向にあります。
そうした場合は動的インポートの導入を検討します

動的インポート

動的インポートは比較的新しい、ダイナミックインポートとも呼ばれます。動的インポートではimport関数を使って必要な時に他のモジュールを読み込む事ができます
これにより、画面の初期表示に必要のない機能などを動的インポートとして読み込むことで、画面の初期表示にかかる時間を短縮できます

動的インポートの記法

const moduleProm = import(“/path/to/module.js”);
moduleProm: 対象ファイルでエクスポートされた関数や変数を保持するオブジェクト(モジュールオブジェクト)がPromiseでラップされたものが渡されます

importを実行するとエクスポートされた機能を保持するオブジェクトがPromiseでラップされた状態、つまり非同期で返されるため、thenメソッドやasync/awaitなどが使用できます
そのため、HTMLのパース処理やレンダリングを邪魔しないコードが書きやすいです。

関数として実行されるため、例えばイベントハンドラ内で importされている場合は、そのページを表示した時には、まだモジュールのファイルは取得されません。対象となるイベントが発火したときに初めてモジュールのファイルがリクエストされて取得されます。

module.js

export function exportedFn() {
  console.log("exportedFnが呼ばれました。");
}

dynamic_import.html

// Promiseオブジェクトでラップされたモジュールオブジェクトが渡される。
let promise = import("./module.js");
promise.then((moduleObject) => {
  /* module.jsでexportされた機能がmoduleObjectのプロパティとして使用可能 */
  moduleObject.exportedFn();
});
// await / asyncを使うことも可能
async function asyncFunction() {
  let { exportedFn } = await import("./module.js");
  exportedFn(); // エクスポートされた関数を実行可能
}
asyncFunction();

コメントをお待ちしております

お気軽にコメントをどうぞ。

CAPTCHA