Vue.js 3.3.4でwebpack + vue-loaderでSFCをバンドルするとエラーになる話

Vue CLIやViteといった環境構築ツールを使わずに、手動でVue.jsやwebpackをインストールして開発環境を構築しようとしたところ、SFC利用段階でエラーに遭遇しました。備忘録として解決に至るまでの流れを解説します。

## 結論

+ Node.jsはある程度新しいものを使いましょう

## 背景

Vue.js学習のため、Windows 10 Home上にUbuntu Server 22.04.3の仮想環境を立てて、そこに学習用環境を構築することにしました。

昨今、Vue.jsを利用する場合はVue CLIやViteを使って環境構築をすることが推奨されるかとは思いますが、今回は最小構築を目指して必要なソフトウェアを1つ1つ手動で導入していくことにしました。

Vue.jsの導入・試用とwebpackによるバンドルまでいけたので、SFCでアプリを組めるようにVue Loaderを導入して動かしたところで問題が発生しました。

なお、当事象に関連するnpmパッケージとそのバージョンは以下のとおりです。

+ vue : 3.3.4
+ vue-loader : 17.2.2
+ webpack : 5.88.2
+ webpack-cli : 5.14

## 問題

Vue Loaderをインストールし、webpackの設定を修正してからバンドルを実行したところ、次のようなエラーメッセージが出力されました。

```
エラー: @vitejs/plugin-vue では、vue (>=3.2.13) または @vue/compiler-sfc が依存関係ツリーに存在する必要があります。
```

ずいぶんと奇妙なエラーメッセージです。

まず「@vitejs/plugin-vue」というパッケージを導入した覚えはありません。node_modulesやpackage-lock.jsonにもそのようなパッケージは見当たりませんでした。名前から察するにViteのプラグインなのでしょう。

続く文言も変です。「3.2.13以降の『vue』か、あるいは『@vue/ccompiler-sfc』を入れてね☆」と教えてくれているのですが、そもそもインストールしている「vue」のバージョンは3.3.4なので問題ないはずです。加えて「@vue/compiler-sfc」はVue.js 3.2.13以降で「vue」をインストールすると同時にインストールされるようになっています。よって問題ないはずです。

## 原因調査 (1)

まずはStack Overflowを始めとする、いくつかのITコミュニティを回りました。

同様のエラーに遭遇されている方を何人か見つけ、そこに書かれている解決策を試しましたが全く解決しませんでした。

## 原因調査 (2)

やりたくないのですが、とりあえずコードを開いてエラー個所を特定することにしました。

調べた結果、vue-loaderのファイルの1つである「node_modules/vue-loader/dist/compiler.js」でエラーが発生していることが分かりました。以下が左記ファイルの全文です。

```js:compiler.js
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.compiler = void 0;
try {
    // Vue 3.2.13+ ships the SFC compiler directly under the `vue` package
    // making it no longer necessary to have @vue/compiler-sfc separately installed.
    exports.compiler = require('vue/compiler-sfc');
}
catch (e) {
    try {
        exports.compiler = require('@vue/compiler-sfc');
    }
    catch (e) {
        throw new Error(`@vitejs/plugin-vue requires vue (>=3.2.13) or @vue/compiler-sfc ` +
            `to be present in the dependency tree.`);
    }
}
```

Vue.jsインストール時に同梱されるSFCコンパイラ「vue/compiler-sfc」を読み込み、それが何らかの理由で失敗した場合は個別にインストールされるSFCコンパイラ「@vue/compiler-sfc」を読み込み・公開を試み、それも失敗したら「@vitejs/plugin-vue requires vue (>=3.2.13) or @vue/compiler-sfc to be present in the dependency tree.」というエラーメッセージを出す処理です。

つまり上記2パッケージの読み込みに失敗したからエラーが出ているわけですね。

## 原因調査 (3)

「読み込むべきnpmパッケージが存在しないのからエラーになっているのかな？」と思ったのですが、ファイルマネージャーから存在していることを確認しました。

そこで、試しに以下のような動作検証用スクリプトを組んで動かしてみることにしました。

```js:test.js
require('vue/compiler-sfc');
```

```
$ node test.js
```

想定では正常完了するはずだったのですが以下のようにエラーが出ました。

```
/node_modules/@vue/compiler-sfc/dist/compiler-sfc.cjs.js:16397
    #fillNegs() {
             ^

SyntaxError: Unexpected token '('
    at wrapSafe (internal/modules/cjs/loader.js:915:16)
    at Module._compile (internal/modules/cjs/loader.js:963:27)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:1027:10)
    at Module.load (internal/modules/cjs/loader.js:863:32)
    at Function.Module._load (internal/modules/cjs/loader.js:708:14)
    at Module.require (internal/modules/cjs/loader.js:887:19)
    at require (internal/modules/cjs/helpers.js:74:18)
    at Object.<anonymous> (node_modules/vue/compiler-sfc/index.js:1:18)
    at Module._compile (internal/modules/cjs/loader.js:999:30)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:1027:10)
```

SFCコンパイラを構成するファイルの1つ「compiler-sfc.cjs」の16,397行目で構文エラーが発生しました。

## 原因調査 (4)

該当箇所を見てみたところ「AST」というクラスのなかで「fillNegs」というプライベートメソッドを定義していました。ここの構文が誤っているとのことです。

ただ、ざっと見た感じでは問題があるようには見られませんでした。確認にはVisual Studio Codeを用いたのですが、Visual Studio Codeの自動構文解析処理にも引っかかっていませんでした。

## もしや……

そんなこんなでコードを眺めていたとき、ふとJavaScriptのプライベートクラス機能がNode.jsでサポートされていないのではないかという考えに至りました。

検証として、以下のようなスクリプトを組んで動かしたところ、案の定エラーになりました。

```js:test.js
class Example {
  constructor() {
    this.#echo();
  }
  #echo() {
    console.log("fooooooooooooooooooooooooooooo");
  }
}

new Example();
```

```
$ node test.js
test.js:5
  #echo() {
       ^

SyntaxError: Unexpected token '('
    at wrapSafe (internal/modules/cjs/loader.js:915:16)
    at Module._compile (internal/modules/cjs/loader.js:963:27)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:1027:10)
    at Module.load (internal/modules/cjs/loader.js:863:32)
    at Function.Module._load (internal/modules/cjs/loader.js:708:14)
    at Function.executeUserEntryPoint [as runMain] (internal/modules/run_main.js:60:12)
    at internal/main/run_main_module.js:17:47
```

エラーの内容まで丸っきり同じ構文エラーということで、結論としてはNode.jsでプライベートクラスがサポートされていないのにもVue Loaderのコード内で関わらず使ったのが原因でした。

めでたし、めでたし。

## いや、それはおかしい

……と、最初は思ったのですがよくよく考えると不自然です。

第1に、こんな単純なエラーにVue Loaderの開発者が気づかないなんてことがあるでしょうか。

第2に、プライベートクラス機能は発表されてからそこそこ経ちます。仮にNode.jsでプライベートクラスが使えないにしても、その場合は「Node.jsではプライベートクラスがサポートされていません」といったエラーメッセージが出てもおかしくないでしょう。

ここで私は学習環境に導入したNode.jsのバージョンが「12.22.9」であることに気づきました。当記事執筆時点ではLTS版として「18.17.1」が登場していますので、かなり古いバージョンです。

Node.jsバージョン管理ツール「n」を用い、Node.jsを18.17.1 LTSに更新をかけたところ正常にバンドルされたことを確認しましました。

## 結論

1. Ubuntu公式リポジトリに上がっているNode.jsのバージョンは12.22.9と非常に古い
2. Node.js 12.22.9ではプライベートクラス機能がサポートされていないのでwebpackとVue Loaderを組み合わせたVue.js SFCのバンドルは失敗する
3. 解決方法の1つとして、Node.jsのバージョンを上げることで対処可能
4. Vue CLIかViteを使っておけば良かったと、ちょっぴり後悔