2022年1月22日 • ☕️ 5 min read

packageのバージョンアップに伴い、Next.jsを12.0.8にアップデートしました。しかし、今まで動いたソースコードがruntimeエラーになり、動かなくなりました。エラーの内容はこれです。

Copy
Unhandled Runtime Error
TypeError: Cannot read properties of undefined (reading '_className')

軽く調べてみると気づいたが、class中のfunctionにthisがundefinedになっているので、this.xxxにアクセスできなくなりました。そして、create-next-appで新しいプロジェクトを作って試してみても同じエラーが出ているので、これはNext.jsのバグだとわかりました。しかし、一体どこに問題があり、どう解決すればいいですか。自分のアプローチを紹介します。

アプローチ

1. create-next-appで新しいプロジェクトを作る

シンプルなアプリケーションを作って、Next.js以外の問題を排除できるでしょう。

2. 問題発生したpackage.jsonと照合する

完全に再現するため、各ライブラリのバージョンを一致させる必要があります。

3. Next.jsを起動し、バンドル済みのソースコードを確認する

開発モードのソースコードは下記のフォルダにあります。

.next/static/chunks/xxx

自分の場合、index.tsxの中に、mockClient.tsを参照しています。mockClient.tsthis.xxxのundefinedエラーが出ているので、.next/static/chunks/pages/index.jsを探せばいいでしょう。

  • developモードでは、アクセスしないとビルドが走らないので、まず、http://localhost:3000/にアクセスします。
  • そして、.next/static/chunks/pages/index.jsmockClientの部分を取り出しましょう。

めちゃわかりづらいものとなります。

webpack-origin.png

整形してみましょう。問題となるfunctionは18行目〜36行目のものとなります。

Copy
function MockClient() {
          var className = arguments.length > 0 && arguments[0] !== void 0 ? arguments[0] : "MockClient";
        _classCallCheck(this, MockClient);
        this.getByArrowFuncWithoutParam = _asyncToGenerator(_Users_shinjo_Study_next_bug_1208_node_modules_next_dist_compiled_regenerator_runtime_runtime_js__WEBPACK_IMPORTED_MODULE_0___default().mark(function _callee() {
              return _Users_shinjo_Study_next_bug_1208_node_modules_next_dist_compiled_regenerator_runtime_runtime_js__WEBPACK_IMPORTED_MODULE_0___default().wrap(function _callee$(_ctx) {
                  while(1)switch(_ctx.prev = _ctx.next){
                      case 0:
                          _ctx.next = 2;
                          return Promise.resolve("".concat(this._className, ": getByArrowFunc"));
                    case 2:
                        return _ctx.abrupt("return", _ctx.sent);
                    case 3:
                    case "end":
                        return _ctx.stop();
                }
            }, _callee, this);
        }).bind(this)).bind(this);
        this.getByArrowFunc = (function() {              var _ref = _asyncToGenerator(_Users_shinjo_Study_next_bug_1208_node_modules_next_dist_compiled_regenerator_runtime_runtime_js__WEBPACK_IMPORTED_MODULE_0___default().mark(function _callee(name) {                  return _Users_shinjo_Study_next_bug_1208_node_modules_next_dist_compiled_regenerator_runtime_runtime_js__WEBPACK_IMPORTED_MODULE_0___default().wrap(function _callee$(_ctx) {                      while(1)switch(_ctx.prev = _ctx.next){                          case 0:                              _ctx.next = 2;                              return Promise.resolve("[".concat(this._className, " ~ ").concat(name, "]: getByArrowFunc"));                        case 2:                            return _ctx.abrupt("return", _ctx.sent);                        case 3:                        case "end":                            return _ctx.stop();                    }                }, _callee, this);            }).bind(this)).bind(this);            return function(name) {                  return _ref.apply(this, arguments);            };        })();        this._className = className;
    }
    _createClass(MockClient, [
          {
              key: "getByNormalFunc",
            value: function getByNormalFunc(name) {
                  return _asyncToGenerator(_Users_shinjo_Study_next_bug_1208_node_modules_next_dist_compiled_regenerator_runtime_runtime_js__WEBPACK_IMPORTED_MODULE_0___default().mark(function _callee() {
                      return _Users_shinjo_Study_next_bug_1208_node_modules_next_dist_compiled_regenerator_runtime_runtime_js__WEBPACK_IMPORTED_MODULE_0___default().wrap(function _callee$(_ctx) {
                          while(1)switch(_ctx.prev = _ctx.next){
                              case 0:
                                  _ctx.next = 2;
                                  return Promise.resolve("[".concat(this._className, " ~ ").concat(name, "]: getByNormalFunc"));
                            case 2:
                                return _ctx.abrupt("return", _ctx.sent);
                            case 3:
                            case "end":
                                return _ctx.stop();
                        }
                    }, _callee, this);
                }).bind(this))();
            }
        }
    ]);
    return MockClient;
}

正直相変わらずわかりづらいですが、}).bind(this)).bind(this);をみた瞬間でやばい感じでしょう。

そこで問題を気づきました。

Copy
}).bind(this)).bind(this);

return function(name) {  return _ref.apply(this, arguments);};

functionを新しく作ってreturnため、IIFEにあるthisがundefinedになってしまいました。

Copy
return Promise.resolve("[".concat(this._className, " ~ ").concat(name, "]: getByArrowFunc"));

4. 結論

ここまでわかってきました。これはNext.jsの新しいバンドルswcのバグです。issueを探してみて見つけました。

https://github.com/swc-project/swc/pull/3252

ひどいバグですね。正直swcにちょっとがっかりですが、頑張ってほしいです。

完了

変なバグが現れたら、バンドル済みのソースコードを分析するのがちょっと面倒かもしれないが、答えを知るための近道でもあります。ご参考になれば幸いです。

再現プロジェクトはこちらです。

(1, 3番に問題ありません。2. Request by getByArrowFuncを押してエラーを確認してみてください)


関連投稿

Next.js + material-ui + styled-componentsの型安全のテーマカスタマイズ

2022年1月16日

ThunderMiracle

Blog part of ThunderMiracle.com