前回の記事では、 CommonJS を使ってみました。 今回は、 SystemJS を使ってみます。
環境
- babel-cli 6.26.0
- babel-preset-env 1.6.1
- babel-plugin-transform-es2015-modules-systemjs 6.24.1
- SystemJS 0.21.1
- Microsoft Edge 16.16299
- Firefox 59.0.2
es2015-modules-systemjs
This plugin transforms ES2015 modules to SystemJS.
これは、 SystemJS という仕様に合わせてトランスパイルするための、 Babel のプラグインのようです。
SystemJS
Dynamic ES module loader
SystemJS
Configurable module loader enabling dynamic ES module workflows in browsers and NodeJS.
module loader と記載されています。 モジュールのローダーのようです。 モジュールを定義するための仕様、という意味合いは弱い印象を持ちました。
じゃあ、どんな定義のモジュールを読み込めるのか? module formats のページを見てみました。
Module Formats
The following module formats are supported:
- esm: ECMAScript Module
- cjs: CommonJS
- amd: Asynchronous Module Definition
- global: Global shim module format
- system: System.register or System.registerDynamic compatibility module format *(Need more? Check the plugins.)
systemjs/module-formats.md at master · systemjs/systemjs · GitHub
読み込むモジュールの定義は ECMAScript Module でも CommonJS でも Asynchronous Module Definition でもいいみたいです。
To load ES6 code with in-browser transpilation, one of the following transpiler plugins must be configured:
- Babel
- TypeScript
- Traceur
ES6 のコードはトランスパイルする必要があるみたいです。 ひとつ前の引用で、 ECMAScript Module でも読み込めるように記載がありましたが、そのままの ECMAScript Module では読み込めなくて、トランスパイルする必要があるみたいでした。
この記事を書き終えた後で、 ECMAScript Module をそのまま読み込んでみたら、 Firefox のコンソールに次のエラーが出力されました。
XML パースエラー: 整形式になっていません。
URL: file:///path/src/main.js
行番号: 1, 列番号: 1:
Error: Unable to dynamically transpile ES module
A loader plugin needs to be configured via `SystemJS.config({ transpiler: 'transpiler-module' })`.
Instantiating file:///path/src/main.js
Loading ./main.js
Microsoft Edge では何も起きませんでした。 正しい結果はコンソールに出力されないし、エラーもコンソールに出力されませんでした。
Examples
前回の記事と同様に、 MDN Web Docs の export のページの Examples を使って進めてみます。
Babel のインストール
パッケージをインストールしました。
$ npm init -y
$ npm install --save-dev babel-cli babel-preset-env babel-plugin-transform-es2015-modules-systemjs
Babel の設定ファイル
Usage
Via .babelrc (Recommended)
.babelrc
Without options:
{ "plugins": ["transform-es2015-modules-systemjs"] }
Babel の設定ファイル (.babelrc) を次のように作成しました。
{
"presets": ["env"],
"plugins": ["transform-es2015-modules-systemjs"]
}
トランスパイル
トランスパイルします。 ECMAScript Modules のコードを src/ ディレクトリーに保存しておいて、 SystemJS のコードを lib/ ディレクトリーに出力します。
$ ./node_modules/.bin/babel src/ -d lib/
src/main.js -> lib/main.js
src/my-module.js -> lib/my-module.js
my-module.js
トランスパイルされたファイルです。 最初に、 my-module.js です。
'use strict';
System.register([], function (_export, _context) {
"use strict";
var foo, graph;
function cube(x) {
return x * x * x;
}
return {
setters: [],
execute: function () {
_export('foo', foo = Math.PI + Math.SQRT2);
_export('graph', graph = {
options: {
color: 'white',
thickness: '2px'
},
draw: function draw() {
console.log('From graph draw function');
}
});
_export('cube', cube);
_export('foo', foo);
_export('graph', graph);
}
};
});
System.register
関数を使うようです。
_export
関数で export しているようで、独特な印象を受けました。
SystemJS って、モジュールのローダーだけかと思っていましたが、モジュールの定義の仕様もあったようでした。
main.js
次に、 main.js です。
'use strict';
System.register(['./my-module.js'], function (_export, _context) {
"use strict";
var cube, foo, graph;
return {
setters: [function (_myModuleJs) {
cube = _myModuleJs.cube;
foo = _myModuleJs.foo;
graph = _myModuleJs.graph;
}],
execute: function () {
graph.options = {
color: 'blue',
thickness: '3px'
};
graph.draw();
console.log(cube(3)); // 27
console.log(foo); // 4.555806215962888
}
};
});
こちらでも System.register
関数が使われています。
System.register
関数の中の return
しているオブジェクトの中に、メインの処理を記述するのが SystemJS の特徴なのかな。
そのオブジェクトの execute
のプロパティがメインっぽい印象を受けました。
setters
プロパティはモジュールの依存関係かな。
systemjs.html
トランスパイルしたコードを呼び出すためにはどうすればよいのか? getting started のページを見てみました。
Loading Modules
Any URL can be loaded as a module with standard URL syntax:
<script src="system.js"></script> <script> // loads relative to the current page URL SystemJS.import('./local-module.js'); // load from an absolute URL directly SystemJS.import('https://code.jquery.com/jquery.js'); </script>
systemjs/getting-started.md at master · systemjs/systemjs · GitHub
system.js のファイルを先に読み込んでおいて、 SystemJS.import
関数にエントリーポイントのファイルのパスを渡してあげればよさそうです。
これを参考に、次のような HTML ファイルを作成しました。
<!-- systemjs.html -->
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>SystemJS</title>
</head>
<body>
<h1>SystemJS</h1>
<script src="https://cdnjs.cloudflare.com/ajax/libs/systemjs/0.21.0/system.js"></script>
<script>
// loads relative to the current page URL
SystemJS.import('./main.js');
</script>
</body>
</html>
system.js のファイルは CDN から読み込むようにしてみました。
このファイルをトランスパイルしたディレクトリー (lib/) に保存しました。
実行
ブラウザーから systemjs.html を開きます。
すると、コンソールに次のように出力されました。
From graph draw function
27
4.555806215962888
モジュールが読み込めているようです。
終わり
SystemJS は、 Dynamic ES module loader と記載されていたように、ブラウザー上で動的にモジュールの依存関係を解決するもののようでした。 これは AMD(RequireJS) と同様の特徴のように感じました。 SystemJS は AMD の仕様のモジュールも読み込めるようなので、 3 つ前の記事でも RequireJS を使うこともなかったのかもしれません。
前回の記事では、 CommonJS Modules の仕様のコードを Browserify を使って事前にバンドルしましたが、SystemJS を使えばバンドルしなくてもブラウザーで使えるようでした。
SystemJS は比較的最近出てきたのかな。 読み込むモジュールの定義は CommonJS でも AMD でもよいところが便利そうでした。
たくさんモジュールがある場合は、ブラウザーから何度もリクエストを送ることになるのかな。 そうなると、パフォーマンス的には事前にバンドルする仕組みの方ががいいのかな。
4 April 2018 追記
次の記事を書きました。