今日から使えるライブラリ製作者のための Rollup 実践教室

## Rollup について

Rollup は Webpack などと同じモジュールバンドラーと呼ばれるものの一種で、複数のファイルやモジュールからなる JavaScript ライブラリを1ファイルにまとめて特定のモジュール形式にあわせて出力するツールだ。

React の開発者が

> Webpack for apps, and Rollup for libraries

と述べているように[^1]、ライブラリ開発に格段に向いており、使用例が増えている。

本エントリでは単なる入門ではなく、実際に使える設定を具体例とともに示していく。NPM でパッケージを公開している人はぜひ導入を検討してみてほしい。とくにビルドのためだけに gulp を使っている人はその簡潔さに驚くだろう。

## インストール

例によって npm でインストールする。なお、下の "i" は "install"、"D" は "--save-dev" の短縮形である。

```sh
npm i -D rollup
```

## 基本形

まずは一番基本的な形から。rollup.config.js という名前で設定ファイルをつくり、そこに記述していく。

```js:rollup.config.js
export default {
  input: './lib/index.js',
  output: {
    file: './dist/index.js',
    format: 'cjs',
  },
};
```

- input … 読み込み元の最上位のファイルパス、いわゆるエントリポイント。
- output.file … 出力先のファイルパス。package.json の "main" と同じ値が入る
- output.format ...モジュール形式。NPM パッケージでは基本 "cjs" すなわち CommonJS を指定する。ほかに ES2015 モジュールや AMD も指定できる（後ほど紹介する）。

ビルドの実行は以下のようなコマンドラインを用いる。

```sh
rollup -c
```

これを package.json の "scripts.prepublishOnly" などに登録しておけば最低限の準備が整う。

## 依存パッケージ

つぎに依存パッケージの扱いを決める。

上の設定のままだと外部のパッケージ（`require('lodash')` など）を読み込むところでエラーが出る。

ふつうならここでそれらをまとめて読み込む手順を紹介するところだが、ライブラリ開発では基本的に依存パッケージをバンドルしないので、そちらを先に解説する。

### 非バンドル用設定

依存パッケージをバンドルから除外するには rollup.config.js の "external" というプロパティに配列形式でモジュール名を列挙すればよい。

下に例を示す。

```js:rollup.config.js
export default {
  input: './lib/index.js',
  output: {
    file: './dist/index.js',
    format: 'cjs',
  },
  external: [
    'lodash',
    'react',
    'react-dom',
  ]
};
```

#### TIPS

package.json の dependencies を機械的に登録したいときは、` external: require('./package.json').dependencies || []` のように記述する（プラグインにあるといい……）。

### バンドル用設定

ライブラリでもポリフィルを読み込んでいたり、プライベートなパッケージを利用している場合などではバンドルしたいことがある。

この場合は "node-resolve" と "commonjs" の2つのプラグインを使う必要がある。前者は NodeJS 流のモジュール名の解決（require の対象を node_modules に探しに行く）のためのもので、後者は CommonJS 流の require を ES2015 の import に変換するためのもの。Rollup はすべてを ES2015 モジュールとして処理するため、こうしたものが必要になる。

#### 手順

NPM でプラグインをインストールし、

```sh
npm i rollup-plugin-node-resolve rollup-plugin-commonjs -D
```

rollup.config.js をつぎのように書き換える。

```js:rollup.config.js
import nodeResolve from 'rollup-plugin-node-resolve';
import commonjs from 'rollup-plugin-commonjs';

export default {
  input: './lib/index.js',
  output: {
    file: './dist/index.js',
    format: 'cjs',
  },
  plugin: [
    nodeResolve(),
    commonjs(),
  ]
};
```

#### esnext 用設定

esnext 版のファイル（モジュールで提供されている場合）を参照したい場合は以下のように "jsnext" オプションを追加する。

```diff
-      nodeResolve(),
+      nodeResolve({ jsnext: true }),
```

## Babel

Rollup を使おうとするひとで Babel を使わないほうが珍しいだろう。

ここでは `babel-core` はすでにインストール済みで、.babelrc も存在することを前提とするが、それでもたいていの場合、前述の理由で .babelrc の出力モジュールを CommonJS から ES2015 に換えなければならない。

この場合、一番簡単なのは "babelrc-rollup" プラグインを使うことだ。なお、このプラグインでは "module" の設定の書き換えのほかに、デフォルトで `babel-external-helpers` の有効化も行われる。

```js:rollup.config.js
import babel from 'rollup-plugin-babel';
import babelrc from 'babelrc-rollup';

export default {
  ...
  plugins: [
    babel(babelrc())
  ]
};
```

ほかにももっと小回りの効くやり方があるが、ここでは割愛する。

## TypeScript

つぎに元の言語に TypeScript を使っている場合だ。まず、公式の "typescript" プラグインは**使ってはならない**。"typescript2" プラグインを使うこと。公式のものはバンドルされているコンパイラが古く、バグも残っていて実用に耐えない。

```sh
npm i rollup-plugin-typescript2 -D
```

このプラグインはデフォルトで "tsconfig.json" を読み込むが、やはりたいていの場合モジュール出力の上書きが必要だ。それも含めると rollup.config.js はつぎのようになる。

```js:rollup.config.js
import typescript from 'rollup-plugin-typescript2';

export default {
  ...
  plugins: [
    typescript({
      tsconfigOverride: {
        compilerOptions: {
          module: "es2015",
          moduleResolution: "node",
        }
      }
    })
  ]
};

```

また特定のディレクトリにキャッシュが作られるので ".rpt2_cache" を .gitignore と .npmignore に足しておこう。

## ES2015 モジュールの出力

せっかくなら ES2015 モジュールでも出力したいだろう。CommonJS との両対応を含めて以下のように簡単にできる。

```js:rollup.config.js
{
  ...
  output:[
    {
      file: './dist/index.js',
      format: 'cjs',
    },
    {
      file: './dist/index.mjs',
      format: 'es2015',
    }
  ]
}
```

やっていることは見てのとおり。ES2015 用の "file" と package.json の "module" エントリを揃えるのを忘れないこと。

## プロダクションビルド

### Minify/Uniglify

uglify プラグインを追加する。

### Source Map

デバッグに必須のソースマップを使うには、istanbul プラグインを追加する。"node_modules" 以下にあるファイルなど特定のファイルを除外することもできる。

両者を含む設定はつぎのようになる。

```js:rollup.config.js
import istanbul from 'rollup-plugin-istanbul';
import uglify from 'rollup-plugin-uglify';

const plugins = [
  // always
  ...
];

if (process.env !== 'production') {
  // dev-only
  plugins.push(istanbul({
    exclude: [
      'test/**/*.js',
      'node_modules/**/*.js'
    ]
  }));
} else {
  // production-only
  plugins.push(uglify());
}

export default {
  ...
  pluguins,
};
```

## UMD モジュールの出力

UMD モジュールでの出力を最近使ったので、追加で記述しておく。

```js:rollup.config.js
export default {
  ...
  output: {
    file: './dist/index.umd.js',
    format: 'umd',
    name: 'YourGreatLibrary',
    global: {
      'jquery': 'jQuery',
      'react': 'React',
      'react-dom': 'ReactDOM',
    }
  }
}
```

新たな項目が2つ出てきている。それぞれについて説明する。

- name …… 自身のパッケージのグローバル名。
- output.global ...… 依存パッケージのパッケージ名と UMD におけるグローバル名の対応を記述する。

注意点として、UMD にないものは自身のバンドルに加えるようにしなければならない（「バンドル用設定」を参照）。

## 参考

- [Rollup Official Guide](https://rollupjs.org/guide/en)
- [Rollup Starter Project](https://github.com/rollup/rollup-starter-project)


[^1]: https://medium.com/webpack/webpack-and-rollup-the-same-but-different-a41ad427058c