aboutsummaryrefslogtreecommitdiff
path: root/files/ja/web/javascript/guide/modules/index.html
blob: 076afbd1d114f7ed0c9838978df7c7344d3b9089 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
---
title: JavaScript モジュール
slug: Web/JavaScript/Guide/Modules
tags:
  - Guide
  - JavaScript
  - Modules
  - export
  - impot
translation_of: Web/JavaScript/Guide/Modules
---
<div>{{jsSidebar("JavaScript Guide")}}{{Previous("Web/JavaScript/Guide/Meta_programming")}}</div>

<p>本章では、JavaScript のモジュールを使い始めるために必要なことすべてを紹介します。</p>

<h2 id="A_background_on_modules" name="A_background_on_modules">モジュールの背景</h2>

<p>JavaScript のプログラムはとても小さいものから始まりました。初期の用途は、必要に応じてウェブページにちょっとした対話的な機能を追加する独立したスクリプト処理がほとんどであったため、大きなスクリプトは通常必要ありませんでした。そして何年かが過ぎ、今や大量の JavaScript を持つ完全なアプリケーションをブラウザーで実行することはもちろん、JavaScript を他のコンテキスト (例えば <a href="/ja/docs/Glossary/Node.js">Node.js</a>) で使うこともあります。</p>

<p>それゆえ近年は、JavaScript プログラムをモジュールに分割して必要な時にインポートできるような仕組みの提供が検討されるようになってきました。Node.js は長年この機能を提供しており、モジュールの利用を可能にする JavaScript ライブラリーやフレームワークも数多くあります (例えば、他の <a href="https://ja.wikipedia.org/wiki/CommonJS">CommonJS</a> や、<a href="https://github.com/amdjs/amdjs-api/blob/master/AMD.md">AMD</a> ベースのモジュールシステムである <a href="https://requirejs.org/">RequireJS</a> など、そしてより最近では <a href="https://webpack.github.io/">Webpack</a><a href="https://babeljs.io/">Babel</a>)。</p>

<p>良い知らせは、モダンブラウザーがモジュール機能のネイティブサポートを開始していることで、この記事がその全てです。これは良いことです。ブラウザーはモジュールの読み込みを最適化できるので、外部ライブラリーを使用してクライアント側の余分な処理やラウンドトリップを行うよりも効率的にすることができます。</p>

<h2 id="Browser_support" name="Browser_support">ブラウザーのサポート状況</h2>

<p>ネイティブの JavaScript モジュール機能は、<code><a href="/ja/docs/Web/JavaScript/Reference/Statements/import">import</a></code><code><a href="/ja/docs/Web/JavaScript/Reference/Statements/export">export</a></code> 文を利用します。これらに対するブラウザーの互換性は次のとおりです。</p>

<h3 id="import" name="import">import</h3>

<p>{{Compat("javascript.statements.import")}}</p>

<h3 id="export" name="export">export</h3>

<p>{{Compat("javascript.statements.export")}}</p>

<h2 id="Introducing_an_example" name="Introducing_an_example">使用例の紹介</h2>

<p>モジュールの使い方を紹介するために、GitHub 上に<a href="https://github.com/mdn/js-examples/tree/master/modules">簡単な使用例</a>を作りました。これらは、ウェブページに {{htmlelement("canvas")}} 要素を追加し、その canvas 上にいくつかの異なる図形 (と、それに関するレポート) を描画する簡単なモジュールの例です。</p>

<p>このような機能はあまり役に立ちませんが、モジュールの説明が明確になるように意図的に単純にしています。</p>

<div class="blockIndicator note">
<p><strong>注意</strong>: 使用例をダウンロードしてローカル実行する場合、ローカルのウェブサーバー上で実行する必要があります。</p>
</div>

<h2 id="Basic_example_structure" name="Basic_example_structure">構造の基本的な例</h2>

<p>最初の使用例 (<a href="https://github.com/mdn/js-examples/tree/master/modules/basic-modules">basic-modules</a> を参照) には、次のようなファイル構造があります。</p>

<pre class="notranslate">index.html
main.js
modules/
    canvas.js
    square.js</pre>

<div class="blockIndicator note">
<p><strong>注意</strong>: このガイドの使用例のファイル構造は、全て基本的に同一ですので、上記のファイル構造をよく見ることになるでしょう。</p>
</div>

<p>ディレクトリー modules には、次の 2 つのモジュールがあります。</p>

<ul>
 <li><code>canvas.js</code> — canvas の設定に関する次の関数を持ちます。

  <ul>
   <li><code>create()</code> — 指定された <code>width</code><code>height</code> を持つ canvas を、指定された ID を持つラッパー {{htmlelement("div")}} の中に作成し、そのラッパー div 自体を指定された親要素の中に追加します。戻り値は、canvas の 2D コンテキストとラッパーの ID を持つ、オブジェクトです。</li>
   <li><code>createReportList()</code> — 順序なしリストを指定されたラッパー要素の中に作成し、これをレポートデータを出力するために使うことができます。戻り値は、リストの ID です。</li>
  </ul>
 </li>
 <li><code>square.js</code> — 次のものを持ちます。
  <ul>
   <li><code>name</code> —文字列 'square' を内容とする定数です。</li>
   <li><code>draw()</code> — 正方形を、指定された canvas 上に、指定された辺の長さ、位置、色を使って描画します。戻り値は、正方形の辺の長さ、位置、色を持つオブジェクトです。</li>
   <li><code>reportArea()</code> — 指定された辺の長さを持つ正方形の面積を、指定されたレポート用のリストに書き出します。</li>
   <li><code>reportPerimeter()</code> — 指定された辺の長さを持つ正方形の周囲の長さを、指定されたレポート用のリストに書き出します。</li>
  </ul>
 </li>
</ul>

<h2 id="余談_—_.mjs_対_.js">余談 — <code>.mjs</code><code>.js</code></h2>

<p>この記事ではモジュールファイルに <code>.js</code> の拡張子を使用していますが、他の記事では <code>.mjs</code> という拡張子が使用されているのを目にすることがあるかもしれません。例えば、<a href="https://v8.dev/features/modules#mjs">V8 のドキュメント</a>ではこれを推奨しています。理由は以下の通りです。</p>

<ul>
	<li>どのファイルがモジュールで、どのファイルが通常の JavaScript であるかを明確にすることができます。</li>
	<li>これにより、<a href="https://nodejs.org/api/esm.html#esm_enabling">Node.js</a> のようなランタイムや <a href="https://babeljs.io/docs/en/options#sourcetype">Babel</a> のようなビルドツールで、モジュールファイルがモジュールとして解析されるようになります。</li>
</ul>

<p>しかし、少なくとも今のところは <code>.js</code> を使い続けることにしました。ブラウザでモジュールを正しく動作させるためには、サーバーが <code>text/javascript</code> などの JavaScript MIME タイプを含む <code>Content-Type</code> ヘッダでモジュールを提供していることを確認する必要があります。そうしないと、"The server responded with a non-JavaScript MIME type" のような厳格な MIME タイプチェックエラーが表示され、ブラウザは JavaScript を実行しません。ほとんどのサーバーでは、<code>.js</code> ファイルにはすでに正しい MIME タイプが設定されていますが、<code>.mjs</code> ファイルにはまだ設定されていません。すでに <code>.mjs</code> ファイルを正しく提供しているサーバーには、<a href="https://pages.github.com/">GitHub Pages</a> や Node.js の <code><a href="https://github.com/http-party/http-server#readme">http-server</a></code> などがあります。</p>

<p>これは、すでにそのような環境を使用している場合や、今はまだ使用していないが、何をしているか知っていてアクセスできる場合には問題ありません(つまり、<code>.mjs</code> ファイルに正しい <code><a href="/ja/docs/Web/HTTP/Headers/Content-Type">Content-Type</a></code> を設定するようにサーバーを設定することができます)。しかし、あなたがファイルを提供しているサーバーを制御できない場合には、混乱を引き起こす可能性があります。</p>

<p>この記事では学習と移植性を考慮して、<code>.js</code> を使用することにしました。</p>

<p>通常の JavaScript ファイルに <code>.js</code> を使用するのと比較して、モジュールに <code>.mjs</code> を使用することの明確さを本当に重視しているが、上記の問題に直面したくない場合は、開発中に <code>.mjs</code> を使用し、ビルドステップで <code>.js</code> に変換することをおすすめします。</p>

<p>また、次の点にも注意してください。</p>

<ul>
	<li><a href="https://www.typescriptlang.org/ja/">TypeScript</a> のように、ツールによっては <code>.mjs</code> をサポートしていないものがあります。</li>
	<li>モジュールが指し示されているとき、それを示すために <code>&lt;script type="module"&gt;</code> 属性を使用します。</li>
</ul>

<h2 id="Exporting_module_features" name="Exporting_module_features">モジュール機能のエクスポート</h2>

<p>モジュールが持つ機能にアクセスするために最初に必要なことは、そのような機能をエクスポートすることです。これは <code><a href="/ja/docs/Web/JavaScript/Reference/Statements/export">export</a></code> 文を使って行います。</p>

<p>最も簡単な使い方は、モジュール外部に公開したい項目の前に <code>export</code> をつけることです。</p>

<pre class="brush: js; notranslate">export const name = 'square';

export function draw(ctx, length, x, y, color) {
  ctx.fillStyle = color;
  ctx.fillRect(x, y, length, length);

  return {
    length: length,
    x: x,
    y: y,
    color: color
  };
}</pre>

<p>エクスポートできるものは、関数、<code>var</code><code>let</code><code>const</code>、および後で見ることになりますが、クラスです。これらは最上位の階層にある必要があります。例えば、関数内で <code>export</code> を使うことはできません。</p>

<p>エクスポートしたい全ての項目をエクスポートするより便利な方法は、モジュールファイルの末尾に単一の export 文を追加し、その後にエクスポートしたい機能のカンマ区切りリストを中かっこで囲んで続けることです。例えば次のようにします。</p>

<pre class="brush: js; notranslate">export { name, draw, reportArea, reportPerimeter };</pre>

<h2 id="Importing_features_into_your_script" name="Importing_features_into_your_script">スクリプトへの機能のインポート</h2>

<p>モジュールから何らかの機能をエクスポートした後は、それらを使えるようにするためにスクリプトにインポートする必要があります。その最も単純な方法は次のとおりです。</p>

<pre class="brush: js; notranslate">import { name, draw, reportArea, reportPerimeter } from './modules/square.js';</pre>

<p><code><a href="/ja/docs/Web/JavaScript/Reference/Statements/import">import</a></code> 文の後ろに、中かっこで囲まれたインポートしたい機能のカンマ区切りリストを続け、その後ろに from キーワードと、モジュールファイルへのパスを続けます。このパスは、サイトのルートからの相対パスであり、<code>basic-modules</code> の場合は <code>/js-examples/modules/basic-modules</code> です。</p>

<p>しかし、この例ではパスの書き方が少し異なっています。「現在の位置」を意味するドット (<code>.</code>) 記法を使っており、その後ろに見つけようとするファイルへのパスを続けています。これは、完全な相対パスを毎回記述するよりも短くてすむためとてもよい方法であり、URL の可搬性もあるため、サイト階層構造の異なる場所に移動させた場合でも動作するでしょう。</p>

<p>そのため、このようなパスは、</p>

<pre class="notranslate">/js-examples/modules/basic-modules/modules/square.js</pre>

<p>このように書けます。</p>

<pre class="notranslate">./modules/square.js</pre>

<p>このような書き方の動作している例は <code><a href="https://github.com/mdn/js-examples/blob/master/modules/basic-modules/main.js">main.js</a></code> にあります。</p>

<div class="blockIndicator note">
<p><strong>注意</strong>: モジュールシステムの中には、ファイルの拡張子やドットを省略できるものがあります (例えば <code>'/modules/square'</code>)。このような書き方は、ネイティブの JavaScript モジュールでは動作しません。</p>
</div>

<p>スクリプトへ機能をインポートすると、同じファイル内で定義されているのと同じように使うことができます。次のコードは、<code>main.js</code> でインポートに続く部分です。</p>

<pre class="brush: js; notranslate">let myCanvas = create('myCanvas', document.body, 480, 320);
let reportList = createReportList(myCanvas.id);

let square1 = draw(myCanvas.ctx, 50, 50, 100, 'blue');
reportArea(square1.length, reportList);
reportPerimeter(square1.length, reportList);
</pre>

<div class="blockIndicator note">
<p><strong>注: </strong>インポートされた機能はファイル内で利用できますが、エクスポートされた機能の読み取り専用ビューです。インポートされた変数を変更することはできませんが、<code>const</code> と同様にプロパティを変更することはできます。さらに、これらの機能はライブバインディングとしてインポートされます。つまり、<code>const</code> と違ってバインディングを変更できなくても値を変更できるということです。</p>
</div>

<h2 id="Applying_the_module_to_your_HTML" name="Applying_the_module_to_your_HTML">HTML にモジュールを適用する</h2>

<p>次に <code>main.js</code> モジュールを HTML ページに適用する必要があります。これは少し重要な点に違いがありますが、通常のスクリプトをページに適用する方法ととてもよく似ています。</p>

<p>最初に <code>type="module"</code>{{htmlelement("script")}} 要素に含めることで、そのスクリプトがモジュールであることを宣言します。</p>

<pre class="brush: js; notranslate">&lt;script type="module" src="main.js"&gt;&lt;/script&gt;</pre>

<p>また、<code>&lt;script&gt;</code> 要素の本文内に JavaScript コードを配置することで、モジュールのスクリプトをHTMLファイルに直接埋め込むこともできます。</p>

<pre class="brush: js notranslate">&lt;script type="module"&gt;
  /* ここに JavaScript モジュールコード */
&lt;/script&gt;</pre>

<p>モジュールをインポートする先のスクリプトは、基本的に最上位のモジュールとして動作します。これを無視すると、例えば Firefox の場合は "SyntaxError: import declarations may only appear at top level of a module" (構文エラー: インポート宣言は最上位のモジュールしか使えません) というエラーが発生します。</p>

<p><code>import</code><code>export</code> 文は、モジュールの中でのみ使うことができます。通常のスクリプトの中では使えません。</p>

<h2 id="Other_differences_between_modules_and_standard_scripts" name="Other_differences_between_modules_and_standard_scripts">モジュールの通常のスクリプトの間のその他の違い</h2>

<ul>
 <li>ローカルでテストしようとするときは注意してください。ローカルから (つまり <code>file://</code> URL を使って) HTML ファイルを読み込もうとすると、JavaScript モジュールのセキュリティ要件のために、CORS エラーが発生します。テストはサーバー経由で行う必要があります。</li>
 <li>また、モジュール内部で定義されたスクリプトの動作は、通常のスクリプト内部のものと異なるかもしれません。これは、モジュール内部では自動的に <a href="/ja/docs/Web/JavaScript/Reference/Strict_mode">Strict モード</a> が使われるからです。</li>
 <li>モジュールのスクリプトを読み込むときに <code>defer</code> 属性 (<a href="/ja/docs/Web/HTML/Element/script#Attributes"><code>&lt;script&gt;</code> の属性</a> を参照) を使う必要はありません。モジュールは自動的に遅延実行されます。</li>
 <li>モジュールは、複数の <code>&lt;script&gt;</code> タグで参照されていても一度しか実行されません。</li>
 <li>最後ですが重要なこととして明らかにしておきますが、モジュールの機能は単独のスクリプトのスコープにインポートされます。つまり、インポートされた機能はグローバルスコープから利用することはできません。それゆえ、インポートされた機能はインポートしたスクリプトの内部からしかアクセスできず、例えば JavaScript コンソールからはアクセスできません。文法エラーは開発ツール上に表示されますが、使えることを期待するデバッグ技術の中には使えないものがあるでしょう。</li>
</ul>

<h2 id="Default_exports_versus_named_exports" name="Default_exports_versus_named_exports">デフォルトエクスポートと名前付きエクスポート</h2>

<p>これまでエクスポートした機能は、<strong>名前付きエクスポート (named export)</strong> というものです。それぞれの項目 (関数、<code>const</code> など) は、エクスポート時にその名前を参照されて、インポート時にもその名前で参照されます。</p>

<p>エクスポートの種類には、他に<strong>デフォルトエクスポート</strong>と呼ばれるものもあります。これは、モジュールがデフォルトの機能を簡単に持つことができるように設計されたもので、また JavaScript のモジュールが既存の CommonJS や AMD のモジュールシステムと相互運用できるようになります (Json Orendorff による <a href="https://hacks.mozilla.org/2015/08/es6-in-depth-modules/">ES6 In Depth: Modules</a> で上手く説明されています。"Default exports" で検索してみてください)。</p>

<p>どのように動作するか説明するので、使用例をみてみましょう。basic-modules の <code>square.js</code> に、ランダムな色、大きさ、位置の正方形を描く <code>randomSquare()</code> という関数があります。この関数をデフォルトとしてエクスポートしたいので、ファイルの末尾に次の内容を書きます。</p>

<pre class="brush: js; notranslate">export default randomSquare;</pre>

<p>中かっこがないことに注意してください。</p>

<p>または、<code>export default</code> を関数に追加して、次のように匿名関数のように定義することもできます。</p>

<pre class="brush: js; notranslate">export default function(ctx) {
  ...
}</pre>

<p><code>main.js</code> では、次のようにしてデフォルトの関数をインポートします。</p>

<pre class="brush: js; notranslate">import randomSquare from './modules/square.js';</pre>

<p>インポートの時にも中かっこがないことに注意してください。これは、デフォルトエクスポートはモジュールごとにひとつしか作れず、<code>randomSquare</code> がそれであることがわかっているからです。上記は、基本的に次の簡略表現です。</p>

<pre class="brush: js; notranslate">import {default as randomSquare} from './modules/square.js';</pre>

<div class="blockIndicator note">
<p><strong>注意</strong>: エクスポートされる項目の名前を変更するために使われる as の文法については、以下の {{anch("Renaming imports and exports")}} セクションで説明します。</p>
</div>

<h2 id="Avoiding_naming_conflicts" name="Avoiding_naming_conflicts">名前の衝突を避ける</h2>

<p>これまでのところ、キャンバスに図形を描く私たちのモジュールは正常に動作しているようです。しかし、円や三角形など別の図形を描くモジュールを追加しようとしたらどうなるでしょう? そのような図形にも <code>draw()</code><code>reportArea()</code> のような関数があるかもしれません。もし同じ名前を持つ異なる関数を同じトップレベルのモジュールファイルにインポートしようとすると、最終的に名前の衝突によるエラーが起きるでしょう。</p>

<p>幸いなことに、これに対処する方法はいくつかあります。それらについて、次のセクションで見ていきましょう。</p>

<h2 id="Renaming_imports_and_exports" name="Renaming_imports_and_exports">インポートやエクスポートの名前を変更する</h2>

<p><code>import</code> 文や <code>export</code> 文の中かっこの中では、キーワード <code>as</code> と新しい名前を使うことで、トップレベルのモジュールでその機能を使うときの名前を変更することができます。</p>

<p>次の二つの例は、異なる方法ですが、同じことをしています。</p>

<pre class="brush: js; notranslate">// module.js の内部
export {
  function1 as newFunctionName,
  function2 as anotherNewFunctionName
};

// main.js の内部
import { newFunctionName, anotherNewFunctionName } from './modules/module.js';</pre>

<pre class="brush: js; notranslate">// module.js の内部
export { function1, function2 };

// main.js の内部
import { function1 as newFunctionName,
         function2 as anotherNewFunctionName } from './modules/module.js';</pre>

<p>実際の例を見てみましょう。<a href="https://github.com/mdn/js-examples/tree/master/modules/renaming">renaming</a> ディレクトリでは、前の使用例と同じモジュールを使っていますが、円や三角形を描画するためのモジュールである <code>circle.js</code><code>triangle.js</code> も追加しています。</p>

<p>それぞれのモジュール内部では、同じ名前を持つ機能がエクスポートされており、それゆえそれぞれの末尾の <code>export</code> 文は次のように同一であることがわかります。</p>

<pre class="brush: js; notranslate">export { name, draw, reportArea, reportPerimeter };</pre>

<p>これらを <code>main.js</code> にインポートするために、次のようにするとします。</p>

<pre class="brush: js; notranslate">import { name, draw, reportArea, reportPerimeter } from './modules/square.js';
import { name, draw, reportArea, reportPerimeter } from './modules/circle.js';
import { name, draw, reportArea, reportPerimeter } from './modules/triangle.js';</pre>

<p>すると、ブラウザーは "SyntaxError: redeclaration of import name" (構文エラー: インポート名の再宣言) (Firefox の場合) のようなエラーを発生させるでしょう。</p>

<p>そのため、それぞれが固有の名前を持つようにするために、次のようにインポートの名前を変える必要があります。</p>

<pre class="brush: js; notranslate">import { name as squareName,
         draw as drawSquare,
         reportArea as reportSquareArea,
         reportPerimeter as reportSquarePerimeter } from './modules/square.js';

import { name as circleName,
         draw as drawCircle,
         reportArea as reportCircleArea,
         reportPerimeter as reportCirclePerimeter } from './modules/circle.js';

import { name as triangleName,
        draw as drawTriangle,
        reportArea as reportTriangleArea,
        reportPerimeter as reportTrianglePerimeter } from './modules/triangle.js';</pre>

<p>他の方法として、例えば次のようにすることで、モジュールファイル側でこの問題を解決することもできます。</p>

<pre class="brush: js; notranslate">// in square.js
export { name as squareName,
         draw as drawSquare,
         reportArea as reportSquareArea,
         reportPerimeter as reportSquarePerimeter };</pre>

<pre class="brush: js; notranslate">// in main.js
import { squareName, drawSquare, reportSquareArea, reportSquarePerimeter } from './modules/square.js';</pre>

<p>これも同じように機能します。どちらのスタイルを取るかはあなた次第ですが、モジュール側のコードはそのままにしてインポート側を変更する方が、間違いなく賢明です。これは、制御できないサードパーティーのモジュールからインポートするときには、特に意味があります。</p>

<h2 id="Creating_a_module_object" name="Creating_a_module_object">モジュールオブジェクトの作成</h2>

<p>上記のインポート方法は正常に動作しますが、少し使いづらく冗長です。よりよい方法は、モジュール内のそれぞれの機能を、モジュールオブジェクトの中にインポートすることです。その構文は次のとおりです。</p>

<pre class="brush: js; notranslate">import * as Module from './modules/module.js';</pre>

<p>これは、<code>module.js</code> の中にある全てのエクスポートを取得して、それらを <code>Module</code> というオブジェクトのメンバーとして利用できるようにすることで、独自の名前空間を持たせるような効果があります。次のようにして使います。</p>

<pre class="brush: js; notranslate">Module.function1()
Module.function2()
など</pre>

<p>実際の使用例を見てみましょう。<a href="https://github.com/mdn/js-examples/tree/master/modules/module-objects">module-objects</a>  ディレクトリでは、また同じ例を使っていますが、この新しい構文を利用するために書き直されています。モジュール内のエクスポートは、いずれも次の単純な構文を使っています。</p>

<pre class="brush: js; notranslate">export { name, draw, reportArea, reportPerimeter };</pre>

<p>一方でインポートは次のようなものです。</p>

<pre class="brush: js; notranslate">import * as Canvas from './modules/canvas.js';

import * as Square from './modules/square.js';
import * as Circle from './modules/circle.js';
import * as Triangle from './modules/triangle.js';</pre>

<p>どの場合も、その指定されたオブジェクト名の配下からモジュールのインポートにアクセスできます。例えば次のようにして使います。</p>

<pre class="brush: js; notranslate">let square1 = Square.draw(myCanvas.ctx, 50, 50, 100, 'blue');
Square.reportArea(square1.length, reportList);
Square.reportPerimeter(square1.length, reportList);</pre>

<p>このように (必要な箇所にオブジェクトの名前を含むようにさえすれば) コードは以前と同じように書くことができ、そしてインポートはより簡潔になります。</p>

<h2 id="Modules_and_classes" name="Modules_and_classes">モジュールとクラス</h2>

<p>最初の方で触れましたが、クラスをエクスポートしたりインポートすることもできます。これがコード上で名前の衝突を避けるもう一つの方法で、もし自分のモジュールを既にオブジェクト指向のスタイルで書いているのであれば、特に便利です。</p>

<p><a href="https://github.com/mdn/js-examples/tree/master/modules/classes">classes</a> ディレクトリの中には、私たちの図形を描くモジュールを ES クラスを使って書き直した例があります。例えば <code><a href="https://github.com/mdn/js-examples/blob/master/modules/classes/modules/square.js">square.js</a></code> ファイルでは、次のように全ての機能を一つのクラスの中に持たせています。</p>

<pre class="brush: js; notranslate">class Square {
  constructor(ctx, listId, length, x, y, color) {
    ...
  }

  draw() {
    ...
  }

  ...
}</pre>

<p>そして、次のようにエクスポートします。</p>

<pre class="brush: js; notranslate">export { Square };</pre>

<p><code><a href="https://github.com/mdn/js-examples/blob/master/modules/classes/main.js">main.js</a></code> では、これを次のようにインポートします。</p>

<pre class="brush: js; notranslate">import { Square } from './modules/square.js';</pre>

<p>そして、正方形を描くために次のようにクラスを使います。</p>

<pre class="brush: js; notranslate">let square1 = new Square(myCanvas.ctx, myCanvas.listId, 50, 50, 100, 'blue');
square1.draw();
square1.reportArea();
square1.reportPerimeter();</pre>

<h2 id="Aggregating_modules" name="Aggregating_modules">モジュールの集約</h2>

<p>複数のモジュールをひとつに集約させたいと思うことがあるかもしれません。依存性の階層は複数になることがあり、いくつかあるサブモジュールをひとつの親モジュールにまとめて管理を単純化したいと思うかもしれません。これは、親モジュールで次の形式によるエクスポート構文を使うことで可能です。</p>

<pre class="brush: js; notranslate">export * from 'x.js'
export { name } from 'x.js'</pre>

<p>使用例は <a href="https://github.com/mdn/js-examples/tree/master/modules/module-aggregation">module-aggregation</a> ディレクトリを参照してください。この例 (クラスを使った以前の例を元にしています) には、<code>shapes.js</code> というモジュールが追加されています。これは <code>circle.js</code><code>square.js</code><code>triangle.js</code> の全ての機能をひとつに集約したものです。また、サブモジュールを <code>modules</code> ディレクトリの中にある <code>shapes</code> というサブディレクトリに移動させています。つまり、この例のモジュール構造は次のようなものです。</p>

<pre class="notranslate">modules/
  canvas.js
  shapes.js
  shapes/
    circle.js
    square.js
    triangle.js</pre>

<p>それぞれのサブモジュールでは、例えば次のような同じ形式のエクスポートが行われています。</p>

<pre class="brush: js; notranslate">export { Square };</pre>

<p>その次は集約を行う部分です。<code><a href="https://github.com/mdn/js-examples/blob/master/modules/module-aggregation/modules/shapes.js">shapes.js</a></code> の内部には次のような行があります。</p>

<pre class="brush: js; notranslate">export { Square } from './shapes/square.js';
export { Triangle } from './shapes/triangle.js';
export { Circle } from './shapes/circle.js';</pre>

<p>これらは、個々のサブモジュールのエクスポートを取得して、それらを <code>shapes.js</code> モジュールから利用できるようにする効果があります。</p>

<div class="blockIndicator note">
<p><strong>注意</strong>: <code>shapes.mjs</code> の中で参照されているエクスポートは、基本的にそのファイルを経由して転送されるだけで、ファイルの中には存在しません。そのため、同じファイルの中でそれらを使ったコードを書くことはできません。</p>
</div>

<p>最後に <code>main.js</code> ファイルでは、全てのモジュールのクラスにアクセスするために、次のインポートを書き換えています。</p>

<pre class="brush: js; notranslate">import { Square } from './modules/square.js';
import { Circle } from './modules/circle.js';
import { Triangle } from './modules/triangle.js';</pre>

<p>書き換え後は、次のような 1行になります。</p>

<pre class="brush: js; notranslate">import { Square, Circle, Triangle } from './modules/shapes.js';</pre>

<h2 id="Dynamic_module_loading" name="Dynamic_module_loading">動的なモジュールの読み込み</h2>

<p>ブラウザーで利用できる JavaScript モジュールの最新機能は、動的なモジュールの読み込みです。これにより、全てを最初に読み込んでしまうのではなく、必要が生じたときにのみ動的にモジュールを読み込むことができます。これには明らかなパフォーマンス上の利点があります。どのように動作するのか、読んで見てましょう。</p>

<p>この新しい機能により、<code>import()</code> を関数として実行し、そのときのパラメーターとしてモジュールへのパスを指定することができます。これは次のように <a href="/ja/docs/Web/JavaScript/Reference/Global_Objects/Promise">Promise</a> を返し、エクスポートにアクセスできるモジュールオブジェクト ({{anch("Creating a module object")}} を参照) を使って fulfilled 状態になります。</p>

<pre class="brush: js; notranslate">import('./modules/myModule.js')
  .then((module) =&gt; {
    // モジュールを使って何かをする。
  });</pre>

<p>例を見てみましょう。<a href="https://github.com/mdn/js-examples/tree/master/modules/dynamic-module-imports">dynamic-module-imports</a> ディレクトリには、以前のクラスの例に基づいた別の使用例があります。しかし、今回は使用例が読み込まれたときにはキャンバスに何も描画しません。その代わり "Circle" (円)、"Square" (正方形)、"Triangle" (三角形) という 3つのボタンを表示し、それらが押されたとき、対応した図形を描くために必要なモジュールを動的に読み込んで使用します。</p>

<p>この使用例では <code><a href="https://github.com/mdn/js-examples/blob/master/modules/dynamic-module-imports/index.html">index.html</a></code><code><a href="https://github.com/mdn/js-examples/blob/master/modules/dynamic-module-imports/main.js">main.js</a></code> のみを変更しており、モジュールのエクスポートは以前と同じままです。</p>

<p><code>main.js</code> では、それぞれのボタンへの参照を取得するために、次のように <code><a href="/ja/docs/Web/API/Document/querySelector">document.querySelector()</a></code> を使っています。</p>

<pre class="brush: js; notranslate">let squareBtn = document.querySelector('.square');</pre>

<p>そしてそれぞれのボタンに、押されたときに関連するモジュールを動的に読み込んで図形を描くためのイベントリスナーを設定します。</p>

<pre class="brush: js; notranslate">squareBtn.addEventListener('click', () =&gt; {
  import('./modules/square.js').then((Module) =&gt; {
    let square1 = new Module.Square(myCanvas.ctx, myCanvas.listId, 50, 50, 100, 'blue');
    square1.draw();
    square1.reportArea();
    square1.reportPerimeter();
  })
});</pre>

<p>Promise が fullfilled 状態になったときにモジュールオブジェクトを返し、クラスはそのオブジェクトの部分機能であるため、<code>Module.Square( ... )</code> のように <code>Module.</code> を追加したコンストラクターにアクセスする必要があります。</p>

<h2 id="Troubleshooting" name="Troubleshooting">トラブルシューティング</h2>

<p>これらは、モジュールの動作に問題があるときに助けになるかもしれないヒントです。もし他にあれば自由にリストに追加してください。</p>

<ul>
 <li>前に説明したので繰り返しになりますが、<code>.mjs</code> ファイルは <code>javascript/esm</code> という MIME タイプ (または JavaScript 互換である <code>application/javascript</code> のような MIME タイプ) で読み込まれる必要があり、そうでなければ厳密な MIME タイプチェックによって "The server responded with a non-JavaScript MIME type" (サーバーが非 JavaScript の MIME タイプを返しました) のようなエラーが発生するでしょう。</li>
 <li>HTML ファイルをローカルから (例えば <code>file://</code> の URL を使って) 読み込もうとすると、JavaScript モジュールのセキュリティ要件によって CORS エラーが発生するでしょう。動作検証はサーバー経由で行う必要があります。GitHub は <code>.mjs</code> ファイルを正しい MIME 型で返すため理想的です。</li>
 <li><code>.mjs</code> は比較的新しい拡張子であり、OS によってはそれを認識しないか、何か別のものに置き換えようとしてしまうかもしれません。例えば macOS は、通知することなく <code>.mjs</code> ファイルに <code>.js</code> を追加して自動的に拡張子を隠すことがわかりました。そのため、実際にやってくるファイルは全て <code>x.mjs.js</code> のようなものでした。ファイル拡張子を自動的に隠すことをオフにして、<code>.mjs</code> を受け入れるように設定すると問題は無くなります。</li>
</ul>

<h2 id="See_also" name="See_also">関連情報</h2>

<ul>
 <li><a href="https://developers.google.com/web/fundamentals/primers/modules#mjs">Using JavaScript modules on the web</a>, Addy Osmani と Mathias Bynens による</li>
 <li><a href="https://hacks.mozilla.org/2018/03/es-modules-a-cartoon-deep-dive/">ES modules: A cartoon deep-dive</a>, Lin Clark による Hacks ブログの投稿</li>
 <li><a href="https://hacks.mozilla.org/2015/08/es6-in-depth-modules/">ES6 in Depth: Modules</a>, Jason Orendorff による Hacks ブログの投稿</li>
 <li>Axel Rauschmayer の書籍 <a href="http://exploringjs.com/es6/ch_modules.html">Exploring JS: Modules</a></li>
</ul>

<p>{{Previous("Web/JavaScript/Guide/Meta_programming")}}</p>