JavaScript ES6モジュールシステムの基本的な使い方:初心者向け完全ガイド
このチュートリアルでは、JavaScriptのES6(ES2015)モジュールを作成し、使用する方法を解説します。
JavaScriptモジュールを使い始めるために必要な全ての情報を提供します。
モジュールシステムの歴史
JavaScriptには長い間、モジュールシステムがありませんでした。
以前のアプリケーションは単純で小規模であり、コードも単純でした。
しかし、アプリケーションが改善されて肥大化するにつれて、コードも大きくなり、管理するために複数のファイルに分割する必要性が生じました。
そこで、モジュールの概念が生まれました。
この概念は、多くのプログラミング言語では基本的な部分ですが、JavaScriptには存在しませんでした。
JavaScriptで書かれたものはデフォルトでグローバルであり、言語の初期段階では大きな問題ではありませんでしたが、開発者がJavaScriptで本格的なアプリケーションを書き始めると、実際の問題に直面しました。
その結果、いくつかのサードパーティソリューションが作成され、AMD
、CommonJS
、およびUMD
の3つのモジュールシステムが作られました。
最も人気があったのはCommonJSで、Node.jsの作成者が最初にデフォルトのモジュールシステムとして採用しました。
しかし、CommonJSはJavaScript自体のデフォルトのモジュールシステムではありません。
ES6モジュールシステムは、CommonJSと非常に似た構文を持ちますが、非常に異なる方法で機能します。
CommonJSの同期は、数百のモジュールを含む大規模なアプリケーションのパフォーマンスの問題を引き起こす可能性があります。
ES6モジュールシステムは、2015年にJavaScriptの公式標準としてリリースされました。
ES6標準が導入される前は、サーバーサイドJavaScriptでソースコードを整理するためのネイティブ実装はありませんでした。
2019年に新しいモジュールシステムがNode.jsに移植されました。
現在、すべての主要なブラウザとNode.jsでサポートされていますが、古いバージョンのブラウザではサポートされていない場合があります。
この記事では、ES6モジュールについて詳しく説明します。
ES6 moduleとは?なぜ必要?
JavaScriptの過去には、別のJavaScriptファイルから直接参照することができませんでした。
他のファイルの関数を使用するには、DOM内に既にロードされていることを前提としていました。
例えば、下記のようにして必要なスクリプトをロードしていました。
<script src="https://code.jquery.com/jquery-3.1.1.js"></script> <script src="dist/test.js"></script> <script src="dist/app.js"></script>
これには、グローバル変数での名前空間の汚染や、依存関係が不明であるといった欠点があります。
ES6モジュールは、コードを整理して分離するために導入されました。
これにより、大規模なアプリケーションを構築するためのデータの構成要素として使用できます。
モジュールを使用すると、大きなスクリプトを小さなモジュールに分割し、他のファイルでも使用できるようになります。
開発者は必要に応じてスクリプトを共有できるようになります。
インポートすることで別のファイルのコンテンツを使用することができ、エクスポートすることで自身のコンテンツをインポートすることができます。
これにより、コードベースの保守性が向上します。
JavaScriptにおいて、大きなファイルは独自の目的を持つ小さなコンポーネントに分割される必要があります。
これにより、柔軟性と適応性が向上しますが、各アプリケーション部分が独自の接続方法を必要とすることを意味します。
情報の受け渡しは、主に「import」と「export」の2つのキーワードを使用して処理されます。
これらのJavaScriptモジュールは、何らかの方法で接続されていない限り、アプリケーションの他の部分から分離されます。
ESモジュールでは、インポートは静的であり、解析時に実行されます。
したがって、インポート構文をコードの途中で使用することはできません。
これにより、エラーを事前に把握できることや、開発者ツールが有効なコードを書くことで、より適切にサポートしてくれます。
ですが、実行時にモジュールを動的にインポートする必要がある場合があります。
解決策はもちろんあります。
動的import()関数です。
これは特別なユースケースとして扱う必要があるため、この記事では取り上げませんのでご了承下さい。
ES moduleとは何かについて説明しました。
それでは、まずブラウザでES6 moduleを使用していく方法を見ていきましょう。
ブラウザでES6 module
ES6 moduleは下記を使用します。
・ export
主に、関数やクラスなどをエクスポートします。
・ import
moduleがエクスポートされた、moduleをインポートできるようにします。
つまり、ES6 moduleはこのimport
とexport
キーワードに依存しています。
最初に、JavaScriptファイルをmoduleに変換する方法を知ることが不可欠です。
例を見ていきましょう。
まず、moduleを使用するには、type
属性を使用してスクリプトがモジュールであることを指定する必要があります。
<!-- index.html --> <!DOCTYPE html> <html lang="ja"> <head> <title>modules test</title> <script type="module" src="./module-main.js"></script> </head> <body></body> </html>
このtype="module"
属性を使用して、通常の JavaScriptファイルを ES moduleファイルに変換します。
また、コンピューターは、モジュール<script>
を参照するために使用するタグの数に関係なく、モジュールを1度だけ実行します。
// func.js export const Greeting = (user) => { console.log(`Hi!! ${user}`); };
// module-main.js import { Greeting } from "./func.js"; Greeting("Taro"); // output: Hi!! Taro
func.js
ファイルがexportキーワードを使用しています。
Greeting()
という名前の関数をexportしています。
module-main.jsでGreeting()
関数をimportキーワードを使用しています。
その後、Taroという入力でこの関数を実行すると、console.logがHi!! Taro
と出力されます。
module-main.jsファイルではGreeting()
関数を定義していないことがわかりますが、func.jsファイルからmodule-main.jsファイルにインポートされているので、アクセスして使用が可能となっています。
ES6 moduleをインポートおよびエクスポートするには、さまざまな方法があります。
エクスポートには、名前付きとdefaultキーワードの2つの種類があり、それぞれ使い方の用途が異なります。
必要に応じて使用が可能です。
ES6 モジュールをURLから読み込むこともできます
下記で、確認してみて下さい。
See the Pen JavaScript ES6 module export by dev.K | Webアプリ開発者 (@enjinia_f) on CodePen.
See the Pen JavaScript ES6 module import by dev.K | Webアプリ開発者 (@enjinia_f) on CodePen.
ES moduleは、http://
やhttps://
のURLを通じてのみ動作し、file://
のようなローカルなURLでは動作しません。
そのため、ブラウザはCORSポリシーエラーを発生させます。
HTMLファイルに2つのES moduleが含まれている場合、ドキュメントを読み込む際にはhttp://
スキームを使用する必要があります。
http://
スキームを使用してHTMLドキュメントを読み込む2つの一般的な方法は、
下記の通りです。
・ローカルサーバーを使用する
・Module Bundlerを使用する
VS Codeエディタを使用している場合、拡張機能のLive Serverをインストールし、HTMLファイルを開くことができます。
ただし、Internet Explorer 11ではES moduleがサポートされていないため、バンドラーを使用する必要があります。
以上のように、ES moduleはローカルURLで動作しないため、http://
スキームを使用してドキュメントを読み込む必要があります。
ローカルサーバーを使用するか、バンドラーを使用することが一般的な解決策となります。
ただし、Internet Explorer 11ではバンドラーが必要となるため、注意が必要です。
名前付きエクスポート
名前付きエクスポートでは2つの方法で作成できます。
個別または下部にまとめてインライン化します。
・ 個別のエクスポート
// func.js export const Greeting = (user) => { document.body.innerHTML = (`Hi!! ${user}`); }; export const name = "Hanako,"; export const age = 30;
これは、前述で学んだ方法です。
以前と同様にインポートします。
// module-main.js import { Greeting, name, age } from "./func.js"; Greeting("Taro, " + name + age);
・ 下部にまとめてエクスポート
インポートするものがたくさんある場合は、すべてをオブジェクトとしてインポートできます。
// func.js const Greeting = (user) => { document.body.innerHTML = (`Hi!! ${user}`); }; let person = "Taro, "; let personArray = ["dev.K", "Hanako", "Michael"]; // リストによる一括でエクスポート export { Greeting, person, personArray };
// module-main.js import { Greeting,person,personArray } from "./func.js"; Greeting("Hello, " + person + personArray);
import {...}
のように中括弧でインポートしたいものでリストが続くそれらをインポートできます。
See the Pen JavaScript ES6 module まとめてexport by dev.K | Webアプリ開発者 (@enjinia_f) on CodePen.
See the Pen JavaScript ES6 module まとめてimport by dev.K | Webアプリ開発者 (@enjinia_f) on CodePen.
名前付きエクスポートの場合は、別の名前を使用して、クラス、変数などをインポートすることもできます。
たとえば、asキーワード
を使用して、 person変数を別の名前user
でインポート可能です。
// func.js const Greeting = (user) => { document.body.innerHTML = (`Hi!! ${user}`); }; let person = "Taro"; export { Greeting, person };
// module-main.js import { Greeting, person as user } from "./func.js"; Greeting("Taro"); // Hi!! Taro console.log(user); // Taro
名前空間のインポート
JavaScriptおよびTypeScriptコードをmoduleに編成している場合、ある時点で「import」と「export」の命名方法を検討する必要があります。
名前に含まれる情報が少なすぎるまたは多すぎるということです。
前者は名前の競合につながり、後者は非常に長い名前につながります。
名前空間インポートはこれを支援するように設計されていますが、名前付きインポートと比較するといくつかの欠点があります。
各インポートの名前を指定せずに、特定のモジュールからすべてのエクスポート可能なアイテムをインポートしたいとします。
そのような場合は、*
構文を使用して、モジュール オブジェクトを介してアイテムを取り込みます。
下記では、名前空間をインポートします。
// func.js const Greeting = (user) => { document.body.innerHTML = (`Hi!! ${user}`); }; let person = "Hanako"; export { Greeting, person };
// module-main.js import * as func from "./func.js" func.Greeting("Taro"); // Hi!! Taro const div = document.createElement("div"); document.body.appendChild(div); div.innerHTML = (func.person) //Hanako
See the Pen JavaScript ES6 module 名前空間のimport by dev.K | Webアプリ開発者 (@enjinia_f) on CodePen.
See the Pen JavaScript ES6 module 名前空間のimport読み込み側 by dev.K | Webアプリ開発者 (@enjinia_f) on CodePen.
インポート後は、インポートしたアイテムをそのまま使用できます。
ただし、モジュールオブジェクトの名前を使用してアクセスする必要があります。
export default
defaultキーワードを使用して、任意のエクスポートをデフォルトのものにすることができます。
通常、開発者はコードをクリーンに保つためにmodule内に単一のエクスポートを保持します。
そのような場合、単一のエクスポートがある場合は、defaultのエクスポートを使用できます。
つまり、デフォルトのエクスポートは開発者がコードを匿名(無名)でエクスポートするために使用する手法です。
defaultのエクスポートがある場合では、中括弧{ }
を使用せずに直接インポートします。
// func.js const Greeting = (user) => { document.body.innerHTML = (`Hi!! ${user}`); }; export default Greeting
// module-main.js // 中括弧{}は使用しない import Greeting from "./func.js"; Greeting("Taro"); // output: Hi!! Taro
上記のように、Greeting()
をインポートする際に、 { Greeting }
ではなく、直接Greeting
を使用していることに注意してください。
See the Pen JavaScript ES6 module default export by dev.K | Webアプリ開発者 (@enjinia_f) on CodePen.
See the Pen JavaScript ES6 module export default を読み込み by dev.K | Webアプリ開発者 (@enjinia_f) on CodePen.
default エクスポートでは1つのファイルに含めることができる既定のエクスポートは1つだけです。
名前付きエクスポートよりも、より制限されます。
moduleの名前が、名前付きエクスポートであるかdefaultエクスポートであるかに基づいて、2つの方法でmoduleをファイルにインポートできます。
名前付きエクスポートは、中括弧{}
を使用して構築され、2つの方法いずれかでエクスポートします。
defaultのエクスポートはそうではありません。
または、上記のコードを次のように書き換えることもできます。
// func.js export default `Hi!! ${user}`;
デフォルトのエクスポート手法を使用して、関数、変数、文字列、クラス、またはオブジェクトリテラルを共有できることに注意してください。
ただし、export defaultキーワードを var
、let
、const
キーワードの前に付けることはできません。
// func.js export default const … // ✖︎
Node.jsでES6 module
Node.jsはv16以降、ESM(ECMAScript Modules)のサポートをデフォルトで提供しています。
ESモジュールは現在安定しており、本番環境で使用する準備ができていると考えられていますが、CommonJSモジュールとES6モジュールの両方に異なるモジュール読み込みメカニズムがあるため、混乱する場合があります。
ES6を新しいNode.jsプロジェクトで使用するのは非常に簡単です。
2つのオプションがあります。
1つは、ファイルの拡張子を.mjs
に変更することです。
ESモジュールでは、ファイルインポート時に常に拡張子を指定する必要があるため、これは重要です。
Node.jsはファイル拡張子によってCommonJSモジュールとESモジュールを区別するため、.js
の拡張子はCommonJSモジュールとして扱われ、.mjs
の拡張子はESモジュールとして扱われます。
2つ目の方法は、「packages.json」ファイルを修正して、"type": "module"
を含めることです。
{ "name": "my-package", "type": "module", "dependencies": { "..." } }
これにより、Node.jsはESモジュールとして扱うことができます。
パッケージ作成者は、モジュールの種類に関係なく、「packages.json」ファイルにこのフィールドが常に含まれていることを確認する必要があります。
したがって、Node.jsでESモジュールを使用する場合、これらのオプションを検討して、それに応じて設定を変更する必要があります。
これにより、ESモジュールの利点を最大限に活用し、Node.jsアプリケーションをよりモダンで効率的にすることができます。
注意すべき重要な機能
・ 厳格モード
デフォルトでは、モジュールは常にuse strict
を使用します。
宣言されていない変数に割り当てると、エラーが発生しますので注意して下さい。
<script type="module"> x = 10; {/* error */} </script>
・ スコープ
モジュールは、他のモジュールの最上位の変数と関数にアクセスできません。
これはモジュールのスコープ問題です。
<script type="module"> {/*personはこのモジュール内のみとなる */} let person = "Taro"; </script> <script type="module"> console.log(person); {/* Error: person is not defined */} </script>
・ this
は定義されません。
つまりモジュールでは、トップレベルではthis
はundefined(未定義)です。
<script> alert(this); {/* global object */} </script> <script type="module"> alert(this); {/* undefined */} </script>
ES6 moduleの利点
・ 保守性
コードが分割されて整理されていれば、保守が容易になります。
また、moduleは常に独立して改善できるように、可能な限り独立させることを目指しています。
また、moduleを変更しても、コードベースをあまり変更する必要はありません。
・ 再利用性
moduleの助けを借りて、コードを何度も再利用が可能です。
ファイルからexportするだけで、プロジェクト全体の他のすべてのファイルをimportして使用できます。
・ 共有性
moduleは独立させて他の開発者と共有できるようにすることを目的としています、プロジェクトにimportして使用することができます。
それの最大の例の1つがnpmとなります。
npmの助けを借りて、他の開発者が共有するパッケージである、モジュールに必要なすべてのファイルを含むパッケージをインポートして、プロジェクトで使用し共有が可能です。
最後に
JavaScriptでモジュールシステムを選ぶ際には、CommonJSとES6モジュールのどちらを選ぶべきか迷うことがあるかもしれませんが、それぞれの状況に応じて使い分けることが大切です。
新しいプロジェクトを開始する場合は、ES6モジュールを使用することをおすすめします。
ES6モジュールは、高速であり、モジュールが1回しか初期化されないため、パフォーマンスが向上します。
また、ツリーシェイキングをサポートしているため、最終出力のコードが小さくなります。
さらに、コードをよりクリーンで使いやすくする最新のJavaScript機能でもあります。
一方、既存のプロジェクトにCommonJSを使っている場合は、引き続きそれを使うことができます。
CommonJSは、Node.jsに組み込まれており、既に多くのプロジェクトで使われているため、エコシステムにおいて広く受け入れられています。
どちらを選んでも、間違いではありません。
JavaScriptのエコシステムは、多様性を受け入れることができるため、それぞれの状況に応じて使い分けることが大切です。
しかし、新しいプロジェクトを始める場合には、ES6モジュールを使うことをお勧めします。
以上が私のアドバイスです。
最後まで読んで頂きありがとうございます。
この記事が役に立った場合は、他の人にも共有していただけると幸いです。