@babel/core@7.13.7とnode-semver@7.0.0とwebpackを併用してハマった話
TL;DR
@babel/core@7.13.7以下をwebpackと一緒に使うとnode-semver@7.0.0でハマるかもしれない
はじめに
仕事でブラウザ上のエディタでmarkdownを書くとリアルタイムにReactでどんなコンテンツが表示されるかを確認できるコンテンツシミュレーターみたいなものを使っている。
このシミュレーターで書けるmarkdownは本来のmarkdownをちょっと拡張して、独自にJS書けたり、インラインでJS関数やオブジェクトの参照ができたりする。
このシミュレーターが既存のPJでは動いているのに、別のPJで新しくセットアップしようとしたら動かないという事象を調査したら、意外なところまで行ったのでその経緯と顛末を書く
発端
あるPJ(以下A PJ)でこんな感じのmdを書くとシミュレーターのコンパイルが失敗するという事象で困ってると相談を受けた。
こんにちは、${context.name}さん。
->
JavaScriptの構文エラーが発生しました ...(Trace的なやつ)
本当はこうなってほしい
こんにちは、${context.name}さん。
->
こんにちは、田中さん
コンテンツを以下のようにすると、特に問題なくシミュレーターが動くので、どうも${context.name}
のところのJSの評価がうまく動かないらしい
こんにちは、世界
なぜエラーになるのか
このシミュレーターの仕組みは、エディタ上で与えられたmdコンテンツをworkerでコンパイルして、JSを評価して表示するコンテンツを生成する。
コンパイルでmarkedとbabelを使っている。
表示するためにReactのコンポーネントを書いておくという感じ。
とりあえずTraceが出てるので、宇宙より壮大なnode_modules配下のbabelされたりしたJSたちにひたすら潜ってlogを挿したりしながら、エラーメッセージをたどっていく。
これが宇宙の旅?
すると最終的に以下に辿り着いた
transform error Error: [BABEL] /[anonymous]: Cannot find module './functions/satisfies' (While processing: "base$0") at webpackEmptyContext (eval at ../../node_modules/@babel/core/node_modules/semver sync recursive (82363a058d1f41f6955e.worker.js:1872), <anonymous>:2:10) at lazyRequire (index.js?0488:5) at Object.get [as satisfies] (index.js?0488:12) at Object.assertVersion (config-api.js?bf03:91) at eval (index.js?8294:252) ...
なんだか、@babel/coreが抱えているsemverでmoduleが読み込めないらしい
node_modulesの宇宙からsemverのpackage.json
を見つけ出してmainのindex.js
を読んでみる
const lrCache = {} const lazyRequire = (path, subkey) => { const module = lrCache[path] || (lrCache[path] = require(path)) return subkey ? module[subkey] : module } const lazyExport = (key, path, subkey) => { Object.defineProperty(exports, key, { get: () => { const res = lazyRequire(path, subkey) Object.defineProperty(exports, key, { value: res, enumerable: true, configurable: true }) return res }, configurable: true, enumerable: true }) } lazyExport('re', './internal/re', 're') ...(略) lazyExport('satisfies', './functions/satisfies') ...(略)
lazyyRequireの機構を自前で実装しているらしい。
logを差し込んで試すと、const module = lrCache[path] || (lrCache[path] = require(path))
がORの右辺に落ちてrequire(path)
でmoduleが見つからずにエラーになっているっぽい。
bundle時に静的に決まっていないrequireをbundlerで解決できないのはそれはそうという感じ(webpackの挙動に詳しくないので違うかもしれない)
なぜ動いているのか
エラーの原因は分かったが、今度はなぜ以前設定したPJ(以下B PJ)では今でも動いているのか謎なので同様に調査
semberのpackage.json
のmainにあるsemver.js
がこちら
exports = module.exports = SemVer var debug /* istanbul ignore next */ if (typeof process === 'object' && process.env && process.env.NODE_DEBUG && /\bsemver\b/i.test(process.env.NODE_DEBUG)) { debug = function () { var args = Array.prototype.slice.call(arguments, 0) args.unshift('SEMVER') console.log.apply(console, args) } } else { debug = function () {} } ...(略)
なるほど、全然違う
というかmainに書いてある値が違うのでsemverのバージョン違いが原因らしい
そのsemverはどこからやってきたのか
エラーになるA PJのsemver
のversionは7.0.0
これは、@babel/core
の7.13.1
から指定されている
エラーにならないB PJのsemverのversionは5.7.1
これは、@babel/core
の7.12.3
から指定されている
@babel/coreが上がってるのは気づいていたが、minor versionだから大丈夫やろと油断していた
いつ上がったのか@babel/coreのリリースブログやchangelogを読んでみるが、それっぽい記述がない
blameするとこいつで変わったらしい
BABEL_8でbreakingするよ的なflagが立っていて、@babel/core
のv7.13系からsemver
がv7系に上がっている
これがPR
PRマージされた後に、@babel/coreとwebpackを併用するとsemverが7.0.0に固定されてissueが起こると書いてある
このコメント、ブラウザでコードエディタやってて@babel/core使ってると書いてあるので、まったく同じユースケースでびっくり
そのあとsemver@v7.1系を使うとBREAKINGなので、6.3系にダウングレードするPR出されて、まだリリースされていない。これが2日前。
ということで、次のbabelがリリースされたら解決しそう
おまけ1: semverのpreload機構
2019年12月ぐらいからちょこちょこissueにはなっていたらしい github.com
ドキュメントが更新されたりしていたが、以下のPRで廃止になっている
おまけ2: semverのsemver
node-semverのレポジトリを調査していたら、node-semverがsemverに従ってないというコメントがついていて、メンテナからするとそんなことはないよなあと思ったので貼っておく
おわりに
ということで、@babelのv7.13系の踏むのが難しいバグを踏み抜いたお話でした。
@babelのv7.13.0は2021-02-22にリリースされて、まだ4日しか経っていないのにもうv7.13.7なので、すぐにfixされるでしょう。
@babelもnode-semverももともとはnode.jsで使うことを想定されていて、ブラウザ環境で使われるのはエコシステムが発展したが故なので、この辺にハマるときはきっちりハマります。
メンテナはユースケースが複雑なので非常に大変だと思いますが、メンテしてもらっている身だとありがたい限りです。自分もブログを書いて他にハマる人がいたときの一助になればよいなと思いつつ、おしまい。