モジュールロードの実装
1. ブラウザの読み込み
伝統的な手法
HTML Web ページでは、ブラウザは<script>
タグを通じて JavaScript スクリプトを読み込みます。
<!-- 页面内嵌的脚本 -->
<script type="application/javascript">
// module code
</script>
<!-- 外部脚本 -->
<script type="application/javascript" src="path/to/myModule.js">
</script>
デフォルトでは、ブラウザは JavaScript スクリプトを同期的に読み込みます。つまり、レンダリング エンジンは<script>
タグに遭遇すると停止し、スクリプトが実行されるまで待機し、その後レンダリングを続けます。外部スクリプトの場合は、スクリプトのダウンロード時間も追加する必要があります。スクリプトのサイズが大きい場合、ダウンロードと実行に時間がかかるため、ブラウザがブロックされ、ユーザーはブラウザが応答せずに「スタック」しているように感じます。ユーザーエクスペリエンスは非常に悪いです。
そのため、ブラウザではスクリプトを非同期的に読み込むことができます。非同期読み込み用の 2 つの構文を次に示します。
<script src="path/to/myModule.js" defer></script>
<script src="path/to/myModule.js" async></script>
defer
またはasync
属性を指定すると、スクリプトは非同期でロードされます。レンダリング エンジンは、このコマンド行を検出すると、外部スクリプトのダウンロードを開始しますが、ダウンロードして実行されるのを待たずに、次のコマンドを直接実行します。
defer
とのasync
違い:
-
defer
ページ全体がメモリ内で正常にレンダリングされるまで (DOM 構造が完全に生成され、他のスクリプトが実行されるまで) 実行されません。 -
async
ダウンロードが完了すると、レンダリング エンジンはレンダリングを中断し、このスクリプトの実行後にレンダリングを続行します。
要約する: 「レンダリング後に実行」と「ダウンロード後に実行」defer
です。async
また、複数のスクリプトがある場合defer
、ページに表示される順序で読み込まれますが、複数のasync
スクリプトは読み込み順序を保証できません。
ロードルール
ブラウザはES6
モジュールをロードします。このモジュールでも<script>
タグが使用されますが、type="module"
属性が追加されます。
<script type="module" src="./foo.js"></script>
ES6 モジュールでは Web ページに埋め込むこともでき、構文と動作は外部スクリプトを読み込む場合とまったく同じです。
<script type="module">
import utils from "./utils.js";
// other code
</script>
予防:
- コードはグローバル スコープではなくモジュール スコープで実行されます。モジュール内のトップレベル変数。外部には表示されません。
- モジュール スクリプトは、宣言されているかどうかに関係なく、厳密モードを自動的に採用します
use strict
。 - モジュール間では、
import
コマンドを使用して他のモジュールをロードできます (.js
サフィックスは省略できません。絶対 URL または相対 URL を指定する必要があります)。また、export
コマンドを使用して外部インターフェイスを出力することもできます。 - モジュール内では、トップレベルの
this
キーワードは を指すundefined
のではなく、 を返しますwindow
。つまり、モジュールの最上位で this キーワードを使用しても意味がありません。 - 同じモジュールが複数回ロードされた場合、実行されるのは 1 回だけです。
ES6 モジュールと CommonJS モジュールの違い
1. CommonJS モジュールは値のコピーを出力しますが、ES6 モジュールは値への参照を出力します。
// lib.js
var counter = 3;
function incCounter() {
counter++;
}
module.exports = {
counter: counter,
incCounter: incCounter,
};
// main.js
var mod = require('./lib');
console.log(mod.counter); // 3
mod.incCounter();
console.log(mod.counter); // 3
// lib.js
var counter = 3;
function incCounter() {
counter++;
}
module.exports = {
get counter() {
return counter
},
incCounter: incCounter,
};
2. CommonJS モジュールは実行時にロードされ、ES6 モジュールはコンパイル時に出力インターフェイスになります。
3. CommonJS モジュールの require() はモジュールを同期的にロードし、ES6 モジュールの import コマンドは非同期的にロードします。また、モジュールの依存関係の独立した解析フェーズがあります。
3. Node.jsモジュールの読み込み方法
JavaScript には現在 2 種類のモジュールがあります。1 つはES6
module (省略形)でESM
、もう 1 つはCommonJS
module (省略形)ですCJS
。
CommonJS
このモジュールはNode.js
独自仕様であり、ES6
モジュールと互換性がありません。文法に加えて、この 2 つの最も明らかな違いは、CommonJS
モジュールではrequire()
and が使用されmodule.exports
、ES6
モジュールではimport
andが使用されることですexport
。
Node.js
モジュールが接尾辞のファイル名ES6
を取得する必要があります。.mjs
つまり、スクリプト ファイル内でimport
またはコマンドが使用されている限りexport
、サフィックス名を使用する必要があります.mjs
。Node.js がファイルを検出すると.mjs
、それが ES6 モジュールであると見なされます。厳密モードはデフォルトで有効になっており、各モジュール ファイルの先頭で指定する必要はありません"use strict"
。
サフィックス名を に変更したくない場合は、.mjs
プロジェクトファイルのようにフィールドpackage.json
を指定できます。type
module
{
"type": "module"
}
設定すると、プロジェクトの JS スクリプトは ES6 モジュールとして解釈されます。
# 解释成 ES6 模块
$ node my-app.js
現時点で CommonJS モジュールを使用したい場合は、CommonJS スクリプトのサフィックス名を に変更する必要があります.cjs
。フィールドがない場合type
、またはtype
フィールドが の場合commonjs
、.js スクリプトはCommonJS
モジュールとして解釈されます。
要約する:.mjs
ファイルは常に ES6 モジュールとしてロードされ、ファイルは常にモジュール.cjs
としてCommonJS
ロードされ、.js
ファイルのロードはpackage.json
内のフィールドの設定によって異なりますtype
。
CommonJS モジュールは ES6 モジュールをロードします
CommonJSrequire()
コマンドは ES6 モジュールをロードできず、エラーが報告されるため、import()
このメソッドは ES6 モジュールをロードする場合にのみ使用できます。
// 在 CommonJS 模块中运行
(async () => {
await import('./my-app.mjs');
})();
require()
ES6 モジュールがサポートされない理由の 1 つは、ES6 モジュールが同期的にロードされ、トップレベルのコマンドが ES6 モジュール内で使用できるためawait
、同期的にロードできないことです。
ES6 モジュールは CommonJS モジュールをロードします
ES6 モジュールimport
コマンドはモジュールをロードできますCommonJS
が、単一の出力項目だけではなく全体としてのみロードできます。
// 正确
import packageMain from 'commonjs-package';
// 报错
import {
method } from 'commonjs-package';
これは、ES6 モジュールは静的コード分析をサポートする必要があるのに対し、CommonJS モジュールの出力インターフェイスはmodule.exports
静的分析できないオブジェクトであるため、全体としてのみロードできるためです。
単一の出力項目をロードするには、次のように記述できます。
import packageMain from 'commonjs-package';
const {
method } = packageMain;
Node.js の組み込みmodule.createRequire()
メソッドを使用する別の読み込み方法もあります。
// cjs.cjs
module.exports = 'cjs';
// esm.mjs
import {
createRequire } from 'module';
const require = createRequire(import.meta.url);
const cjs = require('./cjs.cjs');
cjs === 'cjs'; // true
上記のコードでは、ES6 モジュールはメソッドを通じてモジュールmodule.createRequire()
をロードできます。CommonJS
ただし、この書き方はES6
と をCommonJS
混ぜることに相当するため、使用はお勧めしません。