
ES6, CommonJS, AMD, RequireJS, UMD, System.JS, TypeScriptのModule を完全に理解する
JavaScript, TypeScriptを利用していると様々なモジュールシステムに関するワードが出てきます。
AMD, CommonJS, ES6, ES5, Webpack, Babel, RequireJS, UMD, SystemJS, System.register, module target etc...
これらの用語の関係は複雑で混乱しがちな部分です。
この記事は、1つで全てを理解できる決定版を目指して記述しています。
少々長くなりますが、なんども振り返るノートとして利用していただければと思います。
では本題に入ります。
JavaScriptのModuleとは
JavaScriptの新しい仕様ECMAScript2015(ES6)から、Moduleという概念が登場しました。JavaScriptのModuleファイル内で宣言された変数・関数・クラスなどはexportされない限りファイル内からしかアクセスできず、外部からアクセスしたい場合は明示的にexport・importされる必要があります。
ECMAScript2015では、`import`や`export`がTopレベルに記述されたファイルはModule,そうでないファイルはModuleでないJavaScriptファイルとして扱われます。
ModuleでないJavaScript
ModuleでないJavaScriptファイルでは、宣言した変数や関数はグローバル空間からアクセスできます。そのため1つのファイルで宣言した変数は他のファイルから意図しない形で上書かれる可能性があります。
例えば、以下のような`hello.js`と`goodbye.js`ファイルを考えます。
hello.js
var message = "hello"
function hello() {
return message
}
goodbye.js
var message = "good bye"
function goodBye() {
return message
}
次に、`hello.js`と`goodbye.js`を読み込み、`hello()`と`goodBye()`の結果を出力してみます。
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
<script src="./hello.js"></script>
<script src="./goodbye.js"></script>
<script>
console.log(hello())
console.log(goodBye())
</script>
</head>
<body>
<div id="editor"></div>
</body>
</html>
コンソールには2回`good bye`が出力されます。
good bye
good bye
これは、`goodbye.js`が`hello.js`で定義した変数`message`を上書きしてしまったことが原因です。
このように、通常のJavaScriptでは変数や関数定義がグローバル空間で定義されるため、複数のファイル間で予期しない変更を及ぼしてしまいます。
ModuleであるJavaScriptファイル
Moduleを利用した場合, 変数`message`はファイルの内部に定義されるため、別のファイルから上書きされません。
Moduleの形式には後述するように様々な形式がありますが、今回はNode.jsを利用したModuleシステムの例を記述します。
hello.js
var message = "hello"
module.exports = function hello() {
return message
}
goodbye.js
const hello = require("./hello")
var message = "good bye"
function goodBye() {
return message
}
console.log(hello())
console.log(goodBye())
実行結果は以下のようになり、`message`が上書きされていないことがわかります。
$ node goodbye.js
hello
good bye
Moduleを利用することでJavaScriptを分割したフィアルで管理することができるようになります。
Module format, Module loader, Module bundler, Module Transpiler
JavaScriptのModuleにはES6, CommonJS, AMDなど様々なModuleシステムがあります。また、RequireJS, SystemJS, Webpackなど様々なModuleを扱うためのツールに関する用語があります。
これらの違いを理解するために、まずはModule formats, Module loaders, Moduler bundlers, Module Transpilerの区別を理解しましょう。
Module format
Module formatは、Moduleを扱うための構文です。ECMAScript2016以前にはモジュールを扱う構文がありませんでした。そのため、Moduleを扱うための構文が考え出されました。
JavaScriptはサーバーサイドで実行されることもあれば、ブラウザー環境で実行されることもあります。そのため、実行環境毎に様々な形式のModuleを記述するFormatが存在しています。
- CommonJS: サーバーサイド(Node.js)のモジュール構文
- Asynchronous Module Definition (AMD): ブラウザ環境のモジュール構文
- Universal Module Definition (UMD): サーバーサイドとブラウザ環境両方に対応したモジュール構文
- ECMAScript 2016(ES6)モジュール: ES6で利用できるモジュール構文
- System.register format: ES6の構文をES5に対応させるための構文
CommonJS
CommonJSはサーバーサイド(Node.js)環境で利用されるモジュールの構文です。`require`と`module.exports`などの構文を利用します。
var lib1 = require('./lib1');
module.exports = function(){
// your code
}
Asynchronous Module Definition (AMD)
AMDはブラウザ環境での利用を想定したモジュールシステムです。`define`を利用した以下のような記述がAMDの構文です。
define(['lib1', 'lib2'], function (lib1, lib2) {
return function () {};
});
AMDは名前の通り非同期的にモジュールを読み込む仕組みです。ブラウザ環境ではモジュールの読み込みによってブラウザの描写が遅くならないようにする必要があるために非同期の仕組みを利用します。
Universal Module Definition (UMD)
UMDはAMDとCommonJSの両方の性質を持つ記述で、JavaScriptの実行環境に応じて自動的に利用するモジュールシステムが切り替わる仕組みとなっています。
コードを読むと`define`や`module`といったグローバル変数が定義されているかどうかに応じてモジュールの処理を切り替えているのがわかります。
(function (root, factory) {
if (typeof define === 'function' && define.amd) {
// AMD. Register as an anonymous module.
define(['b'], factory);
} else if (typeof module === 'object' && module.exports) {
// Node. Does not work with strict CommonJS, but
// only CommonJS-like environments that support module.exports,
// like Node.
module.exports = factory(require('b'));
} else {
// Browser globals (root is window)
root.returnExports = factory(root.b);
}
}(this, function (b) {
//use b in some fashion.
// Just return a value to define the module export.
// This example returns an object, but the module
// can return a function as the exported value.
return {};
}));
ES6 module format
ECMAScript2015(ES6)からモジュールの構文が登場しました。CommonJS, AMDなどはJavaScriptにモジュール構文がないために生み出されたものですが、まだES6に対応していない環境が多いためES6だけが使われるという状態ではありません。
lib.js
export function sayHello(){
console.log('Hello');
}
import { sayHello } from './lib';
sayHello();
// => Hello
System.register format
ES6で登場したModuleシステムをES5でも使えるようにするのがSystem.register formatです。
ES6の記述
import { p as q } from './dep';
var s = 'local';
export function func() {
return q;
}
export class C {
}
ES5にコンパイル
System.register(['./dep'], function($__export, $__moduleContext) {
var s, C, q;
function func() {
return q;
}
$__export('func', func);
return {
setters: [
// every time a dependency updates an export,
// this function is called to update the local binding
// the setter array matches up with the dependency array above
function(m) {
q = m.p;
}
],
execute: function() {
// use the export function to update the exports of this module
s = 'local';
$__export('C', C = $traceurRuntime.createClass(...));
}
};
});
後述するBabelなどのTranspilerでES6のモジュール構文をES5で利用できるSystem.register formatに変換して利用したります。
Module loader
Moduleの構文で記述されたJavaScriptは、それ単体ではModuleの依存関係を解消することができません。Moduleの構文を解釈して、依存関係を解消するための仕組みは別途必要となります。これがModule loaderです。Module loaderはJavaScriptの実行時に依存関係を解消して依存ファイルを読み込みます。
Module loaderには以下のようなものがあります。
- Node.js loader: CommonJS構文のModule loader
- RequireJS: AMD構文のModule loader
- SystemJS: AMD, CommonJS, UMD, System.registerの構文を読み込むModule loader
Module loaderのイメージ
* 購入した上で間違いや不足部分などがあればリクエストに応じて追記しようと思います。
ここから先は
¥ 500
この記事が気に入ったらチップで応援してみませんか?