ブラウザーで ECMAScript Modules を使うことを調べました 2(CommonJS Modules)

前回の記事では、 RequireJS を使ってみました。 今回は、 CommonJS を使ってみます。

環境

  • babel-cli 6.26.0
  • babel-preset-env 1.6.1
  • babel-plugin-transform-es2015-modules-commonjs 6.26.0
  • Browserify 16.1.1
  • Microsoft Edge 16.16299
  • Firefox 59.0.2

es2015-modules-commonjs

This plugin transforms ES2015 modules to CommonJS.

CommonJS (CJS) Modules/1.1

ES2015 modules to CommonJS transform · Babel

これは、 CommonJS Modules という仕様に合わせてトランスパイルするための、 Babel のプラグインのようです。

CommonJS

公式のページ CommonJS: JavaScript Standard Library は、あまり良い情報が得られませんでした。

Sample Code

math.js

exports.add = function() {
    var sum = 0, i = 0, args = arguments, l = args.length;
    while (i < l) {
        sum += args[i++];
    }
    return sum;
};

increment.js

var add = require('math').add;
exports.increment = function(val) {
    return add(val, 1);
};

program.js

var inc = require('increment').increment;
var a = 1;
inc(a); // 2

Modules/1.0 - CommonJS Spec Wiki


Modules

Stability: 2 - Stable

In the Node.js module system, each file is treated as a separate module. For example, consider a file named foo.js:

const circle = require('./circle.js');
console.log(`The area of a circle of radius 4 is ${circle.area(4)}`);

On the first line, foo.js loads the module circle.js that is in the same directory as foo.js.

Here are the contents of circle.js:

const { PI } = Math;

exports.area = (r) => PI * r ** 2;

exports.circumference = (r) => 2 * PI * r;

The module circle.js has exported the functions area() and circumference(). Functions and objects are added to the root of a module by specifying additional properties on the special exports object.

Variables local to the module will be private, because the module is wrapped in a function by Node.js (see module wrapper). In this example, the variable PI is private to circle.js.

The module.exports property can be assigned a new value (such as a function or object).

Modules | Node.js v9.10.1 Documentation

引用した CommonJS Modules と Node.js Modules のサンプルのコードを見ると exportsrequire が使われていて、似た印象を持ちました。

仕様

CommonJSで定められた仕様としては以下のようなものがある[6] なおModuleとPromiseについては、後に2015年のECMAScript 6で標準規格が定められている。[7]。

CommonJS - Wikipedia

CommonJS Modules が ECMAScript Modules の仕様になった経緯があるようです。 あまり違和感なく使えそうです。

Browserify

CommonJS Modules をブラウザーで使いたかったのですが、そのためには、 Browserify を使ってバンドルすることが一般的だったようです。

browser-side require() the node.js way http://browserify.org/

browserify

require('modules') in the browser

GitHub - browserify/browserify: browser-side require() the node.js way

webpack

2018 年の現在は、バンドルするツールは Browserify だけじゃないみたいです。

Module Methods

This section covers all methods available in code compiled with webpack. When using webpack to bundle your application, you can pick from a variety of module syntax styles including ES6, CommonJS, and AMD.

Module Methods

webpack にも Module のことが記載されていました。 後から webpack のページのこの記載を見たのですが、 ECMAScript Modules のコードをそのままにバンドルすることができるように見えました。 じゃあ、 Babel を使うこともなかったのかな、と思いましたが、今回は、このまま Babel と Browserify を使うことで進めてみます。

Browserify と webpack のトレンドを見ると、 2015 年 3 月までは webpack の方が少なかったようですが、それ以降、 webpack が急激に伸びていました。

Examples

前回の記事と同様に、 MDN Web Docs の export のページの Examples を使って進めてみます。

Babel のインストール

パッケージをインストールしました。

$ npm init -y
$ npm install --save-dev babel-cli babel-preset-env babel-plugin-transform-es2015-modules-commonjs

Babel の設定ファイル

Usage

Via .babelrc (Recommended)

.babelrc

// without options
{
  "plugins": ["transform-es2015-modules-commonjs"]
}

ES2015 modules to CommonJS transform · Babel

Babel の設定ファイル (.babelrc) を次のように作成しました。

{
  "presets": ["env"],
  "plugins": ["transform-es2015-modules-commonjs"]
}

トランスパイル

トランスパイルします。 ECMAScript Modules のコードを src/ ディレクトリーに保存しておいて、 CommonJS Modules のコードを 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';

Object.defineProperty(exports, "__esModule", {
  value: true
});
function cube(x) {
  return x * x * x;
}
var foo = Math.PI + Math.SQRT2;
var graph = {
  options: {
    color: 'white',
    thickness: '2px'
  },
  draw: function draw() {
    console.log('From graph draw function');
  }
};
exports.cube = cube;
exports.foo = foo;
exports.graph = graph;

もとの my-module.js と比較しても Object.definePropertyexports.xxx = xxx; くらいしか違わないので、それほど違和感なく見られました。

main.js

次に、 main.js です。

'use strict';

var _myModule = require('./my-module.js');

_myModule.graph.options = {
  color: 'blue',
  thickness: '3px'
};

_myModule.graph.draw();
console.log((0, _myModule.cube)(3)); // 27
console.log(_myModule.foo); // 4.555806215962888

こちらも require くらいしか違わないので、それほど違和感なく見られました。

Browserify のインストール

トランスパイルしたファイル(モジュール)の依存関係を解決するために、事前に Browserify でバンドルする必要があるようなので、まず、 Browserify をインストールします。

$ npm install --save-dev browserify

バンドル

一括して提供すること。

バンドルとは - IT用語辞典

バンドルします。

$ ./node_modules/.bin/browserify lib/main.js -o lib/bundle.js

browserify の引数にエントリーポイントとなるファイルを指定するようです。 lib/bundle.js ファイルとして出力されます。

commonjs.html

Hello World With Browserify を参考に、 bundle.js を呼び出す HTML ファイルを、次のように作成しました。

<!-- commonjs.html -->
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>CommonJS</title>
  </head>
  <body>
    <h1>CommonJS</h1>
    <script src="bundle.js"></script>
  </body>
</html>

このファイルをトランスパイルしたディレクトリー (lib/) に保存しました。

実行

ブラウザーから commonjs.html を開きます。

すると、コンソールに次のように出力されました。

From graph draw function
27
4.555806215962888

事前のモジュールの依存関係の解決がちゃんとできていたようです。

終わり

RequireJS と比較して、事前にバンドルするところに違いを感じました。

今回は、これで終わりにします。

4 April 2018 追記

次の記事を書きました。