Mastodonのフロント側で、あるモジュールの関数において、そのモジュールの依存モジュールが一部読み込まれていないらしい、という問題がありました。こんなエラー。
Uncaught TypeError: Cannot read property 'a' of undefined at Object.configureStore [as a] (98:31) at Object.eval (94:59) at eval (94:199) at Object.<anonymous> (common.js:1197) at __webpack_require__ (common.js:55) at Object.eval (98:8) at eval (98:55) at Object.<anonymous> (common.js:1244) at __webpack_require__ (common.js:55) at Object.eval (589:7)
下のコードで configureStore()
を呼び出そうとしていたのですが、何故か loadingBarMiddleware
が読み込まれない。調べていった結果、そもそも以下のimport群のうち hydrateAction
の行までしか実行されていないことがわかりました。
import { createStore, applyMiddleware, compose } from 'redux'; import thunk from 'redux-thunk'; import appReducer, { createReducer } from '../reducers'; import { hydrateStoreLazy } from '../actions/store'; import { hydrateAction } from '../containers/mastodon'; import loadingBarMiddleware from '../middleware/loading_bar'; import errorsMiddleware from '../middleware/errors'; import soundsMiddleware from '../middleware/sounds'; export default function configureStore() { ... }
上記のコードはWebpackによって次のようなコードに変換されています。
/* WEBPACK VAR INJECTION */(function(global) {/* harmony export (immutable) */ __webpack_exports__["a"] = configureStore; /* harmony export (immutable) */ __webpack_exports__["b"] = injectAsyncReducer; /* harmony import */ var __WEBPACK_IMPORTED_MODULE_0_redux__ = __webpack_require__(/*! redux */ 169); /* harmony import */ var __WEBPACK_IMPORTED_MODULE_1_redux_thunk__ = __webpack_require__(/*! redux-thunk */ 370); /* harmony import */ var __WEBPACK_IMPORTED_MODULE_1_redux_thunk___default = __webpack_require__.n(__WEBPACK_IMPORTED_MODULE_1_redux_thunk__); /* harmony import */ var __WEBPACK_IMPORTED_MODULE_2__reducers__ = __webpack_require__(/*! ../reducers */ 371); /* harmony import */ var __WEBPACK_IMPORTED_MODULE_3__actions_store__ = __webpack_require__(/*! ../actions/store */ 35); /* harmony import */ var __WEBPACK_IMPORTED_MODULE_4__middleware_loading_bar__ = __webpack_require__(/*! ../middleware/loading_bar */ 430); /* harmony import */ var __WEBPACK_IMPORTED_MODULE_5__middleware_errors__ = __webpack_require__(/*! ../middleware/errors */ 431); /* harmony import */ var __WEBPACK_IMPORTED_MODULE_6__middleware_sounds__ = __webpack_require__(/*! ../middleware/sounds */ 432); /* harmony import */ var __WEBPACK_IMPORTED_MODULE_7__containers_mastodon__ = __webpack_require__(/*! ../containers/mastodon */ 94); var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; function configureStore() { ... }
全体を包む無名関数の中で、import文はそれぞれ __webpack_require__()
の呼び出しで実現されていることがわかります。JavaScriptにおいて関数宣言は定義ごと巻き上げられるので、もしimportの最中に configureStore()
が呼ばれてしまったら、同じような結果を得ることができそうです。
ん?/containers/mastodon
だって?
import React from 'react'; import { Provider } from 'react-redux'; import PropTypes from 'prop-types'; import configureStore from '../store/configureStore'; ... import { IntlProvider, addLocaleData } from 'react-intl'; import { getLocale } from '../locales'; const { localeData, messages } = getLocale(); addLocaleData(localeData); export const store = configureStore(); const initialState = JSON.parse(document.getElementById('initial-state').textContent);
…ありますね、configureStore()
。変数の初期化に使われているので、モジュールを読み込んだだけで実行されます。結果として、configureStoreモジュールのimportをすべて終える前に、間接的に configureStore()
が呼ばれてしまう、ということになります。
逆に mastodon モジュールから読み込んだ場合も循環してしまいますが、configureStore モジュールを読み込んだだけで mastodon モジュールの関数が呼び出されることはないので、この場合は問題ありません。
__webpack_require__()
は各モジュールを実行する前に、先にキャッシュに登録を行います。一方先に載せたモジュールのコードを見ると、importより先にexportした関数を登録していることがわかります。ですから configureStore モジュールの読み込みが完了する前に mastodon モジュールから configureStore()
関数が呼び出せた、というわけです。