Vue+Vuex+Typescriptの構成でfrontendを書くため現状の書き方をまとめておく。

ソースコード

package.json

{
  "name": "my-app",
  "scripts": {
    "dev": "nuxt",
    "build": "nuxt build",
    "generate": "nuxt generate",
    "fmt": "eslint --fix . --ext .js,.ts,.vue --ignore-path .gitignore",
    "test": "jest",
    "start": "nuxt start"
  },
  "dependencies": {
    "@nuxtjs/axios": "^5.13.6",
    "eslint": "^7.32.0",
    "jest": "^27.0.6",
    "nuxt": "^2.15.7",
    "vuex-module-decorators": "^1.0.1"
  },
  "devDependencies": {
    "@nuxt/types": "^2.15.7",
    "@nuxtjs/eslint-module": "^3.0.2",
    "@nuxtjs/style-resources": "^1.0.0",
    "@nuxtjs/eslint-config-typescript": "^6.0.1",
    "@nuxt/typescript-build": "^2.1.0"
  }
}

tsconfig.json

{
  "compilerOptions": {
    "experimentalDecorators": true,
    "target": "ES2018",
    "module": "ESNext",
    "moduleResolution": "Node",
    "lib": [
      "ESNext",
      "ESNext.AsyncIterable",
      "DOM"
    ],
    "esModuleInterop": true,
    "allowJs": true,
    "sourceMap": true,
    "strict": true,
    "noEmit": true,
    "baseUrl": ".",
    "paths": {
      "~/*": [
	"./*"
      ],
      "@/*": [
	"./*"
      ]
    },
    "types": [
      "@types/node",
      "@nuxt/types",
      "@nuxtjs/axios"
    ]
  },
  "exclude": [
    "node_modules"
  ]
}

nuxt.config.js

export default {
  components: true,
  ssr: false,
  buildModules: ['@nuxt/typescript-build'],
  modules: ['@nuxtjs/axios'],
  plugins: [
    '~/plugins/axios-accessor',
  ],
  axios: {
    baseURL: "http://localhost:8000",  // process.env.API_ORIGIN,
  },
}

vue-shim.d.ts

declare module "*.vue" {
  import Vue from 'vue'
  export default Vue
}

pages/index.vue

<template>
  <ProfileView />
</template>

<script lang="ts">
import Vue from 'vue'

export default Vue.extend({
  name: "Foo",
});
</script>

store/index.ts

import { Store } from 'vuex'
import { initialiseStores } from '~/utils/store-accessor'

const initializer = (store: Store<any>) => initialiseStores(store)

export const plugins = [initializer]
export * from '~/utils/store-accessor'

store/account.ts

import { Action, Module, VuexModule, Mutation } from 'vuex-module-decorators'
import { $axios } from '~/utils/api'
import { Profile } from '~/models'


@Module({
  name: 'account',
  stateFactory: true,
  namespaced: true,
})
export default class Account extends VuexModule {
  profile: Profile = {
    name: "sximada",
    count: 0,
  }

  @Mutation
  updateCount(num: number) {
    this.profile.count += num;
  }

  @Action
  async incr () {
    try {
      const resp = await $axios.$get(
	"/api/profile/", {},
      );
    } catch (err) {
      console.error(`Invalid: ${err}`)
      return
    }
    this.updateCount(1);
  }
}

utils/api.ts

import { NuxtAxiosInstance } from '@nuxtjs/axios'

let $axios: NuxtAxiosInstance

export function initializeAxios(axiosInstance: NuxtAxiosInstance) {
  $axios = axiosInstance
}

export { $axios }

utils/store-accessor.ts

import { Store } from 'vuex'
import { getModule } from 'vuex-module-decorators'
import Account from '~/store/account'

let account: Account;

function initialiseStores(store: Store<any>): void {
  account = getModule(Account, store);

}

export { initialiseStores, account }

plugins/axios-accessor.ts

import { Plugin } from '@nuxt/types'
import { initializeAxios } from '~/utils/api'

const accessor: Plugin = ({ $axios }) => {
  initializeAxios($axios)
}

export default accessor

models.ts

type Name = string;
type Count = number;

export interface Profile {
  name: Name,
  count: Count,
}

その他のファイル

Procfile

frontend: npm run dev
apisprout: apisprout ./openapi.yml

openapi.yml

openapi: 3.0.0

info:
  title: 1627457309 Vue Typescript API
  description: 1627457309 Vue Typescript API
  version: "1"

servers:
  - url: http://localhost:8000
    description: Local Development

  - url: http://host.docker.internal:8000
    description: Local Development

  - url: http://host.docker.internal:5005
    description: Local Development2

paths:
  /api/profile/:
    get:
      summary: ????
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                type: object
                example: |
                  {
                    "name": "sximada",
                    "count": 1
                  }                  

所感

Frontendのframeworkは複雑は複雑な書き方を行う。またちょっと期間を置く と違った実装方法が取り入れられたりする。今回はvue+vuex+typescriptでシ ンプルな構成のfrtonendを実装した。