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


目標

moduleのimportを使用したjavascriptを、Babelrollup.jsを利用して、es3相当にトランスパイルします。

前提とする環境

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

▼package.json

{
  "devDependencies": {
    "@babel/cli": "^7.2.3",
    "@babel/core": "^7.2.2",
    "@babel/plugin-transform-member-expression-literals": "^7.2.0",
    "@babel/plugin-transform-property-literals": "^7.2.0",
    "@babel/plugin-transform-property-mutators": "^7.2.0",
    "@babel/plugin-transform-reserved-words": "^7.2.0",
    "@babel/preset-env": "^7.2.3",
    "glob": "^7.1.3",
    "rollup": "^0.68.2",
    "rollup-plugin-babel": "^4.1.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がちょうどいい感じ

インストール

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

babelの設定

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

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

MSを偲び、ここにIE6対応SPAの作り方を記す

▼.babelrc

{
  "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を追加します。

rollup.jsの実行

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

▼bundle.js

const path = require("path");
const rollup = require("rollup");
const babel = require("rollup-plugin-babel");
const glob = require("glob");

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()]
  };
  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などがありますが、このプラグインでは出力ファイルに共通のimportがある場合、その部分をチャンクとしてくくり出してしまいます。
AdobeCCのバッチでは動的なimportが行えないため、node.jsからrollup.jsを利用しています。

▼package.json

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

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