Babel + rollup.js で ES3環境向けトランスパイル

## 目標

moduleのimportを使用したjavascriptを、[Babel](https://babeljs.io/)と[rollup.js](https://rollupjs.org/guide/en)を利用して、es3相当にトランスパイルします。

### 前提とする環境

本記事で前提とする環境は以下の通りです。
バージョンが異なる場合、この記事の内容は適用できない場合があります。
記事を読む前にバージョンをご確認ください。

*▼package.json*

```json
{
  "devDependencies": {
    "@babel/cli": "^7.22.5",
    "@babel/core": "^7.22.5",
    "@babel/plugin-transform-member-expression-literals": "^7.22.5",
    "@babel/plugin-transform-property-literals": "^7.22.5",
    "@babel/plugin-transform-property-mutators": "^7.22.5",
    "@babel/plugin-transform-reserved-words": "^7.22.5",
    "@babel/preset-env": "^7.22.5",
    "@rollup/plugin-babel": "^6.0.3",
    "glob": "^10.3.1",
    "rollup": "^3.26.0"
  }
}
```

## なぜそんな古い規格を？

es3はブラウザ上からはすっかり消滅しましたが、アプリケーションに組み込まれた独自のjavascriptエンジンではまだ存在します。
私の場合、Adobe CCアプリのバッチ処理のためjsを使用しています。このjsエンジンが非常に古いため、es3相当のコードしか実行できません。
importを利用できれば、共通処理をくくり出せるため、バッチの生産性が高くなります。
どうにかしてimportを使用できる状況でコードを書き、Adobe CC上で実行できるようにトランスパイルします。

## なぜrollup.js?
以前は同様の目的のため、モジュールバンドラーにwebpackを利用していました。
webpackは高機能な反面、バンドル先のファイルに独自の関数を追加します。これはwebpack自身の機能を実現するためです。
この独自の追加部分がES5を想定して作成されているため、バンドル元ファイルによってはES3環境での動作を妨げます。

そこでモジュールバンドラーにrollup.jsを利用することにしました。
こちらはjsファイルのバンドルに特化したモジュールバンドラーです。rollup.jsのプラグインを追加しない状態では、独自の関数をバンドル先に埋め込むことがありません。

rollup.jsの概要と利点については、こちらの記事でより詳しく解説されています。

[Rollupがちょうどいい感じ](https://qiita.com/cognitom/items/e3ac0da00241f427dad6)

## インストール

上記のpackage.jsonに指定されているモジュールをインストールしてください。
すべてnpmからインストールが可能です。

## babelの設定

babelの設定は以下の通りです。
`"@babel/preset-env", { "loose" : true }`でLoose modeを有効にすると、`Object.defineProperties`を使用しないようにトランスパイルが行われます。また`{ "modules": false }`を指定することで、importしたモジュールが単一のファイルにまとめられます。

babelの設定についてはこちらの記事を参考とさせていただきました。ありがとうございます。

[MSを偲び、ここにIE6対応SPAの作り方を記す](https://qiita.com/boiyaa/items/e10a1d52c232010e145e)

*▼.babelrc*

```json
{
  "presets": [
    [
      "@babel/preset-env",
      {
        "loose": true,
        "modules": false
      }
    ]
  ],
  "plugins": [
    "@babel/transform-member-expression-literals",
    "@babel/transform-property-literals",
    "@babel/plugin-transform-property-mutators",
    "@babel/plugin-transform-reserved-words"
  ]
}
```

プラグインはそれぞれ対応した構文のトランスパイルを行います。
その他のes2015以降の機能を利用したい場合は、対応するbabel-pluginを追加してください。
例として、Object.assignを使用したい場合は[@babel/plugin-transform-object-assign](https://babeljs.io/docs/en/babel-plugin-transform-object-assign/)を追加します。

## rollup.jsの実行

次にrollup.jsを利用してimportしたファイルをバンドルします。
今回はrollup.jsをCLIではなくAPIから利用しています。
これはrollupのinputファイルを[node-glob](https://github.com/isaacs/node-glob)を利用して複数指定するためです。

*▼bundle.js*

```js
const rollup = require("rollup");
const babel = require("@rollup/plugin-babel");
const glob = require("glob");
const path = require("path");

const srcDir = "./src";
const distDir = "./bin";

// ./src以下のjsファイルのリストを取得する。ただし_から始まるファイルとmodulesディレクトリは除外する。
const entries = glob.sync("**/*.js", {
  ignore: ["modules/**/*.js", "**/_*.js"],
  cwd: srcDir
});

//リストアップしたjsファイルをバンドルする。
for (let entry of entries) {
  const inputOptions = {
    input: path.resolve(srcDir, entry),
    plugins: [babel({ babelHelpers: "bundled" })],
  };
  const outputOptions = {
    format: "cjs",
    file: path.resolve(distDir, entry)
  };
  build(inputOptions, outputOptions);
}

async function build(inputOptions, outputOptions) {
  const bundle = await rollup.rollup(inputOptions);
  await bundle.write(outputOptions);
}
```

rollup.jsの複数input指定を行うプラグインとしては[rollup-plugin-multi-input](https://github.com/alfredosalzillo/rollup-plugin-multi-input)などがありますが、このプラグインでは出力ファイルに共通のimportがある場合、その部分をチャンクとしてくくり出してしまいます。
AdobeCCのバッチでは動的なimportが行えないため、node.jsからrollup.jsを利用しています。

*▼package.json*

```json
"scripts": {
  "bundle": "node bundle.js"
}
```

最後に`bundle`コマンドを実行すれば`bin`ディレクトリにjsファイルが出力されます。