モジュール
JavaScriptは、はじめは小さなものから始まりました。しかし。昨今のフロントエンドではJavaScriptのコードは非常に大きなものになり、グローバル変数の衝突や保守性などの問題がありました。
node.js
では、ES2015よりも前にモジュールを分割するrequire
構文が実装されていたりなどしていましたが、すでに私達はネイティブのJavaScriptでモジュール構文を使用することができます。
モジュールのルール
JavaScriptとモジュールの世界のルールは少々異なっています。
モジュール内は常にStrictモード
はじめに、コードは常にStrictモードで実行されます。
use strict
構文を追加する必要はありません。
Strictモードは、そもそも下位互換性を維持して既存のアプリケーションが動かなくなることを防ぐための手段でしたが。モージュルは全く新しいコンテキストであるため、モージュルに下位互換性をもたせる必要はありません。
ルートコンテキストでのthis
もう一つの違いは、ルートコンテキストでthis
が何を指すかです。
通常のJavaScriptでは、グローバルオブジェクト(ブラウザではwindow
)を指しますが、モジュールでのルートコンテキストのthis
はundefined
です。
モジュールのexportとimport
モジュールの中に配置された変数や関数はグローバル空間を汚染しません。
モジュールの中の変数や関数を外の世界で利用するためには、export
構文を使用します。
エクスポートできるものには、const
、let
、var
、関数、クラスがあります。これらは、最上位の階層にある必要があり、例えば関数内でexport
することはできません。
export
構文は、デフォルトエクスポートと名前付きエクスポートがあります。
デフォルトエクスポート
デフォルトエクスポートは、1つのモジュールにつき1つしか宣言することができません。
次のように宣言します。
export default function logging() {
// なにかログを行う
}
エクスポートされた変数や関数は、import
することでしようすることができます。
デフォルトエクスポートされたものは、インポート先で自由に名前をつけることができます。
import LogFunc from './src/log'
名前付きエクスポート
一方、名前付きエクスポートはモジュールの中で0個以上宣言することができます。
デフォルトエクスポートとの構文の違いは、defult
を取り除いただけです。
export const array = [10, 20, 30, 40, 50]
export const user = {
name: '鈴木太郎',
age: 25
}
export const MAX_AGE = 100
名前付きエクスポートをインポートしてみます。モジュールのすべてをインポートしたい場合には、*
を使用します。
import * as Utils from './src/user'
しかし、通常は必要が無いものもインポートするのは良い作法とは言えません。
通常は、次のように限定的にインポートをします。
import { user } from 'src/user`
もしもインポートする際に、名前が重複してしまうような場合には、別名をつけることができます。
import { user as defaultUser } from 'src/user`
エクスポートする際に別名を使用することもできます。
const user = {
name: '鈴木太郎',
age: 25
}
export { user as DefaultUser }
動的なモジュールの読み込み
ブラウザで利用できるJavaScriptもモジュールは、常に動的にモジュールを読み込みます。つまり、モジュールが必要なときだけ読み込むようにするので、パフォーマンス上の利点があります。
これはimport
を関数として実行してパラメータとしてパスを指定することができます。これは次のようにPromise
を返します。
import('src/user')
.then(module => {
console.log(user.name)
})
ブラウザでモジュールを実行する
実際にモジュールを使用してみます。
フォルダの構成は次のようになっています。
index.html
module1.mjs
module2.mjs
ネイティブのJavaScriptモジュールは拡張子mjs
を持つことになります。
注意: ネイティブの JavaScript モジュールは、拡張子 .mjs を持つことが重要です。なぜなら、ブラウザーは JavaScript と互換性のある MIME タイプ text/javascript を持つファイルをインポートします。この拡張子を使うことにより、 "The server responded with a non-JavaScript MIME type" のような厳密な MIME タイプのチェックエラーを避けることができます。また、.mjs という拡張子は明確さ (つまり、このファイルはモジュールであり、通常の JavaScript ではないということ) や、他のツールとの相互利用性の観点からもよいことです。Google のさらに詳細なメモも参照してください
それぞれのファイルの中身は以下の通りです。
<!DOCTYPE html>
<html>
<head>
<title>module</title>
</head>
<body>
<script type="module" src="/module1.mjs"></script>
<script type="module" src="/module2.mjs"></script>
</body>
</html>
<script>
タグにtype="module"
を含めることで、そのスクリプトがモジュールであることを宣言します。
export function plus(num1, num2) {
return num1 + num2
}
import { plus } from './module1.mjs'
console.log(plus(1, 1))
モジュールは次のようの簡単なファイル構成です。
これをローカルでテストしようとするとき、file://
URLを使ってHTMLファイルを読み込もうとすると、CORSエラーが発生することに注意してください。
VSCode拡張のLiveServerを使用すると、簡単にローカルサーバーを建てることができます。
モジュールの読み込みに成功していることが確認できました。