ここ数ヶ月はNuxt.jsで開発することが多いのですが、意外と~/plugins
ディレクトリにいろいろ貯まってきたので軽めの共有です。
前提
-
mode: 'spa'
で開発しています- こうするとサーバーサイドレンダリング(SSR)はなくなります
- SSRの場合は
nuxtServerInit
というAPIを利用することでストアの初期化などが可能です
- SPAの場合は初期化処理を
~/plugins
で実装することが可能です- 名前からしてpluginsで初期化するのが正しいのかどうかあんまり自信ないですが、実装上はこれで可能です。
~/plugins
の挙動
- (build後の状態はわからないですが).nuxt/index.js を見ると、以下のような状態になっています。
// 略
import plugin0 from 'plugin0'
import plugin1 from 'plugin1'
import plugin2 from 'plugin2'
import plugin3 from 'plugin3'
// 略
async function createApp (ssrContext) {
//略
if (typeof plugin0 === 'function') await plugin0(ctx, inject)
if (typeof plugin1 === 'function') await plugin1(ctx, inject)
if (typeof plugin2 === 'function') await plugin2(ctx, inject)
if (typeof plugin3 === 'function') await plugin3(ctx, inject)
//略
return {
app,
router,
store
}
}
export { createApp, NuxtError }
※ plugin0
とか plugin1
のようなものはおそらく nuxt.config.js
の設定によって順番にロードされてるものなのだと思います。
- これは
App
を作成するNuxtの初期化処理なのですが、先頭でpluginがimportされ、createAppの中で、functionがexportされてる場合には(awaitして)その関数をContextとともに実行してくれるという感じになています。 - つまり、以下の2つのことをpluginsで実現可能です
- Contextに依存しない初期化処理を行う
- Contextに依存する初期化処理を行う
- 繰り返しになりますが、awaitで実行されているので、非同期処理を待ったうえで後続の処理を実行させるようなことも可能です。
オレオレplugins
というわけで、いろんな初期化処理に使えるので、pluginsは重宝しています。
実案件で作ったpluginをいくつか公開したいと思います。
基本的に手探りで開発してるので、「もっとこうしたほうがいいよ!」的なマサカリは大歓迎です。
(TypeScriptで作ってるのですが、たぶんJSしかわからなくても読めるのでご安心ください)
axios
import _ from "lodash";
export default function ({ app, env, isServer }) {
if (isServer) {
// SPAモードなのでこの条件に来ることはないけどお作法として。
return;
}
// 未認証エラーになった場合はログインページにリダイレクトする
app.$axios.interceptors.response.use(response => response, (error) => {
if (error.statusCode === 401) {
window.location.href = env.loginUrl;
return;
}
return Promise.reject(error);
});
}
@nuxtjs/axiosモジュールを利用しているので、Context内でaxiosインスタンスがシングルトンな感じになってるので、そのAxiosInstanceに対してinterceptorを設定しています。
これによって、APIのレスポンスが401だったら、とりあえずログインページにリダイレクトさせる、みたいなことをやっています。
(都合でlocation.hrefしていますが、app.router.pushとかでも動作するはずです)
emoji
import emoji from "node-emoji";
import Vue from "vue";
Vue.mixin({
methods: {
$emojify(text: string) {
return emoji.emojify(text);
},
},
});
Componentの中でemoji使いたいことはきっと多いはずなので 、node-emojiをサクッとComponentから利用できるようにするemojiのpluginsです。
これで {{$emojify(':innocent:')}}
をComponentに書くだけでいつでも を表示することができます。すばらしい。
i18n
Nuxt.jsでいい感じにi18nを使う
https://qiita.com/takyam/items/4861badeb88dcee67d0a
詳しくは上記参照ください
vee-validate
import _ from 'lodash';
import VeeValidate, { Validator } from "vee-validate";
import Vue from "vue";
import validations from "./vee-validate/validations";
_.each(validations, (rule, name) => {
Validator.extend(name, rule);
});
Vue.use(VeeValidate);
import _ from "lodash";
import identifier from "./identifier";
export default _.merge({}, identifier);
import Identifier from "~/domains/Identifier"; // こういうやつがあるもんだと思ってください
export default {
isId: {
messages: {
en: (field) => "Invalid ID format",
ja: (field) => "有効なIDではありません",
},
validate: (value) => Identifier.isValid(value),
},
};
- バリデーションにはvee-validateを使ってます。
- vee-validateはカスタムバリデーションを実装することが可能なのですが、単一ファイルにたくさんのバリデーションを追加していくと肥大化すること間違いなしなので、ある程度のグループ毎にファイルを分割できるようにしています。
- validationsの定義をplugins以下に置くことにモニョってはいるので、いつかいい感じの場所に移動したいです
console
import Vue from "vue";
Vue.mixin({
methods: {
$console(...args): void {
console.dir(args);
},
},
});
Vueのtemplate内で console.log
したいけど、すると怒られたので拡張してます。
テンプレート内で $console(hoge)
みたいにできてプチ便利。
(絶対もっといいソリューションがあるんだろうなぁとは思ってます・・・!)
provider
import Vue from "vue";
import _ from "lodash";
import HogeRepository from "~/infra/HogeRepository";
class ServiceProvider {
private _services;
constructor(services: { [key: string]: any }) {
this._services = services;
}
get services() {
return this._services;
}
get(serviceName: string): any | null | undefined {
return _.get(this._services, serviceName);
}
}
export default function ({ app }) {
const serviceProvider = new ServiceProvider({
"HogeRepository": new HogeRepository(app.$axios),
});
app.$serviceProvider = serviceProvider;
Vue.mixin({ provide: serviceProvider.services });
}
苦肉の策というか、何かぜったいもっと素敵なソリューションがあるはずなのですが、VueコンポーネントでDI的なことやるいい感じの方法が見つからず、とりあえずオレオレでやってます。
この例でいうと HogeRepository
というRepositoryがaxiosに依存しているので、context生成後にインスタンスを生成してあげて、それをオレオレサービスプロバイダに登録しておきます。
-
app.$serviceProvider
として登録 -
Vue.mixin({ provide: serviceProvider.services })
でProvideしておく
という2通りの登録をやっておくことで、
-
asyncData
/fetch
などのComponentが初期化されていない状態ではapp.$serviceProvider
経由で取得できる - Componentが作られている状態では
@Inject("HogeRepository") hogeRepository: HogeRepository
のようにしておくことで、this.hogeRepository
にアクセスすることができる
という感じになっています。
(控えめにいって nuxt-class-component / vue-property-decorator / vuex-class は神ってるなと思いました)
まとめ
他にもいくつかあったりするんですが、基本は前述のように、 Contextが生成される前の初期化、Context生成後の初期化の2種類なので、それらうまく組み合わせて必要な初期化処理を実現しています。
他にオススメのpluginであったり、上記に対するマサカリであったり、コメントいただけると嬉しいです