16 Module - iruma-tea/dokushujs GitHub Wiki
大規模なシステムを作るときには、意味のある機能のまとまりをモジュールという単位で作成する。
これによって、モジュールを入れ替えたり、他のシステムを作成するときにもモジュールを使いまわすことができるようになる。
一般的なモジュールは、以下の要件を満たす
モジュールは、意味のあるコードのまとまりとして作成する。
これにより、コードの可読性と保守性が上がる。
モジュールを外部から使うためのインタフェースが明確に決まっていれば、開発者がインタフェースを正しく使うことで、
そのモジュールの中身を知らなくても利用できる。
モジュールをインタフェース経由でのみ使用できるようにすることで、モジュール同士の結び付きを弱め、
モジュールの追加や交換を容易にできる。
このように、機能同士の関係性が希薄であることを疎結合と呼ぶ。
システム開発において機能同士の疎結合に保つことは、コードの可読性、保守性、再利用性を向上させるために極めて重要です。
モジュールから特定の機能(関数やクラス)のみインタフェースとして露出(エクスポート)することで、他のモジュールから
機能のみ読込(インポート)を行って使えるようにする。
現代のJavaScripで主に利用されるモジュールシステムにはES Modules(ESM) と **CommonJS(CJS)**がある。
ES ModulesはES6で追加されたESMAScriptの仕様に基づくモジュール管理機能です。ES Modulesは、ブラウザとNode.jsのどちらでも使用できる。
一方、CommonJSは、Node.jsのみで使用可能なモジュール管理機能。
ブラウザでES Moduleの機能を使うには、scriptタグに**type="module"**属性を付与する。
[構文] ブラウザでES Modules機能を有効化する。
・ HTMLファイル内に記述するとき
<script type="module">
/* ES Modulesが有効化されています。 */
<script>
・ JavaScriptファイルを読み込む場合
<script type="module" src="JavaScriptファイルまでのパス">
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 normalVarialbe = "モジュール内で宣言した変数";
function normalFunction() { }
class NormalClass { }
export { normalVariable, normalFunction, NormalClass };
// 別名を指定してエクスポート(as で別名を付ける)
export {
normalVariable as publicVariable,
normalFunction as publicFunction,
NormalClass as PublicClass,
};
// 分割代入しながらエクスポート(オブジェクトから分割代入でエクスポート)
const normalObject = {
normalVal: normalVariable,
normalFn: normalFunction,
NormalCls: NormalClass
}
export const { normalVal, normalFn, NormalCls } = normalObject;
このように、名前付きエクスポートでは、変数名や関数名がそのまま、外部からアクセスするときの識別子になる。
識別子が重複するとエラーになるため注意してください。
一方、モジュールには1つだけデフォルトエクスポートが定義できる。
デフォルトエクスポートで露出した機能は、モジュールの利用者がインポートする際に任意の名前を付けることができる。
そのため、デフォルトエクスポートで露出する関数やクラス名は無視される。
デフォルトエクスポートを使うには、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 };
コード量が大きくなってくると、1つの機能を複数ファイルに細分化して管理する。
そのようなときに使うのがモジュール集約のためのエクスポートです。
例えば、sub.module.jsの機能を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)を使って外部に露出した機能を使うには、まずインポートを行う。
インポートの方法には、静的インポート(Static Imports)と動的インポート(Dynamic Imports)
2種類の方法がある。
静的インポートの場合には、コードを読み込んだ時点で、インポート先のモジュールはすでに決定される。
一方、動的インポートの場合は、コードを実行する段階で初めて、どのモジュールを読み込むかが決定される。
JavaScriptでインポートと言った場合には、基本的に静的インポートのことを指す。
静的インポートでは、コードが読み込まれた時点でインポート先のモジュールのトップレベルのコードの実行まで行う。
・ インポート・静的インポート
// 名前つきエクスポートをインポート
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 "/paath/to/module.js";
// デフォルトエクスポート(defaultExport)と名前付きエクスポートをオブジェクト(moduleObject)のプロパティとしてそれぞれインポート
import defaultExport, * as moduleObject from "/path/to/module.js";
// インポートなしにモジュール(module.js)内のコードを一度だけ実行
import "/path/to/module.js";
動的インポートは、ES2020で追加された比較的新しい機能です。
ダイナミックインポートとも呼ばれる。
動的インポートでは、import関数を使って、必要なときに他のモジュールを読み込むことができる。
[構文]importの記法
const moduleProm = import("/path/to/module.js");
moduleProm: 対象のファイルでエクスポートされた関数や変数を保持するオブジェクト(モジュールオブジェクト)が
Promiseでラップされたものが渡される。
・ モジュールの動的インポート
<script>
// Promiseオブジェクトでラップされたモジュールが渡される。
let promise = import("./module.js");
promise.then(moduleObject => {
moduleObject.exportredFn();
> exportedFnが呼ばれました。
});
// await/asyncを使用することも可能
async function asyncFunction() {
let { exportedFn } = await import("./module.js");
exportedFn();
> exportedFnが呼ばれました。
}
asyncFunction();
</script>
(module.js)
export function exportedFunction() {
console.log("exportedFnが呼ばれました。");
}
モジュールを読み込んだとき、読み先のモジュールのトップレベルコードは、
初回読み込み時のみ実行される。
<script type="module">と記述した場合、その中(タグ内やJavaScriptファイル内)で記述されたコードは、
DOMツリーの構築後に実行される。
ES Moduleを有効にした場合、Strictモードが自動的に有効になる。
Strictモードを有効にするには、ファイルまたは、関数の先頭で"use strict";を記述する。
[構文] Strictモードの有効化
・ ファイルの先頭
"use strict";
・ 関数の先頭
function strictFunction() {
"use strict"; // 関数の先頭
}
・ ES Moduleが有効なとき
<script type="module">
/* Strictモードは有効です。 */
</script>
Strictモードが有効になると、コードの実行に次のような影響がでる。
- 将来的に使いそうなキーワードが予約後として確保される。
- 宣言されていない変数への代入をエラーとする。
- 書き込み不可のプロパティを変更しようとするとエラーが発生する。
- 関数宣言にブロックスコープが適用される。
- thisはプリミティブ値も許容するようになる。
CommonJS は、Node.jsで独自に採用されているモジュール管理システム。
現在、Node.jsも、ES Moduleを使ったモジュール管理の仕組みに移行しているため、
新しくコードを記述するさいは、ES Moduleを積極的に採用すると良い。
CommonJSでのエクスポートでは、exportsオブジェクト、またはmoduleオブジェクトのexportsプロパティに
対してエクスポートしたいプロパティを追加します。
[構文] Common.jsを使ったエクスポート
・ exportsに追加
exports exportedFunction = function() {
/** 関数をエクスポート */
}
・ module.exportsに追加
module.exports.exportedVariable = "これはエクスポートされます。";
exprtsはあくまでmodule.exportsを省略した形で記述できるようにしたものであり、最終的にmodule.exportsが参照している先のオブジェクトがエクスポート対象のモジュール、使用可能な変数や関数が格納されているオブジェクトとみなされる。
そのため、オブジェクトリテラル{}を使ってエクスポートしたい関数や変数を定義したい場合には、module.exportsとexportsの参照先のオブジェクトを一致させるように注意。
CommonJSでのインポートは、エクスポートに比べてシンプル。requireという関数を使って、読込対象モジュールを指定する。
[構文] CommonJSを使ったインポート
const moduleObject = require("/path/to/module.js");
moduleObject: 読み込んだファイル(module.js)のmodule.exportsが保持するオブジェクトが渡される。
(export-module.js)
module.exports.exportedFunction = function() {
console.log("機能の読み込み確認");
};
(import-module.js)
const moduleObject = require("./export-module.js");
moduleObject.exportedFunction();