import/exportとmodule.exports/require

概要

Node.js、ES6、Typescriptを一気に調べていたせいで、いろいろなパターンのモジュール読み込み方法に出くわして、ちょっと混乱したので、整理しました。

Node.jsのモジュール読み込み

Node.jsのモジュール読み込みは以下のような形になります。

// node/myModule.js

class myModule {
    constructor() {
        console.log('Hello, I am a module');
    }

    hello() {
        console.log('hello!');
    }

    goodbye() {
        console.log('goodbye!');
    }
}

function test() {
    console.log("hoge");
};

//// test関数とmyModuleクラスをエクスポート
module.exports.test = test;
module.exports.myModule = myModule;
// node/main.js

//// モジュール読み込み
var mod = require('./myModule');

mod.test();
var m = new mod.myModule();
m.hello();

Node.jsは module.exportsでエクスポートを行い、 requireで読み込みを行います。

Node.jsの形式なので、そのままNode.jsで実行できます。

$ node node/main.js
hoge
Hello, I am a module
hello!

ES6(ECMAScript2015)のモジュール読み込み

ES6のモジュール読み込みは以下のような形になります。

// es6/myModule.js

export class myModule {
    constructor() {
        console.log('Hello, I am a module');
    }

    hello() {
        console.log('hello!');
    }

    goodbye() {
        console.log('goodbye"');
    }
}

export function test() {
    console.log('hoge');
}
// es6/main.js

import {myModule,test} from './myModule';

test();
var m = new myModule();
m.hello();

ES6では、exportをクラス定義や関数定義の前に付けてエクスポートを行い、importで読み込みを行います。

このままでは、Node.jsで実行することができません。

$ node es6/main.js
es6/main.js:1
(function (exports, require, module, __filename, __dirname) { import {myModule,test} from './myModule';
                                                              ^^^^^^

SyntaxError: Unexpected token import
    at createScript (vm.js:80:10)
    at Object.runInThisContext (vm.js:139:10)
    at Module._compile (module.js:599:28)
    at Object.Module._extensions..js (module.js:646:10)
    at Module.load (module.js:554:32)
    at tryModuleLoad (module.js:497:12)
    at Function.Module._load (module.js:489:3)
    at Function.Module.runMain (module.js:676:10)
    at startup (bootstrap_node.js:187:16)
    at bootstrap_node.js:608:3

ES6からES5に変換してくれるBabelを使うと以下のようなソースコードになります。

$ babel es6/myModule.js
'use strict';

Object.defineProperty(exports, "__esModule", {
    value: true
});

var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();

exports.test = test;

function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }

var myModule = exports.myModule = function () {
    function myModule() {
        _classCallCheck(this, myModule);

        console.log('Hello, I am a module');
    }

    _createClass(myModule, [{
        key: 'hello',
        value: function hello() {
            console.log('hello!');
        }
    }, {
        key: 'goodbye',
        value: function goodbye() {
            console.log('goodbye"');
        }
    }]);

    return myModule;
}();

function test() {
    console.log('hoge');
}
$ babel es6/main.js
'use strict';

var _myModule = require('./myModule');

(0, _myModule.test)();
var m = new _myModule.myModule();
m.hello();

Node.jsのmodule.exports/require形式に変換されていますね。

Typescriptのモジュール読み込み

Typescriptでは3パターンの方法が用意されています。

ES6と同じexport/import

Typescriptでは、ES6と同じ形式でモジュールの読み込みをサポートしています。 ほぼ同じなのでソースは省略します。

export =import = require()

また、以下のような形式もサポートしています。

// ts/myModule.ts

class myModule {
    constructor() {
        console.log('Hello, I am a module');
    }

    hello() {
        console.log('hello!');
    }

    goodbye() {
        console.log('goodbye!');
    }
}

function test() {
    console.log('hoge');
}

export = {myModule, test};
// ts/main.ts

import mod = require("./myModule");

mod.test();
var m = new mod.myModule();
m.hello();

/// reference path

読み込むソース(例ではmain.ts)で、/// <reference path="{ファイルパス}" />を記述する方法です。 この方法を使っていると、読み込まれる側(例ではmyModule.ts)では、exportなどの記述は不要になります。

// ts/myModule.ts

class myModule {
    constructor() {
        console.log('Hello, I am a module');
    }

    hello() {
        console.log('hello!');
    }

    goodbye() {
        console.log('goodbye!');
    }
}

function test() {
    console.log('hoge');
}
// ts/main.ts

/// <reference path="./myModule.ts" />

//import mod = require("./myModule");

test();
var m = new myModule();
m.hello();

まとめ

Node.js、ES6、Typescriptでそれぞれモジュールの読み込みの方式が定義されていて、ややこしかったので、まとめてみました。特にES6の方式で記述しているソースをNode.jsで実行しようとしてエラーが出てしまって、よく分かってませんでした。

まだまだいろいろな書き方はあると思いますが、今回は基本的なことだけにとどめました。