くらげになりたい。

くらげのようにふわふわ生きたい日曜プログラマなブログ。趣味の備忘録です。

Nuxt+TypeScript+Firebaseのはじめるときにやること: 2019年12月版

ひさびさに新規でNuxtアプリを作ろうと思ったら、
いろいろ変わっていたので、備忘用のまとめ。

(時間がなかったので割と雑め...いつかきれいに加筆する予定...)

ソースコード

自分用のスタータになるようにGitHubで公開してみた。 (この記事の執筆時よりも変わっていくと思います...)

github.com

プロジェクト作成

# 雛形の作成
$ npx create-nuxt-app <project_name>

# GitとGitFlowの初期化
$ cd <project_name>
$ git init
$ git flow init

# giboでgitignoreを生成
$ gibo dump Vim macOS VisualStudioCode Node > .gitignore

# remoteの設定
$ git remote add origin git@github.com:memory-lovers/<project_name>.git
$ git push -u origin --all

参考: ・インストール - NuxtJSsimonwhitaker/gibo: Easy access to gitignore boilerplates

TypeScriptの設定(ビルド)

パッケージのインストール

$ npm install --save-dev @nuxt/typescript-build 

nuxt.config.jsの設定

// nuxt.config.js
export default {
  buildModules: ['@nuxt/typescript-build']
}

tsconfig.jsonの作成

// tsconfig.json
{
  "compilerOptions": {
    "target": "esnext",
    "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"]
  },
  "exclude": ["node_modules"]
}

TypeScriptの設定(ランタイム)

パッケージのインストール

$ npm install @nuxt/typescript-runtime

package.jsonでnuxt-tsを使うように変更

   "scripts": {
-    "dev": "nuxt",
-    "build": "nuxt build",
-    "start": "nuxt start",
-    "generate": "nuxt generate"
+    "dev": "nuxt-ts",
+    "build": "nuxt-ts build",
+    "start": "nuxt-ts start",
+    "generate": "nuxt-ts generate"
   },

nuxt.configをts化

ファイル名を.jsから.tsに変えつつ、中身も変える。
昔はNuxtConfigurationだったけど、Configurationに変わってた。。

+import { Configuration } from "@nuxt/types";
+require("dotenv").config();

-export default {
+const config: Configuration = {
  mode: "universal",
 };

+export default config;

nuxt-property-decoratorを入れる

Nuxt TypeScriptのCookbookにあるコンポーネントより。
Class APIがすきなので、nuxt-property-decoratorを入れる

# See: https://typescript.nuxtjs.org/ja/cookbook/components/#script
$ npm install nuxt-property-decorator

デコレータを使う場合は、experimentalDecoratorsを有効にする必要があるので、
tsconfig.jsonを変更する。

 // tsconfig.json
 {
   "compilerOptions": {
+     "experimentalDecorators": true,
   },
 }

Vuexで型を使えるようにvuex-module-decoratorsを入れる

公式ガイドのストアのページにこんな一文が。

最も人気のあるアプローチの1つはvuex-module-decoratorsです。- ガイドを参照してください。

この記事がわかりやすかった。 ・Nuxt.js + Typescript + Vuexする現時点のベストと思う方法 - Qiita

ストアのモジュールを作成する

まずは、モジュールを作成。

// ~/store/user.ts
import { Module, VuexModule, Mutation, Action } from "vuex-module-decorators";

// Stateの型を定義する
export interface UserState {
  uid: string | null;
}

// デコレータを設定。nameにモジュール名を指定する。UserStateを継承する
@Module({ stateFactory: true, namespaced: true, name: "user" })
export default class UserModule extends VuexModule implements UserState {
  uid: string | null = null;

  // getterはデコレータなしのgetアクセサで書く
  get isLogin(): boolean {
    return this.uid != null;
  }

  // mutationはデコレータで指定
  @Mutation
  setUser(uid: string | null) {
    this.uid = uid;
  }

  // actionもデコレータで指定
  @Action
  async login(uid: string | null) {
    // thisでmutationが呼べる
    this.setUser(uid);
  }
}
ストアを取りまとめるUtilクラスを作成する

ストアの初期化と取得するUtilクラスを用意。
モジュールが増えるとこのクラスも増やしていく。

// ~/utils/store-accessor.ts

import { Store } from "vuex";
import { getModule } from "vuex-module-decorators";
import UserModule from "~/store/user";

let userStore: UserModule;

// ストアを初期化する関数。rootのstoreを受け取って、モジュールを初期化する
function initialiseStores(store: Store<any>): void {
  // userStoreはここで初期化。
  userStore = getModule(UserModule, store);
}

export { initialiseStores, userStore };
store/index.tsを作成

最後に、store/index.tsに、store-accessor.tsのinitialiseStores()を呼び出すようにする。
一度書いたら、基本変更することはなさそう。

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

// Vuexのプラグインを利用して初期化する
const initializer = (store: Store<any>) => initialiseStores(store)
export const plugins = [initializer]

export * from '~/utils/store-accessor'

コンポーネントから使ってみる

準備は上の3つで完了。使い方はモジュールをimportして使うっぽい。

import { Component, Vue, Prop } from "nuxt-property-decorator";
import { userStore } from "~/store";

@Component
export default class LoginPage extends Vue {
  private async onClickLogin(uid:string) {
    await userStore.setUser(uid);
  }
}

モジュールをそのまま呼ぶので、型がそのまま使える!!
もちろん、$storeでも使える(´ω`)

SASSとBuefyの設定

BuefyのテーマをSASSでカスタマイズしたいので設定していく。

とりあえず、SASS loarderをインストール

$ npm install --save-dev node-sass sass-loader

buefy用のscssを用意(assets/css/buefy.scss)

// Import Bulma's core
@import "~bulma/sass/utilities/_all";

// Set your colors
$primary: #ff99a3;
$primary-invert: findColorInvert($primary);
$twitter: #4099ff;
$twitter-invert: findColorInvert($twitter);

// Setup $colors to use as bulma classes (e.g. 'is-twitter')
$colors: (
  "white": (
    $white,
    $black
  ),
  "black": (
    $black,
    $white
  ),
  "light": (
    $light,
    $light-invert
  ),
  "dark": (
    $dark,
    $dark-invert
  ),
  "primary": (
    $primary,
    $primary-invert
  ),
  "info": (
    $info,
    $info-invert
  ),
  "success": (
    $success,
    $success-invert
  ),
  "warning": (
    $warning,
    $warning-invert
  ),
  "danger": (
    $danger,
    $danger-invert
  ),
  "twitter": (
    $twitter,
    $twitter-invert
  )
);

// Links
$link: $primary;
$link-invert: $primary-invert;
$link-focus-border: $primary;

// Fottoer
$footer-background-color: $primary;

// Import Bulma and Buefy styles
@import "~bulma";
@import "~buefy/src/scss/buefy";

ついでに、page-transition用のscssも用意(assets/css/transition.scss)

// page transiton: slide-fade
.page-enter-active,
.layout-enter-active {
  transition: all 0.3s ease;
}
.page-leave-active,
.layout-leave-active {
  transition: all 0.3s cubic-bezier(1, 0.5, 0.8, 1);
}
.page-enter,
.page-leave-to,
.layout-enter,
.layout-leave-to {
  transform: translateX(30px);
  opacity: 0;
}

最後に、nuxt.config.tsに設定。

 const config: Configuration = {
   /*
    ** Global CSS
    */
-  css: [],
+  css: ["~/assets/css/buefy.scss", "~/assets/css/transition.scss"],
 ...
+
+  /**
+   * nuxt-buefy
+   * Doc: https://github.com/buefy/nuxt-buefy
+   * Doc: https://buefy.org/documentation/constructor-options
+   */
+  buefy: {
+    css: false
+  },
+

Firebaseを利用できるようにする

Firebase AuthとFirebase Cloudstoreを使うので、その設定をしていく。

まずは、パッケージのインストール.

$ npm install --save firebase

次に、.dotenvファイルを用意

BASE_URL=http://localhost:3000
API_KEY='AIXXXXXXXXXXXXXXXXXXXXXXXX'
AUTH_DOMAIN='hogefuga.firebaseapp.com'
DATABASE_URL='https://hogefuga.firebaseio.com'
PROJECT_ID='hogefugao'
STORAGE_BUCKET='hogefuga.appspot.com'
MESSAGING_SENDER_ID='123456789'
APP_ID='1:123456789:web:abcde1234'
MEASUREMENT_ID='G-AAAAAA'

参考: - [Nuxt.js] Firebaseの設定で環境変数dotenvを扱う - Qiita

firebaseを初期化するpluginを作成(plugins/firebase.ts)

//firebase.ts
import * as firebase from "firebase/app";
import "firebase/auth";
import "firebase/firestore";

if (!firebase.apps.length) {
  firebase.initializeApp({
    apiKey: process.env.API_KEY,
    authDomain: process.env.AUTH_DOMAIN,
    databaseURL: process.env.DATABASE_URL,
    projectId: process.env.PROJECT_ID,
    storageBucket: process.env.STORAGE_BUCKET,
    messagingSenderId: process.env.MESSAGING_SENDER_ID,
    appId: process.env.APP_ID,
    measurementId: process.env.MEASUREMENT_ID
  });
  firebase.auth().useDeviceLanguage();
}

export default firebase;

それをnuxt.config.tsに設定

 const config: Configuration = {
   /*
    ** Plugins to load before mounting the App
    */
-  plugins: [],
+  plugins: ["~/plugins/firebase.ts"],

Firebase Authの便利クラスを用意

認証をチェックする便利クラスをよく使うので、用意しておく。

認証状態の変更チェック関数
// plugins/authState.ts
import firebase from "~/plugins/firebase";

export default function(): Promise<firebase.User | null> {
  return new Promise(function(resolve, reject) {
    firebase.auth().onAuthStateChanged(function(user) {
      resolve(user || null);
    });
  });
}
未認証の場合、ログイン画面にリダイレクトするmiddleware
// middleware/checkAuthed.ts

import authState from "~/plugins/authState";

export default async function({ store, redirect }) {
  try {
    // こんな感じのことを書く。そのままだと無限ループする...
    // if (!!store.getters["user/isLogin"]) return;
    // redirect({ name: "login" });
  } catch (e) {
    console.error(`error=${e}`, e);
    return;
  }
}

nuxt.config.tsにも追加

 import { Configuration } from "@nuxt/types";
 require("dotenv").config();

 const config: Configuration = {
   router: {
+    middleware: ["checkAuthed"]
   },
 }

おわりに

だいたいいつもこんな感じのことをしている。
ひさびさに公式ドキュメントをみると、新しいやり方が増えているので、
定期的に振り返ってまとめるの大事...

また、タイミング見て、更新・追記していく予定...

以上!!

昔書いた記事