CJS を ESM に置き換えるのは難しい場合もあった
- カテゴリ:
- JavaScript
- コメント数:
- Comments: 0
◆ 動的 import は非同期になる
◆ 動的 require が多いと async 関数化しないといけない場所が多い
◆ 非同期化できない場所が存在する
◆ export をトップレベルにしか書けないので ブロック内で export してる場合に工夫が必要
◆ 動的 require が多いと async 関数化しないといけない場所が多い
◆ 非同期化できない場所が存在する
◆ export をトップレベルにしか書けないので ブロック内で export してる場合に工夫が必要
最近は Node.js でも ESModules が使えるので CJS の require で書いていた古いコードを ESModules に置き換えようかとしていたのですが 難しいケースがありました
しかし if 文の中や関数の中など 使うときに始めて require しているケースもあります
読み込むモジュールが重めで 必ず使うものでもないと こういう風に遅延ロードしてる場合が多いです
開発時のみ使うデバッグ系のモジュールをロードも本番時にはロードしないように動的に require です
require は同期実行だったのですが import にすると非同期実行になります
そのせいで 動的に require をしていた部分は async 関数にしないといけません
async 関数はそれを使う側も async 関数にする必要があり 伝播します
ネットワークリクエストやファイルの扱いなど非同期要素が入る部分は async 関数でも良いですが import が発生するだけで async 関数になるのはあまり気が進みません
モジュールの遅延読み込みのためだけで 本来は非同期である必要がないものですし
多くの関数を async 関数化して 使用箇所に await を書くのが面倒という以外にも 非同期処理化できないところもあるという問題があります
ライブラリなどでは同期処理しか受け付けてない部分もあり Promise 化しない値を返さないといけないケースもあります
また 処理的に同期的に行わないといけなくて 処理の間に別の処理が入ってはいけないなんてこともありえます
対応できる作りに変更することは不可能ではないと思いますが マルチスレッド処理のような複雑で面倒なことになってくるので ESModules 化のためにわざわざそんなことはしたくないです
そんな感じで同期処理でないといけないところはありえます
動的 import を同期処理化することはできないので こういう場合は CJS を使い続けるしかないです
また 静的 import 化できない require の例を遅延読み込みのためとしましたが 環境変数やデータベースやネットワークから取得する事前にわからない値を元に require するモジュールが変わるという場合も動的にインポートするしかないです
ブロックスコープの中でエクスポートしてるケースです
数行から数十行くらいの小さい処理の結果を複数エクスポートしていて その数が多いです
ひとつひとつに 1 ファイルを使うほどでもなく 全体が 1 ファイルにまとまっていた方が見やすいのでこうしてることがあります
全部をトップレベルに書くと他ブロック用の一時的な変数が混ざるので個別にブロックスコープを使っています
しかし ESModules の export はトップレベルにしか書けません
先に export する変数名を列挙しておいて各ブロックスコープ内でそこへ代入するようなことをしないといけません
let になることや export 内容の変更時にブロックの他に一番上の export の一覧も修正しないといけないなど面倒が増えます
ブロックを即時実行化すると const にでき 離れたところの修正は不要ですが 単純なブロックスコープにできた CJS の方が良かったです
do 式が使えるようになれば このあたりの不満点もなくなるのですが 実現するのかどうかってレベルですしね
1 つのブロックで複数をエクスポートする場合
そんなわけで まとめて ESModules 化しようかなと考えてましたが 思ってた以上に変更すべき箇所があったのと 場合によってはできないケースもあったので CJS で作ったものはもうそのままでいいかなと言う気がしてます
非同期になる
モジュールの一番上で require してる部分なら単純に import に置き換えできますしかし if 文の中や関数の中など 使うときに始めて require しているケースもあります
const foo = require("foo")
function something() {
const bar = require("bar")
// ...
}
// ...
読み込むモジュールが重めで 必ず使うものでもないと こういう風に遅延ロードしてる場合が多いです
開発時のみ使うデバッグ系のモジュールをロードも本番時にはロードしないように動的に require です
require は同期実行だったのですが import にすると非同期実行になります
そのせいで 動的に require をしていた部分は async 関数にしないといけません
async 関数はそれを使う側も async 関数にする必要があり 伝播します
ネットワークリクエストやファイルの扱いなど非同期要素が入る部分は async 関数でも良いですが import が発生するだけで async 関数になるのはあまり気が進みません
モジュールの遅延読み込みのためだけで 本来は非同期である必要がないものですし
多くの関数を async 関数化して 使用箇所に await を書くのが面倒という以外にも 非同期処理化できないところもあるという問題があります
ライブラリなどでは同期処理しか受け付けてない部分もあり Promise 化しない値を返さないといけないケースもあります
また 処理的に同期的に行わないといけなくて 処理の間に別の処理が入ってはいけないなんてこともありえます
対応できる作りに変更することは不可能ではないと思いますが マルチスレッド処理のような複雑で面倒なことになってくるので ESModules 化のためにわざわざそんなことはしたくないです
そんな感じで同期処理でないといけないところはありえます
動的 import を同期処理化することはできないので こういう場合は CJS を使い続けるしかないです
また 静的 import 化できない require の例を遅延読み込みのためとしましたが 環境変数やデータベースやネットワークから取得する事前にわからない値を元に require するモジュールが変わるという場合も動的にインポートするしかないです
export をトップレベルに書かないといけない
もうひとつ ESModules 化し辛いところがありましたブロックスコープの中でエクスポートしてるケースです
// aaa
{
const value = getAAA()
exports.aaa = value
}
// bbb
{
const value = getBBB()
exports.bbb = value
}
// ...
数行から数十行くらいの小さい処理の結果を複数エクスポートしていて その数が多いです
ひとつひとつに 1 ファイルを使うほどでもなく 全体が 1 ファイルにまとまっていた方が見やすいのでこうしてることがあります
全部をトップレベルに書くと他ブロック用の一時的な変数が混ざるので個別にブロックスコープを使っています
しかし ESModules の export はトップレベルにしか書けません
先に export する変数名を列挙しておいて各ブロックスコープ内でそこへ代入するようなことをしないといけません
let になることや export 内容の変更時にブロックの他に一番上の export の一覧も修正しないといけないなど面倒が増えます
export let aaa, bbb
/// aaa
{
const value = getAAA()
aaa = value
}
/// bbb
{
const value = getBBB()
bbb = value
}
ブロックを即時実行化すると const にでき 離れたところの修正は不要ですが 単純なブロックスコープにできた CJS の方が良かったです
do 式が使えるようになれば このあたりの不満点もなくなるのですが 実現するのかどうかってレベルですしね
/// aaa
export const aaa = (() => {
const value = getAAA()
return value
})()
/// bbb
export const bbb = (() => {
const value = getBBB()
return value
})()
1 つのブロックで複数をエクスポートする場合
export const { aaa, bbb } = (() => {
const valueA = getAAA()
const valueB = getBBB()
return { aaa: valueA, bbb: valueB }
})()
そんなわけで まとめて ESModules 化しようかなと考えてましたが 思ってた以上に変更すべき箇所があったのと 場合によってはできないケースもあったので CJS で作ったものはもうそのままでいいかなと言う気がしてます