きっかけ
TypeScriptやJavaScriptに記述したrequireをRollupでバンドルした時、出力にrequireしているファイルが含まれませんでした。
調査して得られた3通りの解決方法をまとめます。
// 解決したいこと: これがRollupの出力にこのまま出てきてしまった (一つのバンドルファイルにまとめてもらえなかった)
const sample = require("sample.js")
使用したRollupとRollupプラグイン
いずれも執筆日2022-09-25の最新バージョンです。
- rollup @2.79.1
- @rollup/plugin-commonjs @22.0.2
- @rollup/plugin-typescript @8.5.0
サンプルリポジトリ
今回扱う全てのソースコードはGitHubに格納してあります。
動作検証を目的としてメソッド実行やログ出力を含んでいますが、この記事で論点としている処理は同じ記述としてあります。
ljourm/sample-rollup-typescript-require
結論
最初に結論をまとめます。
No. | 方法 | 使用条件 | バンドル後のrequire | サンプルコード(GitHub) |
---|---|---|---|---|
00 | Rollupオプションなし | 特になし | requireがそのまま出力される(実行するとエラー) | 00-first-issue |
01 |
plugin-commonjs にextensions: [".js", ".ts"] を指定 |
tsconfigで"module": "CommonJS" を設定 |
バンドルされる | 01-include-ts-js |
02 |
plugin-commonjs にtransformMixedEsModules: true を指定 |
JavaScriptのみを使用 | バンドルされる | 02-only-js |
03 | 02の設定 + requireをJavaScriptに記述し、それをTypeScriptからimport | JavaScriptとTypeScriptをハイブリッドで使用 | バンドルされる | 03-use-loader |
前提として、そもそもrequireを使うべきではない
可能な場合は require
(CommonJSの記法) を廃止し、 import
(ES Modulesの記法) の使用に切り替えるべきです。
公式ドキュメント、GitHub Issue、StackOverflowなどを参照していくと、どこもimportの使用に誘導されていました。
それでもrequireしなければならない時がある
ですが、先日私が出会ったケースでは以下のような制約・条件となっていました。
- CommonJSのライブラリを使用している。
- そのライブラリはベンダー製で、こちらで変更することができない。
- JavaScriptでなくTypeScriptから使用したい。
検討した上でrequireの継続使用を決定し、問題解消の調査に進みます。
問題となったソースコード
作成したソースコード
// a + b するだけの簡単なサンプルメソッドをexportしている
module.exports.add = function (a, b) {
return a + b;
};
export const output = () => {
// requreによってcalculator.jsを読み込む
const { add } = require("./calculator.js");
// 以降、addメソッドを使って何かする
};
Rollupの出力結果
'use strict';
Object.defineProperty(exports, '__esModule', { value: true });
var output = function () {
// requreによってcalculator.jsを読み込む
require("./calculator.js").add; // !!!!ポイント!!!!
// 以降、addメソッドを使って何かする
};
exports.output = output;
requireがaddメソッドに置き換わることを想定していましたが、そうはなりませんでした。
このコードを実行するとrequire("calculator.js")
でファイルを読み込めず、エラーが発生します。
このrequireをaddメソッドに置き換えることが、この記事の目標です。
解決方法
01: plugin-commonjs
にextensions: [".js", ".ts"]
を指定
まずは、今回紹介する中でTypeScriptにrequireを記述できる唯一の方法です。
サンプルコード: 01-include-ts-js
たいていの場合、プラグイン@rollup/plugin-commonjs
を使っていると思います。
このプラグインのオプションextensions
を変更する方法がplugin-typescriptのドキュメントで紹介されていました。
公式ドキュメント: @rollup/plugin-typescript #Importing CommonJS
プラグイン | 変更するオプション | デフォルト値 | 変更後 |
---|---|---|---|
@rollup/plugin-commonjs |
extensions (読み込み対象の拡張子) |
['.js'] |
['.js', '.ts'] |
ただし、これを実現するためにはtsconfig.json
で"module": "CommonJS"
と設定することも必須のため、適用できないケースも多いと思います。
ドキュメントにもit is not recommended
(=推奨していない)とあり、バンドル時に"module": "esnext"
の使用を推奨する警告も表示されました。
02: plugin-commonjs
にtransformMixedEsModules: true
を指定
次はTypeScriptのことを考えず、JavaScriptとしてrequireを使用する方法です。
サンプルコード: 02-only-js
これも先ほどと同じく、プラグイン@rollup/plugin-commonjs
のオプションから設定します。
公式ドキュメント: @rollup/plugin-commonjs #transformMixedEsModules
参考記事: かもメモ Rollup require がバンドルされないにハマる
プラグイン | 変更するオプション | デフォルト値 | 変更後 |
---|---|---|---|
@rollup/plugin-commonjs |
transformMixedEsModules |
false |
true |
TypeScriptを使用しない場合はこれで全て解決ですが、今回の目標ではTypeScriptの使用を継続したいため、さらに別の方法の模索へと進みます。
03: 02の設定 + requireをJavaScriptに記述し、それをTypeScriptで読み込む
最後に実際に採用した方法です。
これによってTypeScriptとrequireによって参照するCommonJSの共存が実現できました。
サンプルコード: 03-use-loader
この方法ではJavaScriptとTypeScriptのハイブリットによって解決を図ります。
RollupのオプションはNo.2と同じですが、自分にとっては発想の転換が必要だったため、別の方法として記載します。
ソースコード
transformMixedEsModules
が結果的にJavaScriptのみに適用される特性に着目し、requireとexportだけを実現するJavaScriptファイルを新たに作成しました。
// このファイルではrequireした結果をexportするのみとする
export const loadCalculator = () => {
return require("./calculator.js");
};
そして、作成したJavaScriptをTypeScriptから読み込みます。
import { loadCalculator } from "./calculator-loader";
export const output = () => {
const { add } = loadCalculator();
const result = add(1, 2);
};
これによってTypeScriptとrequireによって参照するCommonJSの共存が実現できました。
No.1のようなtsconfig.json
の変更も不要です。
最後に
全部がモダンで綺麗なプロジェクトばかりならよいのですが、そうでないケースもどうしてもあります。
同じようなケースでお役に立てたら幸いです。