node.js では、ES2015 よりも前にモジュールを分割する require 構文が実装されていたりなどしていたが、現在はネイティブの JavaScript でモジュール構文を使用することができる。
ECMAScript 2015のModules の標準仕様として策定されており、この機能は、ES2015 Modules、ECMAScript Modules、ES Modules、ESMなどと呼ばれている。
<script type="module" src="/module1.mjs"></script>
JavaScript モジュール使用の際の注意点と使用方法
- モジュールを使用する場合は file プロトコルだと CORS エラーが発生するため、http サーバーを立てる必要がある
- モジュール内は常に Strict モードのため、use strict 構文を追加する必要はない
- 各モジュールには独自の最上位のスコープがあり、モジュール内の最上位の変数や関数は他のスクリプトからは使用できず、export と import 構文を通してしか利用できない(ブラウザの場合、グローバル変数である window プロパティを介してグローバルな変数を作成できるが、あきらかなバッドプラクティスである)
- ルートコンテキストで this はブラウザでは window だが、undefined となる
- IE 以外は使用可能
https://caniuse.com/?search=type%20module - ネイティブの JavaScript モジュールを使用する場合は、ファイル内容を明確化するため拡張子を .mjs とすることが推奨されているが、ほとんどのサーバーは .mjs に対して Content-Type ヘッダーで text/javascript を返さないため、MIME タイプチェックエラーが表示され、ブラウザーは JavaScript を実行しない。
そのため、いまだよく .js 拡張子が使用されている
https://developer.mozilla.org/ja/docs/Web/JavaScript/Guide/Modules - HTMLに記述した script タグのリンク先にGETパラメーターをつけてのキャッシュバスター(例:hoge.js?2023)ができないため、JavaScript のインポート文を直接修正しなければならない
※ ただし、小さい容量の JavaScript ファイルのみキャッシュを削除する場合には、必要分だけキャッシュを削除することがしやすくなり、キャッシュヒット率を高めることができる - JSファイルが細かくなりすぎることで、転送ファイル数が大きくなりやすくなり、HTTP/1.1プロトコルでは同時接続数が限られるため遅くなってしまう
※ HTTP/2プロトコルの場合は同時接続数に制限がなく、昔流行ったCSSスプライトなどもやりたい放題である - node.js と同じく、デフォルトエクスポートは、1つのモジュールにつき1つで、デフォルトエクスポートされたものは、インポート先で自由に名前をつけることができる
export default function hoge() {
// hoge
}
-------------------------------------------------------------------
import Hoge from './src/hoge'
// src/hoge.js
export const hogeArray = [1, 2, 3, 4, 5]
export const HogeObject = {
id: 1,
}
export const HOGE_CONST = 100
-------------------------------------------------------------------
// すべてインポートしたい場合
import * as Hoges from './src/hoge'
// 必要なもののみ
import { HogeObject } from 'src/hoge`
// 別名を付ける場合
import { HogeObject as Hoge } from 'src/hoge`
// キャッシュバスター
import * as Hoges from './src/hoge.js?2023'
// ブラウザで利用できるJavaScriptモジュールは、常に動的にモジュールを読み込む
// つまり、モジュールが必要なときだけ読み込むようにするので、パフォーマンス上の利点がある
// importを関数として実行してパラメータとしてパスを指定することができ、次のようにPromiseを返す
import('src/hoge')
.then(module => {
console.log(HogeObject.id)
})
~
参考:https://qiita.com/azukiazusa/items/98845e79807fff0dd3cb
また import 文には URL も指定ができる。
// どのJSライブラリでもES Modulesとして読み込めるわけではない
// jQuery や React などは ES Modules として配布されていない
import * as THREE from 'https://cdn.skypack.dev/three';
モジュールコードは import 時の初回のみ評価されるため、以下のようなコードの場合は、一度しか発火しない。
// alert.js
alert( 'hoge' );
// hoge.js
import './alert.js'; // このときだけ alert( 'hoge' ); が発火する
import './alert.js'; // ここでは発火しない
// human.js
export let human = { name: 'hoge' };
// hoge.js
import { human } from './human.js';
human.name = 'foo';
import { human } from './human.js';
console.log( human.name ); // foo
Import maps について
Import maps とは、インポート先のライブラリをエイリアス登録することができる機能のこと。
<script type="importmap">
{
"imports": {
"lodash": "https://cdn.jsdelivr.net/npm/lodash-es@4.17.21/lodash.min.js",
"@" : "./sample-alert.js"
}
}
</script>
<script type="module">
import * as _ from 'lodash';
import {sayMessage} from '@';
const a = {'a': 1};
const b = {'a': 3, 'b': 2};
const c = _.defaults(a, b);
sayMessage(JSON.stringify(c));// {a: 1, b: 2}
</script>
引用元:https://ics.media/entry/16511/
import.meta について
現在のモジュールの関する情報を確認するためのオブジェクトである。
import / export もそうだが、モジュールファイル内以外では未定義になるため注意。
<script type="module">
alert(import.meta.url);
// script url (インラインスクリプトに対する HTML ページの url)
</script>
参考:https://ja.javascript.info/modules-intro
ブラウザ固有の特徴
ベア(むき出しの)モジュールは許可されない
Node.js 環境では以下のように記述できるが、
import Hoge from 'Hoge'; // ブラウザではエラーが発生
ブラウザではかならず、相対 URL か絶対 URL である必要がある。
import Hoge from './Hoge.js';
Node.js やバンドルツールのような特定の環境では、モジュールを見つけるための独自の方法や、それらを調整するためのフックがあるため、剥き出しのモジュールを使用することができます。しかしブラウザではまだベアモジュールはサポートされていません。
https://ja.javascript.info/modules-intro
遅延読み込み
モジュールスクリプトは外部スクリプトとインラインスクリプト両方で、常に遅延され、defer
属性(参考:ページのライフサイクル)と同じ効果を持つ。
そのため、以下のような特徴を持つ。
- HTMLの読み込みをブロックしない
- HTMLの読み込みが完了してから発火
- 相対的な順序は維持され、ドキュメントの最初にあるスクリプトが最初に実行される
参考:https://ja.javascript.info/modules-intro
async 属性について
モジュールスクリプトでない場合は、async 属性は外部スクリプトでのみ動作し、async スクリプトは、他のスクリプトや HTML ドキュメントとは関係なく、準備ができ次第すぐに実行される。
参考:https://isaxxx.com/archives/5356/
モジュールスクリプトの場合(インラインスクリプトでも動作する)も同様に動作し、準備ができたときに実行される。
そのため、カウンターや広告、解析スクリプトなどでは async 属性を付与するとよい。
外部スクリプトについて
参考:https://ja.javascript.info/modules-intro
同じ src 属性値の場合は、一度のみ実行
<!-- hoge.js は一度だけ取得され実行される -->
<script type="module" src="hoge.js"></script>
<script type="module" src="hoge.js"></script>
別ドメインの外部スクリプトはCORS ヘッダが必要
<!-- hoge.com は「Access-Control-Allow-Origin: *」ヘッダーを設定しなければならない -->
<script type="module" src="https://hoge.com/hoge.js"></script>
nomodule 属性について
参考:https://ja.javascript.info/modules-intro
JavaScript モジュールをサポートしないブラウザの場合、type=module を付与するとそのスクリプトは無視される。
その場合は、nomodule 属性を使ってフォールバックすることができる。
<script type="module">
alert("Runs in modern browsers");
</script>
<script nomodule>
alert("現在のブラウザは type=module と nomodule どちらも知っているので、これはスキップされます")
alert("古いブラウザは未知の type=module を持つスクリプトは無視しますが、これは実行します");
</script>
JavaScript モジュール以前に使用されていたモジュールシステム
JavaScript モジュールは前述のように言語レベルのモジュールシステムとして 2015 年に ES2015 で策定され、現在ではすべての主要なブラウザとNode.js でサポートされた。
その前には、以下のような技術が使用され、いまでも古いスクリプトで使用されている。
- AMD(Asynchronous module definition)
最も古いモジュールシステムの1つで、最初はライブラリrequire.jsで実装 - CommonJS
Node.js サーバ用に作られたモジュールシステム - UMD(Universal Module Definition)
もう1つのモジュールシステムで、ユニバーサルなものとして提案され、AMD と CommonJS と互換性がある