Nuxt.jsモジュールを自作して開発効率を向上させる

24

1月

Nuxt.jsモジュールを自作して開発効率を向上させる

2020年01月24日

Nuxt.jsのモジュールを自作して簡単に使い回す

基礎知識があるのとないのとでは理解度に違いがあるので、
モジュールとは何?という方はこちらを読んでみてください。
Nuxt.jsはドキュメントが多くて助かりますね!

引用

この記事は @mya_akeさんの Nuxt.js と axios のエラーハンドリング周りの話 から引用させていただきました。
この場を借りてお礼申し上げます。

対象読者

  • 「エラーハンドリングで冗長なコードを書きたくない」と考えている方
  • 「他のプロジェクトでもコードを使い回ししたい」と考えている方
  • 個人開発をしている方

はじめに

Nuxt.jsにはモジュールというNuxtのコア機能を拡張するもの存在します。
有名所は以下のGithubにリストがあります。
nuxt-community / modules
実際に使っている方は多いのではないでしょうか。
しかし、自作するとなると敷居が高いように感じるかもしれません。

公式にはこう書いてあります。

モジュールは、 Nuxt 起動時に順番に呼び出される、シンプルな関数です。

モジュールは、結構簡単に作れるのです。
作成したモジュールはnpmパッケージとして公開することもできます。
この記事ではnpmパッケージを公開することはしません。

車輪(タイヤ)の再発明をしないようにするためにも、今回は例を用いてNuxtモジュールを自作していきたいと思います。

モジュール作成前に

引用ですがモジュールを作成するには理解しておく必要のある知識です。

moduleOptions
これは modules の配列を利用するために、モジュールの利用者から渡されるオブジェクトです。これを使うことで modules のふるまいをカスタマイズすることができます。

this.options
この参照を利用して Nuxt options へ直接アクセスすることができます。これはすべてのデフォルトのオプションがアサインされた、ユーザーの nuxt.config.js の内容です。モジュール間で共有されるオプションとして利用できます。

this.nuxt
現在の Nuxt インスタンスへの参照です。利用可能なメソッドは Nuxt クラスのドキュメント を参照してください。

this
モジュールのコンテキストです。利用可能なメソッドは モジュールコンテナ クラスのドキュメントを参照してください。

module.exports.meta
この行は npm パッケージとして公開するときは必須です。Nuxt はあなたのパッケージをより良く機能させるために、内部でメタ情報を利用します。

プロジェクトの用意

今回プロジェクト名はnuxt-modulesとします。

$ npx create-nuxt-app nuxt-modules
or
$ yarn create nuxt-app nuxt-modules

プロジェクトの用意/実行結果

モジュール作成

今回はaxiosモジュールを拡張したモジュールを作成していきます。
axiosで通信を行う際にエラーハンドリングを各所に書くのは面倒なので共通化したいと思います。
プロジェクト毎にプラグインファイルを配置してもいいですが、npmモジュール化することでプロジェクトへの導入が楽になります。

では早速モジュールを作成していきましょう。
先ほど作成したプロジェクトへ移動します。

$ cd nuxt-modules/

ディレクトリの用意

今回作成するモジュールはmodules/resourcesディレクトリに作成してくので、
modules/resourcesディレクトリを作成します。

$ mkdir modules
$ mkdir modules/resources
$ cd modules/resources

ファイルの用意

modules/resourcesディレクトリへ必要なファイルを作成します。

$ touch module.js plugin.js Resources.js Response.js

ここまで出来たら以下構造になっていると思います。

/
    assets/
    components/
    ...省略
    modules/
        - resources/
            - module.js
            - plugin.js
            - Resources.js
            - Response.js
    ...省略
    nuxt.config.js
    package.json

とりあえず動くサンプルの作成から

それぞれのファイルは以下のようになります。
動くサンプルとしてクラスを定義し、インスタンスのタイミングでログを出力しているだけです。

// module.js
import path from 'path'

// モジュールルート
const libRoot = path.resolve(__dirname, '..')

function resourceModule (_moduleOptions) {

    // nuxt.config.jsで定義されているオプションを取得
    const moduleOptions = { ...this.options.resources, ..._moduleOptions }

    // プラグインに登録
    // function名.call(this) でthisを共有できる
    copyResources.call(this)
    copyResponse.call(this)
    copyPlugin.call(this, { options: moduleOptions })
}

// Resource.jsをコピー
function copyResources() {
    this.addTemplate({
        src: path.resolve(libRoot, 'resources', 'Resources.js'),
        fileName: path.join('resources', 'Resources.js')
    })
}

// Response.jsをコピー
function copyResponse() {
    this.addTemplate({
        src: path.resolve(libRoot, 'resources', 'Response.js'),
        fileName: path.join('resources', 'Response.js')
    })
}

// plugin.jsをコピー
function copyPlugin({ options }) {
    this.addPlugin({
        src: path.resolve(libRoot,'resources', 'plugin.js'),
        fileName: path.join('resources', 'plugin.js'),
        options
    })
}

export default resourceModule
// plugin.js
import Resources from './Resources'
import Response from './Response'

export default (ctx, inject) => {
    new Response()
    new Resources()
}
// Resources.js
export default class Resources {
    constructor() {
        console.log('Resources')
    }
}
// Response.js
export default class Response {
    constructor() {
        console.log('Response')
    }
}

確認

どちらを使っているかで実行方法が違うので自身の環境に合わせてください。

$ npm run dev
or
$ yarn dev

結果

Response
Resources

実際に処理を書いていく

ここまできたらあとは実際の処理を書いていくのみです。

Resources.js
axiosをラップしたクラス。
エラーハンドリングの共通化を行います。

Response.js
レスポンスを加工するクラス。
レスポンスの共通化を行います。

以上を踏まえて実装していきます。

// Resources.js
export default class Resources {
    constructor(axios, responseBuilder) {
        this._axios = axios
        this._responseBuilder = responseBuilder
    }

    // get関数を提供
    async get(url, config) {
        const response = await this._axios
            .get(url, config)
            .catch(err => err.response)
        return this._buildResponse(response)
    }

    // レスポンスを加工する関数が存在すれば呼び出すコールバック関数
    _buildResponse(response) {
        if (typeof this._responseBuilder !== 'function') {
            return response
        }
        return this._responseBuilder(response)
    }
}
// Response.js
export default class Response {
    constructor(response) {
        this._rawResponse = response
        this._expectStatus = 200
        this._buildResponse()
        return this
    }

    // 正常な場合のステータスコードを設定する
    expectStatus(expectStatus) {
        this._expectStatus = expectStatus
        return this
    }

    // レスポンスのdataプロパティをclassのインスタンスへと変換する
    toModel(Model) {
        if (this.isError) {
            return this
        }
        this.data = new Model(this.data)
        return this
    }

    // Vue.jsのcomputed的なもの
    // ただし結果はキャッシュされないので参照があるたびに関数が実行される
    get isError() {
        return this.status !== this._expectStatus
    }

    // 必要なプロパティだけ抜き出している
    _buildResponse() {
        const { status, data } = this._rawResponse
        this.status = status
        this.data = data
    }
}

plugin.jsも修正します。

// plugin.js
import axios from 'axios'
import Resources from './Resources'
import Response from './Response'

// レスポンスを加工する関数
// 今回はResponseクラスのインスタンスを返すようにしている
const responseBuilder = (response) => {
    return new Response(response)
}

export default (ctx, inject) => {
    const resources = new Resources(axios, responseBuilder)
    ctx.$resources = resources
    inject('resources', resources) // これでいろいろなところで使えるようにする
}

動作確認

nuxt-modules/pages/index.vueに処理を追加します。
確認の為、qiitaからデータを取得します。

export default {
    ...省略
    async asyncData(ctx) {
        const url = 'https://qiita.com/api/v2/items?page=1&per_page=5&query=DDD'
        const response = await ctx.$resources.get(url)
        response.toModel(Items)
        if (response.isError) {
            ctx.error({
                statusCode: response.status,
                message: response.data.message
            })
            return
        }
        const { items } = response.data
        return { items }
    }
}

まとめ

今回は、Nuxtモジュールに焦点を当てて記事を書きました。
Nuxtは記事が多い印象でしたが、Nuxtモジュールに関する記事は少なかったです。
(公式の@nuxtjs/axiosや@nuxtjs/authのコードを追いながら作成しました。)

実際のプロジェクトに導入する場合はプロジェクト毎にエラーハンドリングやコーディングルール等が違ったりするケースが多いことも多く、その際は共通化に躍起にならない方がいい場合もあります。

(個人開発をNuxtでしている方で「エラーハンドリングで冗長なコードを書きたくない」や「他のプロジェクトでも使い回したい」と考えている方にオススメの方法だと個人的に思っています。仕事ではこの辺りを個人の裁量で決めれないことも多いため。)

今回作成したモジュールはaxiosのエラーハンドリングを共通化したものでした。
今回のモジュールを拡張(POST、PUT、DELETE関数を提供するように)したり、アクセスログをファイルに書き込んだりとあなたの工夫次第で色々できるので試してみてください。

参考

Nuxt.js公式
Qiita API V2 Documentation
@mya_akeさんのNuxt.js と axios のエラーハンドリング周りの話のコード

PR

Japonlineでは、Nuxt.jsの技術支援も行っています。フロント開発や設計など一部分からお請けすることも可能ですのでお気軽にお問い合わせください。