Rollup なぜrollup-plugin-commonjs、rollup-plugin-node-resolveプラグインが必要なのか

Rollupで複数のファイルを1つにまとめる際にrollup-plugin-commonjsrollup-plugin-node-resolveプラグインが使われる。この2つのプラグインはいったいどんな役割があるのだろうか。Rollupの概要をおさえた上で、環境をつくり動きを確認していく。

Rollupとは

Rollupとは、モジュールバンドラ(module bundler)だ。
モジュール(module)は機能ごとに分割されたファイルのことで、バンドラ(bundler)はそれらのモジュールをまとめることだ。RollupはES2015(ES6)をベースにしている。つまり、特に設定しなくてもimportexport文をつかって複数ファイルを1ファイルにまとめることができる。

Rollupのインストール

さっそくRollupが動く環境を準備していく。 package.jsonを用意し、rollupをインストールする。
それからsrc、distディレクトリを用意する。

npm install --save-dev rollup

package.json

{
  "devDependencies": {
    "rollup": "^1.3.0"
  }
}

ディレクトリ

.
├── dist
├── package-lock.json
├── package.json
└── src

RollupでES2015のimport/export文のファイルをまとめる

ES2015のimport、export文で書かれた2ファイルを1ファイルにまとめられるか確認していく。
calculator.jsindex.jsファイルを用意する。

2つの引数を取り、引数同士を足し合わせた結果を返す関数calculator.jsを作成する。そして、そのcalculator.jsで定義した関数をindex.jsから呼び出す。

calculator.js

export default function(a, b) {
    return a + b;
}

index.js

import add from './calculator.js';

const result = add(1, 2);
console.log(result);

calculator.jsindex.jsを1ファイルにまとめるためにrollupコマンドを実行する。
--fileオプションで出力ファイルを指定し、 --formatオプションで出力ファイル形式を指定する。出力ファイル形式として指定しているumdはサーバー、ブラウザいずれでも動く形式だ。umdを指定した場合は--nameが求められる。

$ npx rollup src/index.js --file dist/bundle.js --format umd --name "calculator"

src/index.js → dist/bundle.js...
created dist/bundle.js in 36ms

生成されたbundle.jsを見てみると、1ファイルにまとめられていることがわかる。

bundle.js

(function (factory) {
    typeof define === 'function' && define.amd ? define(factory) :
    factory();
}(function () { 'use strict';

    function add(a, b) {
        return a + b;
    }

    const result = add(1, 2);
    console.log(result);

}));

nodeコマンドで生成されたファイルが本当に動くのか確認する。

$ node dist/bundle.js 
3

うん、動いてる。たしかにES2015のimportexportで書かれたファイルを1ファイルにまとめることができた。

RollupだけではCommonJSのrequire/exportsのファイルをまとめられない

CommonJSではexportsオブジェクトに格納されたモジュールを、require関数で呼び出すことでファイルを分割している。

さきほどのcalculator.jsindex.jsをCommonJSで書き直してみる。

calculator.js

exports.add = function (a, b) {
    return a + b;
}

index.js

const add = require('./calculator.js').add;

const result = add(1, 2);
console.log(result);

node src/index.jsと実行するとさきほどと同じように計算結果を出力することができる。 再度、rollupコマンドでcalculator.jsindex.jsを1つにまとめてみる。npx rollup src/index.js --file dist/bundle.js --format umd --name "calculator"を実行すると次のファイルが出力される。






 






(function (factory) {
	typeof define === 'function' && define.amd ? define(factory) :
	factory();
}(function () { 'use strict';

	const add = require('./calculator.js');

	const result = add(1, 2);
	console.log(result);

}));

ES2015のimport/export文で書かれたファイルは1ファイルにまとめられていた。しかし、CommonJSのrequire/exportsで書かれたファイルはrequireがそのまま残ってしまい、1ファイルにまとめられなかった。

念のため実行してみると、予想通りエラーになる。






 





$ node dist/bundle.js 
module.js:559
    throw err;
    ^

Error: Cannot find module './calculator.js'
    at Function.Module._resolveFilename (module.js:557:15)
    at Function.Module._load (module.js:484:25)
    at Module.require (module.js:606:17)
    at require (internal/module.js:11:18)

Cannot find moduleと出力されてmoduleが見つからないと怒られてしまう。rollupだけではCommonJS形式でかかれたファイルを1つにまとめられないのだ。

rollup-plugin-commonjsでrequire/exportsのファイルをまとめる

require/exportsで指定したファイルを1つのファイルにまとめるには、rollup-plugin-commonjsプラグインが必要だ。さっそくrollup-plugin-commonjsをインストールする。

npm install --save-dev rollup-plugin-commonjs

package.json




 



{
  "devDependencies": {
    "rollup": "^1.3.0",
    "rollup-plugin-commonjs": "^9.2.1",
  }
}

さらに、インストールしたプラグインを使うために、rollup.config.jsという設定ファイルを用意する。






 




.
├── dist
│   └── bundle.js
├── package-lock.json
├── package.json
├── rollup.config.js
└── src
    ├── calculator.js
    └── index.js

先ほどまでコマンドラインで実行していたことが、設定ファイルを使っても同じことができるか確認する。設定ファイルに入力先、出力先と出力形式を指定する。

rollup.config.js

export default {
  input: 'src/index.js',
  output: {
    file: 'dist/bundle.js',
    format: 'umd',
    name: 'calculator'
  }
}

設定ファイルを読み込んで実行するにはnpx rollup -cとする。すると先ほどまでコマンドラインで実行していたのと同じファイルが出力される。

(function (factory) {
	typeof define === 'function' && define.amd ? define(factory) :
	factory();
}(function () { 'use strict';

	const add = require('./calculator.js').add;

	const result = add(1, 2);
	console.log(result);

}));

次はrollup-plugin-commonjsを使う。rollup.config.jspluginsに追加する。

 








 
 
 


import commonjs from 'rollup-plugin-commonjs'

export default {
  input: 'src/index.js',
  output: {
    file: 'dist/bundle.js',
    format: 'umd',
    name: 'calculator'
  },
  plugins: [
    commonjs(),
  ],
}

npx rollup -cを実行してみると、requireで指定したファイルが展開されて1ファイルになっている!

bundle.js

(function (global, factory) {
    typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
    typeof define === 'function' && define.amd ? define(factory) :
    (global = global || self, global.calculator = factory());
}(this, function () { 'use strict';

    var add = function (a, b) {
        return a + b;
    };

    var calculator = {
    	add: add
    };

    const add$1 = calculator.add;

    const result = add$1(1, 2);
    console.log(result);

    var src = {

    };

    return src;

}));

このファイルを実行してみると、1ファイルでもちゃんと実行できるようになった。

$ node dist/bundle.js 
3

ブラウザでも同様に動くことを確認する。 index.htmlを作成し、生成したJavaScriptを読み込ませる。 このHTMLをGoogle Chromeでひらき、デベロッパーツールを見てみる。

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>Rollup Test</title>
    <script src="dist/bundle.js"></script>
</head>
<body>
</body>
</html>

よし。ブラウザでも1ファイルで実行できていることが確認できた。 ブラウザでバンドルしたファイルが動くことを確認する

まとめられたファイルに関数がexportされるようにする

ところで、先ほど生成したbundle.jsをよく見てみると、空のオブジェクトをreturnしている。これは生成元のindex.jsで何もexportしていないからだ。

    var src = {

    };

    return src;

関数がexportできることを確認するために、index.jsに次のような関数を定義して、npx rollup -cを実行する。

const add = require('./calculator.js').add;

module.exports = function (a, b) {
    return add(a, b) * 2;
}

生成されたbundle.jsにはsrcに関数が代入されており、exports.defaultで関数が定義されている。

bundle.js

















 
 
 
 
 





(function (global, factory) {
    typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
    typeof define === 'function' && define.amd ? define(['exports'], factory) :
    (global = global || self, factory(global.calculator = {}));
}(this, function (exports) { 'use strict';

    var add = function (a, b) {
        return a + b;
    };

    var calculator = {
    	add: add
    };

    const add$1 = calculator.add;

    var src = function (a, b) {
        return add$1(a, b) * 2;
    };

    exports.default = src;

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

}));

bundle-use.jsを作成しこの関数が読み込めることを確認する。

bundle-use.js

const addAndDouble = require('./dist/bundle.js').default;

console.info(addAndDouble(1,2));

node bundle-use.jsを実行すると、関数が実行できていることがわかる。

$ node bundle-use.js 
6

サードパーティのライブラリ(node_modules)を使う

さて、自分自身でファイルを1つのファイルにまとめることはできた。
次はサードパーティのライブラリを1つのファイルにまとめることができるか確認していく。
ここでは便利な関数が多く用意されているLodashを読み込んでみる。

$ npm install --save lodash

package.jsonlodashがインストールされている。







 



{
  "devDependencies": {
    "rollup": "^1.3.0",
    "rollup-plugin-commonjs": "^9.2.1"
  },
  "dependencies": {
    "lodash": "^4.17.11"
  }
}

生成元のindex.jslodashのadd関数を追加する。

index.js

const add = require('./calculator.js').add;
const _ = require('lodash');

module.exports = function (a, b) {
    return _.add(add(a, b) * 2, 3);
}

npx rollup -cで実行するとUnresolved dependenciesとコンソールに出力される。どうやらlodashが読み込まれていないようだ。




 







$ npx rollup -c

src/index.js → dist/bundle.js...
(!) Unresolved dependencies
https://rollupjs.org/guide/en#warning-treating-module-as-external-dependency
lodash (imported by src/index.js, commonjs-external:lodash)
(!) Missing global variable name
Use output.globals to specify browser global variable names corresponding to external modules
lodash (guessing 'lodash')
created dist/bundle.js in 27ms

いちおう出力されたファイルをみてみるが、lodashのadd関数はどこにも定義されていない。
rollup-plugin-commonjsだけではサードパーティのライブラリを使えないのだ。

dist.js

(function (global, factory) {
    typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('lodash')) :
    typeof define === 'function' && define.amd ? define(['exports', 'lodash'], factory) :
    (global = global || self, factory(global.calculator = {}, global.lodash));
}(this, function (exports, lodash) { 'use strict';

    lodash = lodash && lodash.hasOwnProperty('default') ? lodash['default'] : lodash;

    var add = function (a, b) {
        return a + b;
    };

    var calculator = {
    	add: add
    };

    const add$1 = calculator.add;


    var src = function (a, b) {
        return lodash.add(add$1(a, b) * 2, 3);
    };

    exports.default = src;

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

}));

rollup-plugin-node-resolveでサードパーティのライブラリを読み込めるようにする

サードパーティのライブラリを使うには、rollup-plugin-node-resolveプラグインを使う必要がある。

$ npm install --save-dev rollup-plugin-node-resolve

package.json





 






{
  "devDependencies": {
    "rollup": "^1.3.0",
    "rollup-plugin-commonjs": "^9.2.1",
    "rollup-plugin-node-resolve": "^4.0.1"
  },
  "dependencies": {
    "lodash": "^4.17.11"
  }
}

rollup-plugin-node-resolveをインストールしたら、設定ファイルのpluginsでプラグインを読み込む。

rollup.config.js


 










 



import commonjs from 'rollup-plugin-commonjs'
import resolve from 'rollup-plugin-node-resolve';

export default {
  input: 'src/index.js',
  output: {
    file: 'dist/bundle.js',
    format: 'umd',
    name: 'calculator'
  },
  plugins: [
    commonjs(),
    resolve(),
  ],
}

npx rollup -cでファイルを生成すると、今度はlodashが定義されている!
これで無事サードパーティのライブラリを含めて1つのファイルにまとめられるようになった。

dist.js





















 



















(function (global, factory) {
    typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
    typeof define === 'function' && define.amd ? define(['exports'], factory) :
    (global = global || self, factory(global.calculator = {}));
}(this, function (exports) { 'use strict';

    var add = function (a, b) {
        return a + b;
    };

    var calculator = {
    	add: add
    };

    var commonjsGlobal = typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {};

    function createCommonjsModule(fn, module) {
    	return module = { exports: {} }, fn(module, module.exports), module.exports;
    }

    var lodash = createCommonjsModule(function (module, exports) {
    (function() {
    // ...略(lodashの関数が定義されている)
    }.call(commonjsGlobal));
    });

    const add$1 = calculator.add;


    var src = function (a, b) {
        return lodash.add(add$1(a, b) * 2, 3);
    };

    exports.default = src;

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

}));

まとめ

  • ES2015のimport/exportで書かれた自分でつくったファイルを1つにまとめるならrollupだけあればよい。
  • CommonJSのrequire/exportsで書かれたファイルを1つにまとめるにはrollup-plugin-commonjsプラグインが必要。
  • サードパーティのライブラリを読み込むにはrollup-plugin-node-resolveが必要。

・参考
https://github.com/rollup/rollup
https://github.com/rollup/rollup-plugin-commonjs
https://github.com/rollup/rollup-plugin-node-resolve
http://wiki.commonjs.org/wiki/Modules/1.1