Nuxt.jsアプリケーションに外部パッケージのVuex StoreをModuleとして登録する方法
いろいろな事情があって、Nuxt.js アプリケーションからデカ目の機能を切り出して Vuex Store と Component を独立させたパッケージにすることになった。 要件はこんな感じ:
- 外部 module は
@@/packages/<package名>/...
以下に配置 - Vuex Store は Module mode
@/store/
に export * from ...
する js ファイル置けば終わりでしょ?とか思ってたけど、ダメ。一捻り必要だった。
Modules mode
Nuxt.js の Vuex Store は store フォルダ内にある js ファイルごとに自動で 名前空間の独立したVuex Moduleを生成してくれる便利機能がある。これを Modules mode という。
store/hoge.js
はhoge
store/deep/something.js
はdeep/something
という namespace を持つ。なので、いわゆる 非 Nuxt 環境の Vue アプリケーションのように自分で 子 module を import する必要がない。
Classic mode(deprecated) について
new Vuex.Store()
を返す関数を store/index.js
で export default
することで、
従来の Vuex のように自分で store を設定可能な Classic モードになる。
作った store をそのまま使ってくれるので、当然ながらネストした modules
も普通に取り込める。Module mode では フォルダ構造が module 構造と一致するのでこれは出来ない。
しかし、現時点で deprecated (Nuxt 3 で廃止予定)で 、非推奨機能なので避けたほうが良い。規約ファーストな Nuxt.js というフレームワークの設計思想的にも使う理由がない。なので実質 Modules mode 一択。
どうやって外部化した Vuex module を Module mode に組み込むか
子 module を持たない store なら単純に import してやればいい:
const state = () => ({
is: 'shallow module',
});
const getters = {
greet(state) {
return `this is ${state.is}`;
},
};
export default {
state,
getters,
};
上記のように Module mode 非互換の export default { ... }
形式の export についても、Module mode の module としてグルーコードを書けば問題ない:
import shallow from '@@/packages/shallowModule';
export const state = shallow.state;
export const getters = shallow.getters;
ところが、modules
プロパティに関してはうまくいかない。そもそも Nuxt.js が module 登録する過程で ['state', 'getters', 'actions', 'mutations']
以外は無視している ので次のコードは意図通り動かない:
import parent from '@@/packages/family';
export const state = parent.state;
export const getters = parent.getters;
export const modules = parent.modules; // No children allowed 😭
modules を一旦無視して、modules 相当の構造を Nuxt.js アプリケーション上で自力用意すれば一応動かせる。要は Vuex modules を Nuxt.js Vuex Modules mode で再現することになる。外部パッケージに隠蔽されるべき内部的な module 構成と実装が Nuxt.js アプリケーション側と密結合するので流石に無理がある。
では、そのような状態で外部パッケージの store を module としてどう登録すればいいのか。
ここから本題。
Nuxt.js Plugins で Dynamic (Vuex) module registration
結論から言えば、Vuex store に対して動的に module 登録する Nuxt.js の Plugin を作れば良い。
注意すべきなのは、これは Vuex Plugins ではなく、Nuxt.js Plugins であること。
(Modules も同じく、Nuxt.js と Vuex と名前かぶりしていて紛らわしい。)
Nuxt.js Plugins は、Nuxt.js の中で生成される Vue.js アプリケーションのインスタンス化前の処理を追加する。
ここでは js ファイル (たとえば Vue の Plugin を import して Vue.use(...)
するようなコード) を静的に評価する以外に、関数を export default
することで、 引数経由で context
をとれる。
Vuex store が初期化されていれば context.store
から RootStore を参照できるので store.registerModule
で動的に module 登録ができる。
つまり、ネストした外部 Vuex Module を、Vuex Modules mode と共存させるにはこうすれば良い:
import rootModule from './store.js';
export default ({ store }) => {
store.registerModule('someModule', rootModule);
};
外部 module なので当然だけど Nuxt.js Modules mode の外にいるので、自動名前空間設定が行われず、module ごとにnamespaced: true
は明示的に設定する必要がある。
登録する module の namespace (上記例で someModule
) は既存 module とぶつからないように注意。
Nuxt.js の Plugin は今の所 nuxt.config.js
から 設定値が渡せないので、もし namespace を決め打ちではなく利用側に設定させたくなってきたら Nuxt.js Modules 化を検討したほうがよい。そちらのやり方は割愛。
Example
せっかくなので全体像を。まずは適当な子モジュール:
const state = () => ({
is: 'hoge',
bornIn: 1990,
});
const getters = {
profile: state => {
return `${state.is} was born in ${state.bornIn}.`;
},
};
export default {
state,
getters,
namespaced: true,
};
// 略
(要らないなら別に良いけど) namespaced: true
を忘れずに。
そして、これらを読み込む親(root):
import hoge from './modules/hoge';
import fuga from './modules/fuga';
export default {
modules: {
hoge,
fuga,
},
namespaced: true,
};
これも namespaced: true
を忘れずに。
名前空間取れたほうが良い気がするので定数にして export
してある:
export const NAMESPACE = 'ryonkmr::someModule';
// UI を export したり
export { default as HogeProfile } from './components/HogeProfile.vue';
export { default as FugaPforile } from './components/FugaProfile.vue';
最後に肝心の Nuxt.js Plugin:
import { NAMESPACE } from './index.js'; // === 'ryonkmr::someModule'
import rootModule from './store.js';
export default ({ store }) => {
store.registerModule(NAMESPACE, rootModule);
};
後は読み込むだけ。
plugins: ['@@/packages/someModule/plugin.js'];
おしまい。