自作TypeScriptパッケージをそこそこいい感じにビルドする (Rollup / tsc / PostCSS / Babel)

## やりたいこと

[Rollup](https://rollupjs.org/)で下記を達成したいと思います。

- 自作TypeScriptプロジェクトのビルド
    - ES6 Modules (`import ...`) での出力
        - Webpackで参照するにはこちらの形式が有利です（TreeShaking）
    - CommonJS (`require(...)`) での出力
        - NodeJSで参照するにはこちらの形式が必要です
    - 型宣言(`*.d.ts`)の出力
    - ソースマップ(`*.js.map`)の出力
- ビルドしたJavaScriptをBabelでトランスパイルする
- ソース中で参照しているCSSはPostCSSで処理しながら、一つのファイルとして出力
- 外部ライブラリ(`node_modules`配下)は特に展開しないで外部参照のまま
- ソースコードウォッチのサポート

内容的には[TypeScriptパッケージをwebpack (Tree Shaking)とNode.js両方で使えるようにする(ES6 Modules / CommonJS)
](https://qiita.com/knjname/items/dcbc3620d5118f0009a5) の続きです。

（2021/03/09 追記） [tsdxを使うほうが遥かに楽なので](https://zenn.dev/knjname/articles/cacd33c2149268bd71a4) 手動で構成せずに **[tsdx](https://github.com/formium/tsdx) を使うことをおすすめします。**

## リポジトリ

下記に今回の構成のプロジェクトがあります。

https://github.com/knjname/2020-05-21_rollupconfig

## 基本的な戦略

- Rollupを使う理由は、あまりにビルド工程が面倒なためです。純粋なtscなどのツールだけでやりたいことを達成しようとすると、辛い。
    - Web出力抜きのCRAが欲しいぐらい。
- Babelを使う予定ですが、あまり速度面以外のメリットがないので、TypeScript→JavaScriptの変換はtscをそのまま使うこととします。`@babel/preset-typescript` は使いません。
    - どうせ型宣言ファイルの出力も必要なので、tscをそのまま使ったほうがメリットがあります。
    - 逆にBabelには `@babel/preset-env` でtscの吐いた文法をダウングレードしてもらいます。
- そういうことで、TypeScriptはそのまま tsc を使い、esnextの文法で、es6モジュールを出力してもらいます。
    - Rollup にCommonJSに変換してもらいます。
- 外部ライブラリは展開しません。基本的にRollupを使うユーザは外部ライブラリ込みの大きな塊のJavaScriptを出したいケースがほとんどだと思いますが、今回はやりません。最終的にこのライブラリを使うプロジェクトでバンドルすればいいでしょう。

## で、どうやるの

最小構成の設定ファイルを記述していきます。

### package.json

```js:package.json
{
  "name": "rollupconfig",
  "version": "1.0.0",
  "license": "MIT",

  // CommonJS / ES6 で出力先がわかれます。型定義は *.js の隣に *.d.ts が生えるイメージ
  "main": "build/commonjs/index.js",
  "module": "build/es/index.js",

  // ビルド＆ウォッチコマンド
  "scripts": {
    "build": "rollup --config",
    "start": "rollup --config --watch"
  },

  "devDependencies": {
    // 基本的に下記があればいいです
    "@babel/core": "^7.9.6",
    "@babel/preset-env": "^7.9.6",
    "@rollup/plugin-babel": "^5.0.0",
    "@rollup/plugin-typescript": "^4.1.1",
    "rollup": "^2.10.5",
    "rollup-plugin-postcss": "^3.1.1",
    "tslib": "^2.0.0",
    "typescript": "^3.9.3",
  },

}
```

### tsconfig.json

```js:tsconfig.json
{
  // とにかく tsc が素直に最新のJSを出す感じにする
  "compilerOptions": {
    // Babelが変換するので素のまま出してもらいましょう
    "target": "ESNext",
    // Rollupの変換に任せましょう
    "module": "ES6",

    "strict": true,
    "esModuleInterop": true,
    "sourceMap": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true
  },
  "include": ["src"]
}
```

### rollup.config.js

もっとマシな書き方があるでしょうが、ベタ書きします。

```js:rollupconfig.js
import babel from "@rollup/plugin-babel";
import postcss from "rollup-plugin-postcss";
import typescript from "@rollup/plugin-typescript";

const extensions = [".ts", ".tsx", ".js", ".jsx"];

// [{CommonJS用の設定, ES6モジュール用の設定}] という感じで複数エクスポートする
export default [

  {
    // いわゆるプロジェクトレベルのbarrelファイルです。ここからたどり着けないものはTree Shakingされます。
    // 生成が面倒な人は barrelsby とか使おうね。
    input: "src/index.ts",

    // これをtrueにしない場合はすべて index.js に集約されます。
    preserveModules: true,

    // 出力指定
    output: {
      dir: "build/commonjs",
      format: "cjs",
      exports: "named",
      sourcemap: true,
    },

    // Rollupのプラグイン指定です。TypeScript → Babel → PostCSSと処理していきます。
    plugins: [
      postcss({
        extract: true,
      }),
      babel({
        extensions,
      }),
      typescript({
        // ここに書く内容は基本的にtsconfig.jsonへのオーバーライドとなります。
        // そして、型宣言ファイルを出す場合は、下記３つのオプションが今のところ必須となります。
        // 詳しくは https://github.com/rollup/plugins/issues/61 を参照。
        // あと、 declarationDir は↑に書いてある output.dir のサブディレクトリじゃないと駄目です。
        declaration: true,
        rootDir: "src",
        declarationDir: "build/commonjs",
      }),
    ],
  },

  // 基本的にはCommonJS版と同じ。
  {
    input: "src/index.ts",
    preserveModules: true,
    output: {
      dir: "build/es6",
      format: "es",
      exports: "named",
      sourcemap: true,
    },

    plugins: [
      postcss({
        extract: true,
      }),
      babel({
        extensions,
      }),
      typescript({
        declaration: true,
        rootDir: "src",
        declarationDir: "build/es6",
      }),
    ],
  },
];

```

### .babelrc

単にJSの文法をダウングレードさせるpresetの指定だけですね。

```js:.babelrc
{
  "presets": [["@babel/preset-env", { "modules": false }]],
}
```

なるべくtscよりBabelで文法変換させるほうが柔軟性があがると思います。

### PostCSSの設定

今回はなしです。置けば効くと思います。たぶん。

### あとは

あとは `src/` の中に `*.ts` 置くなり `*.css` を置くなり。好きにしてください。もちろん、Reactとか使うと追加で設定が必要になると思われます。

```console
# ウォッチ
$ yarn start

# ビルド
$ yarn build
```

下記のようにファイルが作られると思います。

```console
$ find build
build
build/es6
build/es6/_virtual
build/es6/_virtual/_rollupPluginBabelHelpers.js.map
build/es6/_virtual/_rollupPluginBabelHelpers.js
build/es6/index.js
build/es6/utils
build/es6/utils/a.d.ts
build/es6/utils/a.js
build/es6/utils/a.js.map
build/es6/utils/b.js.map
build/es6/utils/b.js
build/es6/utils/b.d.ts
build/es6/index.css
build/es6/index.js.map
build/es6/index.d.ts
build/commonjs
build/commonjs/_virtual
build/commonjs/_virtual/_rollupPluginBabelHelpers.js.map
build/commonjs/_virtual/_rollupPluginBabelHelpers.js
build/commonjs/index.js
build/commonjs/utils
build/commonjs/utils/a.d.ts
build/commonjs/utils/a.js
build/commonjs/utils/a.js.map
build/commonjs/utils/b.js.map
build/commonjs/utils/b.js
build/commonjs/utils/b.d.ts
build/commonjs/index.css
build/commonjs/index.js.map
build/commonjs/index.d.ts
```

## まとめ

ただこれだけの設定を作るだけで結構時間を使っちゃいました。JSのバンドル周りはマジで沼です。