モジュールの背景
JavaScript のプログラムはとても小さいものから始まりました。初期の用途は、必要に応じてウェブページにちょっとした対話的な機能を追加する独立したスクリプト処理がほとんどであったため、大きなスクリプトは通常必要ありませんでした。そして何年かが過ぎ、今や大量の JavaScript を持つ完全なアプリケーションをブラウザーで実行することはもちろん、JavaScript を他のコンテキスト(例えば Node.js)で使うこともあります。
複雑なプロジェクトでは、必要に応じて JavaScript プログラムを別個のモジュールに分割し、インポートできる仕組みが必要です。 Node.js は長年この機能を提供しており、モジュールの利用を可能にする JavaScript ライブラリーやフレームワークも数多くあります(例えば、他の CommonJS や、AMD ベースのモジュールシステムである RequireJS、 webpack や Babel)。
現行のブラウザーはすべて、トランスパイルを必要とせずにモジュール機能にネイティブで対応しています。これは良いことであるに違いありません。ブラウザーはモジュールの読み込みを最適化することができ、ライブラリーを使用してクライアント側で余分な処理や余分なラウンドトリップを行うよりも効率的です。しかし、 webpack のようなバンドラーが不要になるわけではありません。バンドラーは、コードを合理的なサイズの塊に分割する作業に依然として優れており、また、ミニファイ、デッドコードの排除、ツリーシェイクなどの最適化も可能です。
例の紹介
モジュールの使い方を紹介するために、GitHub 上に一連の例を作りました。これらは、ウェブページに <canvas> 要素を追加し、そのキャンバス上にいくつかの異なる図形(と、それに関するレポート)を描画するモジュールの例です。
このような機能はあまり役に立ちませんが、モジュールの説明が明確になるように意図的に単純にしています。
メモ: 使用例をダウンロードしてローカル実行する場合、ローカルのウェブサーバー上で実行する必要があります。
基本的な構造の例
最初の例 (basic-modules を参照) は、次のようなファイル構造になっています。
index.html
main.js
modules/
canvas.js
square.js
メモ: このガイドの使用例のファイル構造は、全て基本的に同一ですので、上記のファイル構造をよく見ることになるでしょう。
modules ディレクトリーには、次の 2 つのモジュールがあります。
-
canvas.js— キャンバスの設定に関する次の関数を持ちます。create()— 指定されたwidthとheightを持つキャンバスを、指定された ID を持つラッパー<div>の中に作成し、そのラッパー div 自体を指定された親要素の中に追加します。返値は、キャンバスの 2D コンテキストとラッパーの ID を持つオブジェクトです。createReportList()— 順序なしリストを指定されたラッパー要素の中に作成し、これをレポートデータを出力するために使うことができます。返値は、リストの ID です。
-
square.js— 次のものを持ちます。name—文字列 'square' を内容とする定数です。draw()— 正方形を、指定されたキャンバス上に、指定された辺の長さ、位置、色を使って描画します。返値は、正方形の辺の長さ、位置、色を持つオブジェクトです。reportArea()— 指定された辺の長さを持つ正方形の面積を、指定されたレポート用のリストに書き出します。reportPerimeter()— 指定された辺の長さを持つ正方形の周囲の長さを、指定されたレポート用のリストに書き出します。
余談 — .mjs と .js
この記事ではモジュールファイルに .js の拡張子を使用していますが、他の記事では .mjs という拡張子が使用されているのを目にすることがあるかもしれません。例えば、V8 のドキュメントではこれを推奨しています。理由は以下の通りです。
- どのファイルがモジュールで、どのファイルが通常の JavaScript であるかを明確にすることができます。
- これにより、Node.js のようなランタイムや Babel のようなビルドツールで、モジュールファイルがモジュールとして解析されるようになります。
しかし、少なくとも今のところは .js を使い続けることにしました。ブラウザーでモジュールを正しく動作させるためには、サーバーが Content-Type ヘッダーで JavaScript の MIME タイプ、例えば text/javascript などを含めて提供していることを確認する必要があります。そうしないと、"The server responded with a non-JavaScript MIME type" のような厳格な MIME タイプチェックエラーが表示され、ブラウザーは JavaScript を実行しません。ほとんどのサーバーでは、.js ファイルにはすでに正しい MIME タイプが設定されていますが、.mjs ファイルにはまだ設定されていません。すでに .mjs ファイルを正しく提供しているサーバーには、GitHub Pages や Node.js の http-server などがあります。
これは、すでにそのような環境を使用している場合や、今はまだ使用していないが、何をしているか知っていてアクセスできる場合には問題ありません(つまり、.mjs ファイルに正しい Content-Type を設定するようにサーバーを設定することができます)。しかし、あなたがファイルを提供しているサーバーを制御できない場合には、混乱を引き起こす可能性があります。
この記事では学習と移植性を考慮して、.js を使用することにしました。
通常の JavaScript ファイルに .js を使用するのと比較して、モジュールに .mjs を使用することの明確さを本当に重視しているが、上記の問題に直面したくない場合は、開発中に .mjs を使用し、ビルドステップで .js に変換することをおすすめします。
また、次の点にも注意してください。
- 一部のツールは
.mjsに対応していないことがあります。 - モジュールが指し示されているとき、それを示すために
<script type="module">属性を使用してください。
モジュール機能のエクスポート
モジュールが持つ機能にアクセスするために最初に必要なことは、そのような機能をエクスポートすることです。これは export 文を使って行います。
最も簡単な使い方は、モジュール外部に公開したい項目の前に export をつけることです。
export const name = "square";
export function draw(ctx, length, x, y, color) {
ctx.fillStyle = color;
ctx.fillRect(x, y, length, length);
return { length, x, y, color };
}
エクスポートできるものは、関数、var、let、const、および後述するクラスです。これらは最上位の階層にある必要があります。例えば、関数内で export を使うことはできません。
エクスポートしたい全ての項目をエクスポートするより便利な方法は、モジュールファイルの末尾に単一の export 文を追加し、その後にエクスポートしたい機能のカンマ区切りリストを中かっこで囲んで続けることです。例えば次のようにします。
export { name, draw, reportArea, reportPerimeter };
スクリプトへの機能のインポート
モジュールから何らかの機能をエクスポートした後は、それらを使えるようにするためにスクリプトにインポートする必要があります。その最も単純な方法は次のとおりです。
import { name, draw, reportArea, reportPerimeter } from "./modules/square.js";
import 文の後ろに、中かっこで囲まれたインポートしたい機能のカンマ区切りリストを続け、その後ろに from キーワードと、モジュール指定子を続けます。
モジュール指定子は、JavaScript 環境がモジュールファイルへのパスを解決できる文字列を提供します。
ブラウザーでは、これはサイトルートからの相対パスとなり、basic-modules の例では /js-examples/module-examples/basic-modules となります。
しかし、ここでは代わりにドット(.)構文を使用して、「現在の場所」を意味しており、その後に探そうとしているファイルへの相対パスを記述しています。相対パスの方が短いし、URL の移植性も高いので、この例はサイト階層の別の場所に移しても作業することができますから、絶対パス全体を毎回書き出すよりもずっとよいでしょう。
そのため、次のようなパスは、
/js-examples/module-examples/basic-modules/modules/square.js
次のように書くことができます。
./modules/square.js
このような書き方の動作している例は main.js にあります。
メモ:
モジュールシステムの中には、相対パスでも絶対パスでもなく、ファイル拡張子もない modules/square のようなモジュール指定を使用するものがあります。
このような指定子は、最初にインポートマップを定義しておけば、ブラウザー環境でも使用できます。
スクリプトへ機能をインポートすると、同じファイル内で定義されているのと同じように使うことができます。次のコードは、main.js でインポートに続く部分です。
const myCanvas = create("myCanvas", document.body, 480, 320);
const reportList = createReportList(myCanvas.id);
const square = draw(myCanvas.ctx, 50, 50, 100, "blue");
reportArea(square.length, reportList);
reportPerimeter(square.length, reportList);
メモ:
インポートされた値は、エクスポートされた機能の読み取り専用ビューとなります。const 変数と同様に、インポートされた変数を再代入することはできませんが、オブジェクト値のプロパティを変更することは可能です。値を再代入することができるのは、その値をエクスポートしているモジュールだけです。例として、import のリファレンス を参照してください。
インポートマップを使用したモジュールのインポート
ブラウザーがモジュールをインポートするのに、絶対 URL か、文書のベース URL を使用して解決される相対 URL であるモジュール指定子を使用する方法は、前述したとおりです。
import { name as circleName } from "https://example.com/shapes/circle.js";
import { name as squareName, draw } from "./shapes/square.js";
インポートマップにより、モジュールをインポートするときに、モジュール指定子でほぼ全ての好きなテキストを代わりに指定することができます。このマップは、モジュールの URL が解決されたときにテキストを置き換える対応する値を提供します。
例えば、下記のインポートマップの imports キーは、「モジュール指定マップ」JSON オブジェクトを定義し、プロパティ名をモジュール指定子として使用でき、ブラウザーがモジュール URL を解決する際に対応する値が代入されます。
値は、絶対 URL または相対 URL でなければなりません。
相対 URL は、インポートマップを含む文書のベース URL を使用して絶対 URL アドレスに解決されます。
<script type="importmap">
{
"imports": {
"shapes": "./shapes/square.js",
"shapes/square": "./modules/shapes/square.js",
"https://example.com/shapes/square.js": "./shapes/square.js",
"https://example.com/shapes/": "/shapes/square/",
"../shapes/square": "./shapes/square.js"
}
}
</script>
インポートマップは <script> 要素の中の JSON オブジェクト で、 type 属性を importmap に設定して定義することができます。
なお、インポートマップは文書内の特定の要素にのみ適用されることに注意してください。仕様では、ワーカーやワークレットのコンテキストでインポートマップを適用する方法についてはカバーされていません。
このマップで、上記のプロパティ名をモジュール指定子として使用することができるようになりました。 モジュール指定子キーに末尾のスラッシュがない場合は、モジュール指定子キー全体が照合されて置換されます。 例を説明すると、下記はモジュール名と一致し、URL を別のパスに再マップしています。
// Bare module names as module specifiers
import { name as squareNameOne } from "shapes";
import { name as squareNameTwo } from "shapes/square";
// Remap a URL to another URL
import { name as squareNameThree } from "https://example.com/shapes/square.js";
モジュール指定子が末尾にスラッシュがある場合、値が同様にスラッシュを持つ必要があり、キーは「パス接頭辞」として照合されます。 これにより、URL の全クラスを再マッピングすることができます。
// Remap a URL as a prefix ( https://example.com/shapes/)
import { name as squareNameFour } from "https://example.com/shapes/moduleshapes/square.js";
インポートマップ内の複数のキーがモジュール指定子を有効に一致することがあります。
例えば、shapes/circle/ というモジュール指定子は、shapes/ と shapes/circle/ というモジュール指定子キーと一致する可能性があります。
この場合、ブラウザーは最も具体的な(最も長い)モジュール指定キーに一致するものを選択します。
インポートマップは、(Node.js のように)素のモジュール名を使用してモジュールをインポートすることができ、ファイル拡張子の有無にかかわらず、パッケージからのインポートをシミュレートすることも可能です。 上記では示していませんが、モジュールをインポートするスクリプトのパスに基づいて、特定のバージョンのライブラリーをインポートすることもできます。 一般的に、これらは開発者がより人間に優しいインポートコードを書くことを可能にし、サイトで使用されるモジュールの異なるバージョンと依存関係を管理することを容易にします。 これにより、ブラウザーとサーバーの両方で同じ JavaScript ライブラリーを使用するために必要な労力を縮小することができます。
以下の節では、上記で説明した様々な機能について、さらに詳しく説明します。
機能検出
インポートマップに対応しているかどうかは、HTMLScriptElement.supports() 静的メソッドを使用してチェックすることができます(これ自体は広く対応しています)。
if (HTMLScriptElement.supports?.("importmap")) {
console.log("Browser supports import maps.");
}
モジュールの素の名前でのインポート
Node.js のような一部の JavaScript 環境では、モジュール指定子に素の名前を使用することができます。 これは、環境がモジュール名をファイルシステム内の標準的な場所に解決することができるため、動作します。 例えば、 "square" モジュールをインポートするために、以下の構文を使用することができます。
import { name, draw, reportArea, reportPerimeter } from "square";
ブラウザーで素の名前を使用するには、インポートマップが必要です。これは、ブラウザーがモジュール指定子を URL に解決するために必要な情報を提供します(JavaScript は、モジュールの場所に解決できないモジュール指定子をインポートしようとすると TypeError を発生します)。
下記は square というモジュール指定子のキーを定義したマップですが、この場合、相対アドレスの値に割り当てられました。
<script type="importmap">
{
"imports": {
"square": "./shapes/square.js"
}
}
</script>
このマップにより、モジュールをインポートするときに素の名前を使用することができるようになりました。
import { name as squareName, draw } from "square";
モジュールのパスの再マッピング
モジュール指定子マップの項目で、指定子キーとその関連値に末尾のフォワードスラッシュ (/) がある場合、パス接頭辞として使用することができます。
これにより、インポート URL の集合全体を、ある場所から別の場所に再マッピングすることができます。
また、Node の環境で見られるような「パッケージとモジュール」の作業をエミュレートするために使用することもできます。
メモ:
末尾の / は、モジュール指定子キーがモジュール指定子の一部として指定することができることを示します。
これが存在しない場合、ブラウザーはモジュール指定子キー全体にのみ一致します(置換します)。
モジュールのパッケージ
以下の JSON インポートマップ定義は、lodash を素の名前として、モジュール指定辞 lodash/ をパス /node_modules/lodash-es/ (文書のベース URL に解決)に割り当てたものです。
{
"imports": {
"lodash": "/node_modules/lodash-es/lodash.js",
"lodash/": "/node_modules/lodash-es/"
}
}
このマッピングを使用すると、素の名前を使用する「パッケージ」全体と、(パスマッピングを使用する)その中のモジュールの両方をインポートすることができます。
import _ from "lodash";
import fp from "lodash/fp.js";
上記の fp を .js というファイル拡張子なしでインポートすることは可能ですが、パスを使用するのではなく、lodash/fp というように、そのファイルに対して素のモジュール指定子キーを作成する必要があります。
これは、1 つのモジュールだけなら妥当かもしれませんが、多くのモジュールをインポートしたい場合には、拡大縮小することになります。
一般的な URL 再マッピング
モジュール指定キーはパスである必要はなく、絶対 URL(または ./, ../, / のような URL ライクな相対パス)であってもかまいません。
これは、リソースへの絶対パスを持つモジュールを自分自身でローカルリソースと再マッピングしたい場合に有用な場合があります。
{
"imports": {
"https://www.unpkg.com/moment/": "/node_modules/moment/"
}
}
バージョン管理のためのスコープ付きモジュール
Node のような環境では、モジュールとその依存関係を管理するために npm のようなパッケージマネージャーを使用します。 パッケージマネージャーは、各モジュールが他のモジュールやその依存関係から確実に区切られるようにします。 その結果、複雑なアプリケーションでは、モジュールグラフの異なる部分に複数の異なるバージョンで同じモジュールを複数回記載することができますが、ユーザーはこの複雑さについて考える必要はありません。
メモ: 相対パスを使用してバージョン管理を行うこともできますが、この方法は他にも、自分のプロジェクトに特定の構造を強制し、素のモジュール名を使用することができないなどの点で劣ります。
インポートマップも同様に、アプリケーションに複数のバージョンの依存関係を保有し、同じモジュール指定子を使用してそれらを参照することができます。
これを実装するために scopes キーを使用します。このキーでは、インポートを実行するスクリプトのパスに応じて使用されるモジュール指定子マップを提供することができます。
下記の例では、これを実演しています。
{
"imports": {
"cool-module": "/node_modules/cool-module/index.js"
},
"scopes": {
"/node_modules/dependency/": {
"cool-module": "/node_modules/some/other/location/cool-module/index.js"
}
}
}
このマッピングでは、 /node_modules/dependency/ を格納した URL のスクリプトが cool-module をインポートしている場合、 /node_modules/some/other/location/cool-module/index.js にあるバージョンが使用されます。
imports のマップは、スコープされたマップに一致するスコープがない場合、または一致するスコープに一致する指定するものが格納されていない場合に、予備として使用されます。例えば、cool-module がスコープパスに一致しないスクリプトからインポートされた場合、代わりに imports のモジュール指定子マップを使用し、 /node_modules/cool-module/index.js にあるバージョンにマッピングします。
なお、スコープを選択するために使用されるパスは、アドレスの解決方法には影響しません。 割り当てられたパスの値がスコープのパスと一致する必要はありませんし、相対パスは依然としてインポートマップを格納するスクリプトのベース URL に解決されます。
モジュール指定子マップの場合と同様に、多くのスコープキーを保有することができ、これらには重複するパスが格納される可能性があります。
複数のスコープがリファラーURLに一致する場合、最も固有のスコープパスが最初に(最も長いスコープキーが)指定子を指定しないか調べられます。
ブラウザーは、一致する仕様がない場合、次に一致するほとんどのスコープパスにフォールバックし、さらにその先に進みます。
一致するスコープのいずれにも一致する指定子がない場合、ブラウザーは imports キーのモジュール指定子マップに一致する指定子があるかどうかを調べます。
ハッシュ化されたファイル名の割り当てによるキャッシュの改善
ウェブサイトで使用されるスクリプトファイルは、キャッシュを容易にするためにハッシュ化されたファイル名にすることがよくあります。 この手法の欠点は、モジュールが変更された場合、そのハッシュ化されたファイル名を使用してそれをインポートするすべてのモジュールも更新/再生成する必要があることです。 このため、潜在的に更新のカスケードが発生し、ネットワークリソースを浪費することになります。
インポートマップは、この問題に対する便利な解決策を提供します。 アプリケーションやスクリプトは、固有のハッシュ化されたファイル名ではなく、代わりにモジュール名(アドレス)のハッシュ化されていないバージョンに依存します。 下記のようなインポートマップは、実際のスクリプトファイルへのマッピングを提供します。
{
"imports": {
"main_script": "/node/srcs/application-fg7744e1b.js",
"dependency_script": "/node/srcs/dependency-3qn7e4b1q.js"
}
}
もし dependency_script が変更された場合、ファイル名に格納されているハッシュも変更されます。この場合、モジュールの名前の変更を反映するためにインポート マップを更新するだけでよくなります。
import 文の指定子は変わらないので、これに依存する JavaScript コードのソースを更新する必要はありません。
JavaScript 以外のリソースの読み込み
統一されたモジュールアーキテクチャがもたらす魅力的な機能のひとつに、JavaScript以外のリソースをモジュールとして読み込む機能があります。例えば、 JSON を JavaScript オブジェクトとして、または CSS を CSSStyleSheet オブジェクトとしてインポートすることができます。
インポートするリソースの種類を明示的に宣言する必要があります。 既定では、ブラウザーはリソースが JavaScript であると想定し、解決されたリソースがそれ以外の場合にはエラーが発生します。 JSON、CSS、またはその他のリソースをインポートするには、import 属性構文を使用します。
import colors from "./colors.json" with { type: "json" };
import styles from "./styles.css" with { type: "css" };
ブラウザーはモジュール型の検証も行います。例えば、./data.json が JSON ファイルに解決されない場合は失敗します。これにより、データをインポートするだけで、誤ってコードが実行されないことを保証します。インポートが正常に完了すると、インポートした値を通常の JavaScript オブジェクトまたは CSSStyleSheet オブジェクトとして使用することができます。
console.log(colors.map((color) => color.value));
document.adoptedStyleSheets = [styles];
HTML にモジュールを適用する
次に main.js モジュールを HTML ページに適用する必要があります。これは少し重要な点に違いがありますが、通常のスクリプトをページに適用する方法ととてもよく似ています。
最初に type="module" を <script> 要素に含めることで、そのスクリプトがモジュールであることを宣言します。main.js をインポートするには、次のようにします。
<script type="module" src="main.js"></script>
また、JavaScript コードを <script> 要素の本文内に配置することで、モジュールのスクリプトを HTML ファイルに直接埋め込むこともできます。
<script type="module">
/* ここに JavaScript モジュールコード */
</script>
import および export 文はモジュール内でのみ使用することができ、通常のスクリプトでは使用できません。 <script> 要素に type="module" 属性がなく、他のモジュールをインポートしようとした場合、エラーが発生します。例えば次のような場合です。
<script>
import _ from "lodash"; // SyntaxError: import declarations may only appear at top level of a module
// …
</script>
<script src="a-module-using-import-statements.js"></script>
<!-- SyntaxError: import declarations may only appear at top level of a module -->
通常、すべてのモジュールを個別のファイルで定義する必要があります。 HTML にインラインで宣言されたモジュールは、他のモジュールをインポートすることはできますが、それらがエクスポートする何らかの情報は、他のモジュールからアクセスすることはできません(URL を保有していないため)。
メモ:
モジュールとその依存関係は <link> 要素で rel="modulepreload" を指定することで、事前読み込みすることができます。
これにより、モジュールを使用する時点での読み込み時間を大幅に縮小することができます。
モジュールとクラシックスクリプトとのその他の違い
- ローカルでテストしようとするときは注意してください。ローカルから(つまり
file://URL を使って)HTML ファイルを読み込もうとすると、JavaScript モジュールのセキュリティ要件のために、CORS エラーが発生します。テストはサーバー経由で行う必要があります。 - また、モジュール内部で定義されたスクリプトの動作は、クラシックスクリプト内部のものと異なるかもしれません。これは、モジュール内部では自動的に厳格モードが使われるからです。
- モジュールのスクリプトを読み込むときに
defer属性(<script>の属性 を参照)を使う必要はありません。モジュールは自動的に遅延実行されます。 - モジュールは、複数の
<script>タグで参照されていても一度しか実行されません。 - 最後ですが重要なこととして明らかにしておきますが、モジュールの機能は単独のスクリプトのスコープにインポートされます。つまり、インポートされた機能はグローバルスコープから利用することはできません。それゆえ、インポートされた機能はインポートしたスクリプトの内部からしかアクセスできず、例えば JavaScript コンソールからはアクセスできません。文法エラーは開発者ツール上に表示されますが、使えることを期待するデバッグ技術の中には使えないものがあるでしょう。
モジュールで定義した変数は、グローバルオブジェクトに明示的に割り当てられない限り、そのモジュールのスコープに属します。他にも、グローバル定義する変数は、モジュール内で利用できます。例えば、以下のコードが指定された場合は次のようになります。
<!doctype html>
<html lang="en-US">
<head>
<meta charset="UTF-8" />
<title>ページの例</title>
<link rel="stylesheet" href="" />
</head>
<body>
<div id="main"></div>
<script>
// var 文はグローバル変数を作成する。
var text = "Hello";
</script>
<script type="module" src="./render.js"></script>
</body>
</html>
/* render.js */
document.getElementById("main").innerText = text;
グローバル変数 text と document はモジュール内で利用できるので、ページにはまだ Hello が表示されます。(この例から、モジュールは必ずしも import/export 文を必要としないことにも注意してください。必要なことは、エントリーポイントに type="module" があることだけです)。
デフォルトエクスポートと名前付きエクスポート
これまでエクスポートした機能は、名前付きエクスポート (named export) というものです。それぞれの項目(関数、const など)は、エクスポート時にその名前を参照されて、インポート時にもその名前で参照されます。
エクスポートの種類には、他にデフォルトエクスポート (default export) と呼ばれるものもあります。これは、モジュールがデフォルトの機能を簡単に持つことができるように設計されたもので、また JavaScript のモジュールが既存の CommonJS や AMD のモジュールシステムと相互運用できるようになります (Json Orendorff による ES6 In Depth: Modules で上手く説明されています。"Default exports" で検索してみてください)。
どのように動作するか説明するので、使用例をみてみましょう。basic-modules の square.js に、ランダムな色、大きさ、位置の正方形を描く randomSquare() という関数があります。この関数をデフォルトとしてエクスポートしたいので、ファイルの末尾に次の内容を書きます。
export default randomSquare;
中かっこがないことに注意してください。
または、export default を関数に追加して、次のように匿名関数のように定義することもできます。
export default function (ctx) {
// …
}
main.js では、次のようにしてデフォルトの関数をインポートします。
import randomSquare from "./modules/square.js";
インポートの時にも中かっこがないことに注意してください。これは、デフォルトエクスポートはモジュールごとにひとつしか作れず、randomSquare がそれであることがわかっているからです。上記は、基本的に次の簡略表現です。
import { default as randomSquare } from "./modules/square.js";
メモ: エクスポートされる項目の名前を変更するために使われる as 構文については、以下の インポートやエクスポートの名前を変更するの節で説明します。
名前の衝突を避ける
これまでのところ、キャンバスに図形を描く私たちのモジュールは正常に動作しているようです。しかし、円や三角形など別の図形を描くモジュールを追加しようとしたらどうなるでしょう? そのような図形にも draw() や reportArea() のような関数があるかもしれません。もし同じ名前を持つ異なる関数を同じトップレベルのモジュールファイルにインポートしようとすると、最終的に名前の衝突によるエラーが起きるでしょう。
幸いなことに、これに対処する方法はいくつかあります。それらについて、次のセクションで見ていきましょう。
インポートやエクスポートの名前を変更する
import 文や export 文の中かっこの中では、キーワード as と新しい名前を使うことで、トップレベルのモジュールでその機能を使うときの名前を変更することができます。
例えば、次のどちらも同じ仕事をしますが、少し異なる方法で行います。
// -- module.js --
export { function1 as newFunctionName, function2 as anotherNewFunctionName };
// -- main.js --
import { newFunctionName, anotherNewFunctionName } from "./modules/module.js";
// -- module.js --
export { function1, function2 };
// -- main.js --
import {
function1 as newFunctionName,
function2 as anotherNewFunctionName,
} from "./modules/module.js";
実際の例を見てみましょう。renaming ディレクトリーでは、前の使用例と同じモジュールを使っていますが、円や三角形を描画するためのモジュールである circle.js と triangle.js も追加しています。
それぞれのモジュール内部では、同じ名前を持つ機能がエクスポートされており、それゆえそれぞれの末尾の export 文は次のように同一であることがわかります。
export { name, draw, reportArea, reportPerimeter };
これらを