2019年9月29日日曜日

Nuxtでcore-jsのエラーの解消

以前、こちらの記事
以下のエラーに対応するために core-js@2.6.7 を別途追加する
旨の記載をしましたが、対応が間違っていたようです。

こちらのリポジトリを修正してあります。
https://github.com/TAC/nuxt-firebase-auth-example

These dependencies were not found:                                                                                                                                      friendly-errors 16:19:36
                                                                                                                                                                        friendly-errors 16:19:36
* core-js/modules/es6.array.find in ./.nuxt/client.js                                                                                                                   friendly-errors 16:19:36
* core-js/modules/es6.array.iterator in ./.nuxt/client.js                                                                                                               friendly-errors 16:19:36
* core-js/modules/es6.date.to-string in ./.nuxt/utils.js, ./src/helpers/cookies.ts                                                                                      friendly-errors 16:19:36
* core-js/modules/es6.function.name in ./.nuxt/client.js, ./src/store/models/users.ts                                                                                   friendly-errors 16:19:36
* core-js/modules/es6.object.assign in ./.nuxt/client.js                                                                                                                friendly-errors 16:19:36
* core-js/modules/es6.object.keys in ./.nuxt/index.js                                                                                                                   friendly-errors 16:19:36
* core-js/modules/es6.object.to-string in ./.nuxt/router.scrollBehavior.js, ./.nuxt/components/nuxt-link.client.js and 2 others                                         friendly-errors 16:19:36
* core-js/modules/es6.promise in ./.nuxt/client.js                                                                                                                      friendly-errors 16:19:36
* core-js/modules/es6.regexp.constructor in ./.nuxt/utils.js                                                                                                            friendly-errors 16:19:36
* core-js/modules/es6.regexp.match in ./.nuxt/client.js                                                                                                                 friendly-errors 16:19:36
* core-js/modules/es6.regexp.replace in ./.nuxt/store.js, ./.nuxt/components/nuxt.js                                                                                    friendly-errors 16:19:36
* core-js/modules/es6.regexp.search in ./.nuxt/utils.js                                                                                                                 friendly-errors 16:19:36
* core-js/modules/es6.regexp.split in ./.nuxt/store.js, ./node_modules/babel-loader/lib??ref--2-0!./node_modules/vue-loader/lib??vue-loader-options!./.nuxt/components/nuxt-build-indicator.vue?vue&type=script&lang=js&
* core-js/modules/es6.regexp.to-string in ./.nuxt/utils.js, ./src/helpers/cookies.ts                                                                                    friendly-errors 16:19:36
* core-js/modules/es6.string.includes in ./.nuxt/client.js, ./.nuxt/components/nuxt-link.client.js and 1 other                                                          friendly-errors 16:19:36
* core-js/modules/es6.string.iterator in ./.nuxt/App.js                                                                                                                 friendly-errors 16:19:36
* core-js/modules/es6.string.repeat in ./.nuxt/utils.js                                                                                                                 friendly-errors 16:19:36
* core-js/modules/es6.string.starts-with in ./.nuxt/utils.js, ./src/helpers/cookies.ts                                                                                  friendly-errors 16:19:36
* core-js/modules/es6.symbol in ./.nuxt/store.js, ./.nuxt/components/nuxt-link.client.js                                                                                friendly-errors 16:19:36
* core-js/modules/es7.array.includes in ./.nuxt/store.js, ./.nuxt/components/nuxt-link.client.js and 1 other                                                            friendly-errors 16:19:36
* core-js/modules/es7.object.get-own-property-descriptors in ./.nuxt/utils.js                                                                                           friendly-errors 16:19:36
* core-js/modules/es7.promise.finally in ./.nuxt/client.js                                                                                                              friendly-errors 16:19:36
* core-js/modules/es7.symbol.async-iterator in ./.nuxt/client.js, ./.nuxt/components/nuxt-link.client.js                                                                friendly-errors 16:19:36
* core-js/modules/web.dom.iterable in ./.nuxt/App.js, ./.nuxt/components/nuxt-link.client.js

確かに core-js@2.6.7 を追加することでエラーは解消し、正常に動作するようになりますが、どうやら yarn.lock を削除して node_modules を構築し直すとエラーは解消するようでした。
バージョンアップによって依存関係が正しく変更されなかった影響のようです。

参考サイト

Nuxt 2.5.0 にバージョンアップしたら core-js のエラーが出るようになった
core-js problems with 2.5.0


Written with StackEdit.

2019年9月25日水曜日

`vuex-class-component` のバージョンアップにはまった

NuxtでFirebaseのAuthenticationを使った認証を行う」でつかったプロジェクトのモジュールをアップグレードした際に vuex-class-component がバージョンアップしていたことではまったので、その内容と解決方法を記録しておきます。

修正内容は以下のリポジトリに反映してあります。
https://github.com/TAC/nuxt-firebase-auth-example

Nuxtを2.9.2にバージョンアップ

Nuxt を 2.4.0 から 2.9.2 にバージョンアップする」でやった手順をもとにバージョンアップしたところ、以下のエラーが発生するようになりました。

ERROR  [Vue warn]: Error in render: "TypeError: Cannot redefine property: user"

当初はどこに問題があるのかわからなかったのですが、調べていくうちに vuex-class-component1.6.0 から 2.0.4 へ大きくバージョンアップして、使用方法が変わっていることにたどりつきました。
さらに調べた結果、バージョンアップの影響で stategetter を付与する方法だと、上記のエラーが出るようになってしまっていたので、該当箇所は以下のように変更しました。

app/src/store/models/users.ts

- @getter user: User | null = null
+ private user: User | null = null

+ get get() {
+   return this.user
+ }

app/src/pages/index.vue

export default class extends Vue {
- @Users.Getter('user') user
+ @Users.Getter('get') user

[追記:2019-10-26]

上記の対応方法を間違えていたようです。
初期値にnullundefineを設定していた場合に出るエラーのようでした。
以下で訂正します。

- @getter user: User | null = null
+ @getter user: User = {}

  get isAuthenticated() {
-   return !!this.user
+   return !!this.user.uid
  }

  @mutation
  public UNSET_USER() {
-   this.user = null
+   this.user = {}
  }

さらなるエラー

これでビルドは通ったのですが、Sign-In Google のボタンを押下したときに以下のエラーが出るようになりました。

[vuex] unknown local action type: modules/signIn, global type: modules/auth/modules/signIn

どうやらモジュールモードでのパスの設定がうまく行っていないようでした。
該当箇所を以下のように修正することで対応できました。

app/src/store/modules/auth.ts

- @action()
+ @action({ mode: 'raw' })
  public async signInGoogle() {
+   const context = getRawActionContext(this)
    const provider = new firebase.auth.GoogleAuthProvider()
-   await this.signIn(provider)
+   await context.dispatch('signIn', provider)
  }

Written with StackEdit.

2019年9月24日火曜日

Nuxt.js v2.8.0 release note 和訳

※翻訳勉強中のため、間違っている可能性があります。


https://github.com/nuxt/nuxt.js/releases/tag/v2.8.0

😎 Developer Experience

開発者の体験

  • #5770 Add a group for SSR logs to avoid polluting the browser console
  • #5810 Fancier browser logs with consola
  • #5820, #5832, #5834 Show build indicator in the browser when rebuilding code:
  • #5753 Watch for pages/ creation when default page displayed
  • #5812 Only listen for file changes for supported extensions
  • #5753 Re-use the same port when randomly assigned when restarting in dev mode

  • #5770 ブラウザコンソールを汚染しないように、SSRログのグループを追加します。
  • #5810 consolaを使用した派手なブラウザーログ
  • #5820, #5832, #5834 コードを再構築するときに、ブラウザにビルドインジケーターを表示します。
  • #5753 デフォルトのページが表示されたら、 pages/ の作成に注意してください。
  • #5812 サポートされている拡張子のファイル変更のみをリッスンします。
  • #5753 開発モードで再起動するときにランダムに割り当てられた場合、同じポートを再使用します。

🐛 Bug Fixes

バグ修正

  • vue-renderer
    • #5807 Add User-Agent‍ to Vary header in modern server mode
    • #5764 Safe format SSR logs
  • server
    • #5793 Preserve random port when restarting
    • Return listener when calling listen
  • builder
    • #5753 Watch for pages/ creation when default page displayed
    • #2812 Only listen for file changes for supported extensions
  • generator
    • #5791 Minify spa fallback
  • types
    • #5785 Add type definition for functional babel.presets
  • vue-app
    • #5757 Reuse page component with watchQuery
    • #5746 Remove trailing slash in vue-router non-strict mode
    • #5752 Don’t attach catch handler to already loaded component
    • #5824 fixPrepatch in-out transition fix (issue #5797)
  • utils
    • #5754 Handle serializeFunction edge case

  • vue-renderer
    • #5807 最新のサーバーモードで User-Agent‍Vary ヘッダーに追加
    • #5764 安全な形式のSSRログ
  • server
    • #5793 再起動時にランダムなポートを保持
    • listenを呼び出すときにリスナーを返す
  • builder
    • #5753 デフォルトのページが表示されたときに pages/ の作成を監視する
    • #2812 サポートされている拡張子のファイル変更のみをリッスンします
  • generator
    • #5791 SPAのフォールバックを縮小
  • types
    • #5785 機能的な babel.presets のタイプ定義を追加
  • vue-app
    • #5757 watchQueryでページコンポーネントを再利用します
    • #5746 vue-router non-strictモードで末尾のスラッシュを削除
    • #5752 既に読み込まれているコンポーネントにキャッチハンドラーをアタッチしないでください
    • #5824 fixPrepatchインアウト遷移の修正 (issue #5797)
  • utils
    • #5754 serializeFunction エッジケースの処理

🚀 Features

機能

  • vue-renderer
    • #5745 Add render.injectScripts option
    • #5784 Support render.ssrLog for controlling SSR logs

  • #5745 render.injectScripts オプションを追加
  • #5784 SSRログを制御するための render.ssrLog のサポート

💅 Refactors

リファクタ

  • core
    • #5796 Use require.resolve instead of Module internals
  • builder
    • #5792 Pass nuxt options to template as nuxtOptions
  • vue-app
    • #5770 Add a group for SSR logs
    • #5826 simplify mount error log
  • general
    • #5748 Small readability improvements

  • core
    • #5796 Module 内部の代わりに require.resolve を使用
  • builder
    • #5792 nuxtオプションを nuxtOptions としてテンプレートに渡します
  • vue-app
    • #5770 SSRログのグループを追加します
    • #5826 マウントエラーログを簡素化
  • general
    • #5748 小さな読みやすさの改善

📝 Examples

事例

  • auth-jwt
    • #5775 Use named store export to prevent warning
  • typescript
    • #5742 Add missing ts-node dependency

  • auth-jwt
    • #5775 警告を防止するために名前付きストアのエクスポート(※)を使用する
  • typescript
    • #5742 欠落しているts-node依存関係を追加

(※) Vuexモジュールモードのことと思われる


🏡 Chore

雑用

  • ci
    • #5802 Upload test report to CircleCI and Azure
    • Add flags for codecov
    • Enable audit
  • general
    • Remove unused dependency cross-env
    • Add FUNDING.md
    • Improve links in readme

  • ci
    • #5802 テストレポートをCircleCIおよびAzureにアップロードする
    • codecovのフラグを追加
    • 監査を有効にする
  • general
    • 未使用の依存関係 cross-env を削除します
    • FUNDING.md を追加します
    • readme のリンクを改善する

♻️ Tests

テスト

  • general
    • #5790 Add unit tests for core/resolver.js
    • #5782 Remove duplicate unit tests in packages/core/test/resolver

  • general
    • #5790 core/resolver.js の単体テストを追加
    • #5782 packages/core/test/resolver の重複した単体テストを削除します

Written with StackEdit.

2019年9月17日火曜日

firebase-tools v7.2.1 release note 和訳

※翻訳勉強中のため、間違っている可能性があります。


https://github.com/firebase/firebase-tools/releases/tag/v7.2.1

  • Fixed bug causing Realtime Database emulator to serve errors on the .inspect/coverage.json route.
  • Fixed bug causing Realtime Database emulator to reject requests with Host headers not containing "localhost".
  • Fixed regression in deployment with service account.

  • Realtime Databaseエミュレーターが .inspect/coverage.json ルートでエラーを提供する原因となるバグを修正しました。
  • Realtime Databaseエミュレーターが "localhost" を含まない Host ヘッダーのリクエストを拒否するバグを修正しました。
  • サービスアカウントを使用した展開のリグレッションが修正されました。

翻訳メモ

  • Fixed bug causing Realtime Database emulator to serve errors on the .inspect/coverage.json route.

こちらの .inspect/coverage.json` route というのがよくわからなかったので調べました。
Realtime Database エミュレータ起動時に http://localhost:9000/.inspect/coverage?ns=<database_name> にアクセスするとテストレポートが生成されるようです。
その際に起きていたエラーのバグを修正したということのようでした。

Error: Unexpected server error. At /.inspect/coverage.json?ns=foo
テストレポートを生成する


Written with StackEdit.

2019年9月6日金曜日

firebase-tools v7.2.0 release note 和訳

※翻訳勉強中のため、間違っている可能性があります。


https://github.com/firebase/firebase-tools/releases/tag/v7.2.0

  • Allow the RTDB emulator to hot-reload rules file on changes.
  • Allows emulated Cloud Functions to talk to production RTDB/Firestore if the emulators are not running.
  • Fixes an issue where internal logs would sometimes appear in stdout.
  • Improves RTDB emulator support for JWT algorithms and arbitrary Google Cloud auth tokens.
  • Fixes an issue where functions:shell logs could be double escaped.
  • Fixes an issue with false noisy logging about emulators not running.
  • Allow starting the RTDB or Firestore emulators with any project ID.

  • RTDBエミュレータが変更時に rules ファイルをホットリロードできるようにします。
  • エミュレーターが実行されていない場合、エミュレートされたCloud Functionsが実稼働しているRTDB/Firestoreと通信できるようにします。
  • 内部ログが標準出力に表示されることがある問題を修正しました。
  • JWTアルゴリズムと任意のGoogle Cloud認証トークンのRTDBエミュレーターサポートを改善します。
  • functions:shell ログが二重にエスケープされる問題を修正しました。
  • エミュレーターが実行されていないという誤ったノイズのあるログの問題を修正
  • 任意のプロジェクトIDでRTDBまたはFirestoreエミュレーターを起動できます。

Written with StackEdit.

2019年9月4日水曜日

Nuxt を 2.4.0 から 2.9.2 にバージョンアップする

2.9.0 から TypeScript の対応が変わったのでバージョンバップ時の手順を残しておきます。

こちらのリポジトリをバージョンアップしました。
https://github.com/TAC/nuxt-typescript-example/tree/Nuxt_2.4.0

バージョンアップ後はこちらになります。
https://github.com/TAC/nuxt-typescript-example

モジュールのアップグレード

まずは npm-check-updates を使って一括でアップグレードしてしまいます。

> ncu -u
Upgrading C:\work\repos\nuxt\typescript\app\package.json
[====================] 24/24 100%

 nuxt                                ^2.4.0  →     ^2.9.2
 nuxt-property-decorator             ^2.1.3  →     ^2.4.0
 ts-node                             ^8.1.0  →     ^8.3.0
 @nuxt/typescript                    ^2.7.1  →     ^2.8.1
 @nuxtjs/eslint-config               ^0.0.1  →     ^1.1.2
 @typescript-eslint/eslint-plugin    ^1.9.0  →     ^2.0.0
 babel-eslint                       ^10.0.1  →    ^10.0.3
 eslint                             ^5.15.1  →     ^6.3.0
 eslint-config-prettier              ^4.1.0  →     ^6.1.0
 eslint-config-standard            >=12.0.0  →   >=14.1.0
 eslint-loader                       ^2.1.2  →     ^3.0.0
 eslint-plugin-import              >=2.16.0  →   >=2.18.2
 eslint-plugin-jest                >=22.3.0  →  >=22.16.0
 eslint-plugin-node                 >=8.0.1  →    >=9.2.0
 eslint-plugin-nuxt                 >=0.4.2  →    >=0.4.3
 eslint-plugin-prettier              ^3.0.1  →     ^3.1.0
 eslint-plugin-promise              >=4.0.1  →    >=4.2.1
 eslint-plugin-standard             >=4.0.0  →    >=4.0.1
 eslint-plugin-vue                   ^5.2.2  →     ^5.2.3
 nodemon                            ^1.18.9  →    ^1.19.1
 prettier                           ^1.16.4  →    ^1.18.2
 rimraf                              ^2.6.3  →     ^3.0.0

Run npm install to install new versions.

package.json が更新されるので yarn でモジュールを更新します。

そして、不要になったモジュールをアンインストールします。

yarn remove ts-node @nuxt/typescript @nuxtjs/eslint-config @typescript-eslint/eslint-plugin

次に新しく必要となったモジュールを追加します。

yarn add @nuxt/typescript-runtime
yarn add -D @nuxt/typescript-build @nuxtjs/eslint-config-typescript

tsconfig.json を修正します。

app/tsconfig.json

{
  "compilerOptions": {
    "types": [
      "@types/node",
-     "@nuxt/vue-app"
+     "@nuxt/types"
    ]

nuxt.config.ts を修正します。

app/nuxt.config.ts

-import NuxtConfiguration from '@nuxt/config'
+import { Configuration } from '@nuxt/types'

-const nuxtConfig: NuxtConfiguration = {
+const nuxtConfig: Configuration = {

+  buildModules: ['@nuxt/typescript-build'],

package.json を修正して、nuxt コマンドを nuxt-ts コマンドに変更します。

app/package.json

  "scripts": {
-   "dev": "nuxt",
-   "build": "nuxt build && yarn build:copy",
+   "dev": "nuxt-ts",
+   "build": "nuxt-ts build && yarn build:copy",

.eslintrc.js を修正して ESLint の設定も変更します。

app/.eslintrc.js

  extends: [
-   '@nuxtjs',
+   '@nuxtjs/eslint-config-typescript',

これだけだと以下の警告メッセージが出ます。

=============

WARNING: You are currently running a version of TypeScript which is not officially supported by typescript-estree.

You may find that it works just fine, or you may not.

SUPPORTED TYPESCRIPT VERSIONS: >=3.2.1 <3.6.0

YOUR TYPESCRIPT VERSION: 3.6.2

Please only submit bug reports when using the officially supported version.

=============

これは TypeScript のバージョンが新しすぎた場合の警告で、.eslintrc.jsparserOptions で表示を抑制できるみたいですが、根本的な解決は依存しているモジュールのバージョンが新しくなることのようなので対応を待つしかないようです。

app/.eslintrc.js

module.exports = {
  parserOptions: {
+   'warnOnUnsupportedTypeScriptVersion': false
  },

functions のモジュールもアップグレード

app と同様の手順でアップグレードします。

> ncu -u                                                                                                                                      Upgrading C:\work\repos\nuxt\typescript\functions\package.json
[====================] 14/14 100%

 @nuxt/config              ^2.7.1  →   ^2.9.2
 cross-env                 ^5.2.0  →   ^5.2.1
 express                  ^4.17.0  →  ^4.17.1
 firebase                  ^6.0.2  →   ^6.5.0
 firebase-admin            ~7.0.0  →   ~8.4.0
 firebase-functions        ^2.3.0  →   ^3.2.0
 nuxt                      ^2.4.0  →   ^2.9.2
 nuxt-property-decorator   ^2.1.3  →   ^2.4.0
 ts-node                   ^8.1.0  →   ^8.3.0
 @nuxt/typescript          ^2.7.1  →   ^2.8.1
 @types/express           ^4.16.1  →  ^4.17.1
 tslint                   ^5.12.0  →  ^5.19.0
 typescript                ^3.2.2  →   ^3.6.2

Run npm install to install new versions.

次に Configuration インターフェースの取得先が @nuxt/config から @nuxt/types に変わったので、モジュールを入れ替えます。

> yarn remove @nuxt/config
> yarn add -D @nuxt/types

Nuxtの設定している箇所を修正します。

functions/src/index.ts

-import NuxtConfiguration from '@nuxt/config'
+import { Configuration } from '@nuxt/types'

-const nuxtConfig: NuxtConfiguration = {
+const nuxtConfig: Configuration = {

あとは以前と同じ手順でビルドして、firebase serve で動作確認できます。


以上の設定で 2.4.0 から 2.9.2 にバージョンアップすることができました!

参考サイト

Migration from Nuxt 2.8
Nuxt.js 2.9でのTypeScript対応
typescript環境でALEのeslint利用fixerが動かなくなっていたことの対処方法


Written with StackEdit.

2019年8月28日水曜日

NuxtでFirebaseのAuthenticationを使った認証を行う

FirebaseAuthenticationを使用しての認証機能の実装方法の紹介になります。

完成したプロジェクトは以下のリポジトリになります。
https://github.com/TAC/nuxt-firebase-auth-example

Firebaseの設定

最初にFirebaseの設定を行います。
Firebaseのアカウント作成や設定などは他のサイトなどで確認して下さい。
接続情報は.env/development.jsに記載してnuxt.config.tsで読み込むようにします。
これはリポジトリに入れていないので、上記リポジトリを clone した場合は自分で作成してください。

.env/development.js

module.exports = {
  apiKey: "<your apiKey>",
  authDomain: "<your authDomain>",
  databaseURL: "<your databaseURL>",
  projectId: "<your projectId>",
  storageBucket: "<your storageBucket>",
  messagingSenderId: "<your api messagingSenderId>"
}

app/nuxt.config.ts

import NuxtConfiguration from '@nuxt/config'
import pkg from './package.json'
import envSet from '../.env/environment.js'

const nuxtConfig: NuxtConfiguration = {
  mode: 'universal',
  srcDir: 'src',

  env: envSet,
  ...
}

設定値はprocess.env.apiKeyという形式で利用できます。

次にFirebase の初期設定を行うpluginsを作成します。

app/src/plugins/firebase.js

import firebase from 'firebase/app'

if (!firebase.apps.length) {
  firebase.initializeApp({
    apiKey: process.env.apiKey,
    authDomain: process.env.authDomain,
    databaseURL: process.env.databaseURL,
    projectId: process.env.projectId,
    storageBucket: process.env.storageBucket,
    messagingSenderId: process.env.messagingSenderId
  })
}

export default firebase

FirebaseSDKを使用する箇所でこのプラグインを読み込んで使うことになります。

認証モジュール

Vuex.Store に認証モジュールを作成します。

app/src/store/modules/auth.ts

import {
  VuexModule,
  Module,
  action,
  getRawActionContext
} from 'vuex-class-component'
import firebase from '@/plugins/firebase'
import 'firebase/auth'

@Module({ namespacedPath: 'modules/auth/', target: 'nuxt' })
class Store extends VuexModule {
  @action({ mode: 'raw' })
  public async signIn(provider) {
    const context = getRawActionContext(this)
    context.commit('models/users/UNSET_USER', null, { root: true })
    await firebase
      .auth()
      .signInWithPopup(provider)
      .then(result => {
        if (result.user) {
          context.commit('models/users/SET_USER', result.user, { root: true })
        }
      })
      .catch(error => {
        console.error(error)
        throw error
      })
  }

  @action()
  public async signInGoogle() {
    const provider = new firebase.auth.GoogleAuthProvider()
    await this.signIn(provider)
  }

  @action({ mode: 'raw' })
  public async signOut() {
    const context = getRawActionContext(this)
    await firebase
      .auth()
      .signOut()
      .then(() => {
        context.commit('models/users/UNSET_USER', null, { root: true })
      })
  }

  @action({ mode: 'raw' })
  public isSignIn() {
    const context = getRawActionContext(this)
    return new Promise(resolve => {
      const unsubscribe = firebase.auth().onAuthStateChanged(user => {
        unsubscribe()
        if (user) {
          context.commit('models/users/SET_USER', user, { root: true })
        }
        resolve(user || false)
      })
    })
  }
}

export default Store.ExtractVuexModule(Store)

渡されたプロバイダーの認証を行う signIn アクションと認証データを削除する signOut アクション、サインイン状態の確認を行う isSignIn アクションがあります。
プロバイダーに firebase.auth.GoogleAuthProvider() を渡すとGoogleアカウントの認証ができます。
これを signInGoogle アクションとして作成しておきます。
他にもTwitterやFacebookが認証プロバイダーとして用意されています。
https://firebase.google.com/docs/auth/web/start#next_steps

認証データの保存

firebase.auth().onAuthStateChanged() で取得できるユーザデータを Vuex.Store に保存しておきます。
最初、ユーザデータをそのまま Vuex.Store に保存しようとしたのですが、エラーで動かなかったので修正しました。

参考記事 : Firebase AuthenticationとVuexの合わせ技バグでハマった

app/src/store/models/users.ts

import { VuexModule, Module, getter, mutation } from 'vuex-class-component'

interface User {
  [key: string]: any
}

@Module({ namespacedPath: 'models/users/', target: 'nuxt' })
class Store extends VuexModule {
  @getter user: User | null = null

  get isAuthenticated() {
    return !!this.user
  } 

  @mutation
  public SET_USER(payload) {
    this.user = {
      uid: payload.uid,
      displayName: payload.displayName,
      email: payload.email,
      emailVerified: payload.emailVerified,
      isAnonymous: payload.isAnonymous,
      phoneNumber: payload.phoneNumber,
      photoURL: payload.photoURL,
      providerData: payload.providerData
    }
  }

  @mutation
  public UNSET_USER() {
    this.user = null
  }
}

export default Store.ExtractVuexModule(Store)

認証のテスト

FirebaseSDKをモック化してくれるモジュールをインストールします。(※ただし、このモジュールは残念なことに現在メンテされていません。)

yarn add -D firebase-mock

セットアップ手順をもとに以下のファイルを作成します。

app/tests/__mocks__/plugins/firebase.js

import firebasemock from 'firebase-mock'

const mockauth = new firebasemock.MockAuthentication()
const mockdatabase = new firebasemock.MockFirebase()
const mockfirestore = new firebasemock.MockFirestore()
const mockstorage = new firebasemock.MockStorage()
const mockmessaging = new firebasemock.MockMessaging()

const mocksdk = new firebasemock.MockFirebaseSdk(
  // use null if your code does not use RTDB
  (path) => {
    return path ? mockdatabase.child(path) : mockdatabase
  },
  // use null if your code does not use AUTHENTICATION
  () => {
    return mockauth
  },
  // use null if your code does not use FIRESTORE
  () => {
    return mockfirestore
  },
  // use null if your code does not use STORAGE
  () => {
    return mockstorage
  },
  // use null if your code does not use MESSAGING
  () => {
    return mockmessaging
  }
)

export default mocksdk

@/plugins/firebase の読み込みがモックになるように jest.config.js で設定します。

app/jest.config.js

  moduleNameMapper: {
    '^vue$': 'vue/dist/vue.common.js',
+   '^@/plugins/firebase$': '<rootDir>/tests/__mocks__/plugins/firebase.js',
    '^@/(.*)$': '<rootDir>/src/$1',
    '^~/(.*)$': '<rootDir>/src/$1'
  },

これで Jest での実行時には @/plugins/firebase の読み込みがすべてモックになります。

次に store/modules/auth.ts のテストを書いていきます。

app/tests/store/modules/auth.test.js

import 'firebase/auth'
import Vuex from 'vuex'
import { cloneDeep } from 'lodash'
import { createLocalVue } from '@vue/test-utils'
import flushPromises from 'flush-promises'
import firebase from '@/plugins/firebase'
import auth from '@/store/modules/auth.ts'
import users from '@/store/models/users.ts'

const localVue = createLocalVue()
localVue.use(Vuex)

describe('store/modules/auth.ts', () => {
  let store
  let user

  beforeAll(() => {
    firebase.auth().autoFlush()
  })

  beforeEach(() => {
    // create store
    store = new Vuex.Store({
      modules: {
        'modules/auth': cloneDeep(auth),
        'models/users': cloneDeep(users)
      }
    })

    // mock data
    user = {
      uid: 'alice',
      email: 'alice@mail.com'
    }

    // initialize auth
    firebase
      .auth()
      .createUser(user)
      .then(user => {
        firebase.auth().changeAuthState(user)
      })
  })

  describe('actions', () => {
    test('isSignIn', async () => {
      await store.dispatch('modules/auth/isSignIn')
      const result = store.getters['models/users/user']
      expect(result).toMatchObject(user)
    })

    test('signOut', async () => {
      await store.dispatch('modules/auth/signOut')
      const result = store.getters['models/users/user']
      expect(result).toBeNull()
    })

    test('signInGoogle', async () => {
      firebase.auth().signInWithPopup = jest.fn(provider => {
        return new Promise(resolve => {
          user.providerData = [provider]
          resolve({
            user
          })
        })
      })

      await store.dispatch('modules/auth/signInGoogle')
      await flushPromises()

      const result = store.getters['models/users/user']
      expect(result.providerData[0]).toMatchObject({
        providerId: 'google.com'
      })
    })
  })
})

ポイントとしては以下の2点になります。

  • beforeEach() で初期化時に firebase.auth().autoFlush() を呼び出し、 firebase.auth().createUser() でモック用の認証データを作成しておく
  • firebase.auth().signInWithPopup をモック化する

firebase.auth().autoFlush() を呼び出すことで firebase-mock でモック化された firebase.auth() での変更が反映されるようになります。
また、firebase.auth().signInWithPopup はすでにモックになってますが、期待通りの動きはしてくれません。
なので、正しく動作する形に再度モック関数を設定し直しています。

認証ボタンの作成

最後に作成したアクションを呼び出すボタンを pages/index.vue に配置します。

app/src/pages/index.vue

<template lang="pug">
  section.container
    template(v-if='isAuthenticated')
      ButtonsAction(@action='signOut')
        template(v-slot:buttonText) SignOut
    template(v-else)
      ButtonsAction(@action='signInGoogle')
        template(v-slot:buttonText) Sign-In Google
</template>

<script lang="ts">
import { Component, Vue, namespace } from 'nuxt-property-decorator'
import ButtonsAction from '@/components/buttons/Action.vue'

const Users = namespace('models/users')
const Auth = namespace('modules/auth')

@Component({
  components: {
    ButtonsAction
  }
})
export default class extends Vue {
  @Users.Getter('user') user
  @Users.Getter('isAuthenticated') isAuthenticated
  @Auth.Action('signInGoogle') signInGoogle
  @Auth.Action('signOut') signOut
}
</script>

これで Sign-In Google のボタンが表示され、押すとGoogleアカウント認証のウィンドウがポップアップされると思います。
そのウィンドウで任意のアカウントを選択すると認証が完了します。(GoogleChromeで認証済みの場合はパスワードの入力がスキップされると思います。)
認証が完了するとボタンが SignOut に変わりユーザIDと名前が表示されるかと思います。

SSRに対応する

これでサインイン、サインアウトの実装は終わったのですが、サインインした状態でリロードするとボタンの表示やユーザ情報の表示がちらつくのに気づくと思います。

これはSSR時に認証情報が取得できないためです。
Firebase Authentication では認証情報を IndexedDB に保存しているため、IndexedDB にアクセスできないサーバ側の処理では認証情報を取得できないのです。

そこでサーバ側の処理でも認証情報を取得できるように cookie に認証トークンを保存するように変更します。

Cookieの保存

まずは以下のモジュールを追加します。

yarn add cookie-universal-nuxt jwt-decode core-js@2.6.7

cookie-universal-nuxt はフロント側でもサーバ側でも簡単に cookie にアクセスできるようにしてくれるモジュールです。$cookies をコンテキストに追加してくれます。

jwt-decode はJWTトークンをデコードしてくれるそのまんまのモジュールです。Firebase Authentication で使う認証トークンがJWTトークンなのでこれでデコードします。

core-js は後述するシリアライズ処理で必要になります。
(Nuxt.js 2.8.1 での問題かもしれませんが追加しないとエラーが出ます。)

[追記:2019-09-28]
上記の対応は正しくなかったようです。
詳しくはこちらの記事で説明しています。

次に cookie を操作するヘルパー関数を作成します。

app/src/helpers/cookies.ts

const _OPTIONS = {
  path: '/',
  maxAge: 60 * 60 * 24 * 7,
  secure: true
}

export function getCookie(cookies, name) {
  const data = deserialize(cookies.get('__session'))
  return data[name]
}

export function setCookie(cookies, name, value, isLocalhost) {
  const data = deserialize(cookies.get('__session'))
  data[name] = value
  _OPTIONS.secure = !isLocalhost
  cookies.set('__session', serialize(data), _OPTIONS)
}

export function removeCookie(cookies, name, isLocalhost) {
  const data = deserialize(cookies.get('__session'))
  delete data[name]
  _OPTIONS.secure = !isLocalhost
  cookies.set('__session', serialize(data), _OPTIONS)
}

function serialize(obj) {
  try {
    const str = JSON.stringify(obj, function replacer(k, v) {
      if (typeof v === 'function') {
        return v.toString()
      }
      return v
    })
    return str
  } catch (e) {
    return {}
  }
}

function deserialize(str) {
  try {
    const obj = JSON.parse(str, function reciever(k, v) {
      if (typeof v === 'string' && v.startsWith('function')) {
        return Function.call(this, 'return ' + v)()
      }
      return v
    })
    return obj
  } catch (e) {
    return {}
  }
}

注意点は __session という名前で保存する必要がある点です。Firebaseでホスティングする場合、これ以外の名前の cookie は削除されてしまいます。
https://firebase.google.com/docs/hosting/manage-cache#using_cookies

__session 以外の名前が使えないので、この中に連想配列をシリアライズして登録します。

認証トークンの保存

以下に記載する箇所で認証トークンの保存と読み込みを行います。

app/src/pages/index.vue

  private signInGoogle() {
    const that = this as any
    this.authSignInGoogle().then(() => {
      const currentUser = firebase.auth().currentUser
      if (currentUser) {
        currentUser.getIdToken().then(token => {
          setCookie(that.$cookies, 'token', token, that.$isLocalhost)
        })
      }
    })
  }

  private signOut() {
    const that = this as any
    this.authSignOut().then(() => {
      removeCookie(that.$cookies, 'token', that.$isLocalhost)
    })
  }

サインインした時に認証トークンを取得して保存、サインアウトした時に保存してある認証トークンを削除します。

app/src/plugins/auth.js

import firebase from '@/plugins/firebase'
import 'firebase/auth'
import { setCookie } from '@/helpers/cookies'

export default async function({ store, app }) {
  await store.dispatch('modules/auth/isSignIn')

  const currentUser = firebase.auth().currentUser
  if (currentUser) {
    currentUser.getIdToken().then(token => {
      setCookie(app.$cookies, 'token', token, app.$isLocalhost)
    })
  }
}

認証プラグインを作成して、リロード時に認証トークンが保存されるようにします。

app/src/store/index.ts

import jwtDecode from 'jwt-decode'
import { getCookie } from '@/helpers/cookies'

export const actions = {
  nuxtServerInit: ({ commit }, { app }) => {
    const token = getCookie(app.$cookies, 'token')
    if (token) {
      commit('models/users/SET_USER', jwtDecode(token))
    }
  }
}

Vuex.storenuxtServerInit 処理時に保存してある認証トークンからユーザデータを取得します。(今回はユーザデータをFirestoreなどから取得していないのでこのような形ですが、サーバ側からデータ取得する際には認証トークンの検証が必要になります。)

認証トークンから取得できるユーザデータが onAuthStateChanged とは若干違うのでユーザデータの保存処理も修正します。(ちょっと無理やりですが・・・)

app/src/store/models/users.ts

  @mutation
  public SET_USER(payload) {
    const uid = payload.uid !== undefined ? payload.uid : payload.user_id
    const displayName =
      payload.displayName !== undefined ? payload.displayName : payload.name
    const emailVerified =
      payload.emailVerified !== undefined
        ? payload.emailVerified
        : payload.email_verified
    const photoURL =
      payload.photoURL !== undefined ? payload.photoURL : payload.picture
    this.user = {
      uid,
      displayName,
      email: payload.email,
      emailVerified,
      isAnonymous: payload.isAnonymous,
      phoneNumber: payload.phoneNumber,
      photoURL,
      providerData: payload.providerData
    }
  }

これでサインインしたあとにリロードしてもボタン等がちらつかなくなったと思います。

まとめ

ざっくりとですが Firebase Authentication を使った認証の実装の一例を紹介しました。

Firebase は他にも便利な機能があるのでもっと紹介してきたいと思います!


Written with StackEdit.

2019年8月26日月曜日

firebase-tools v7.1.1 release note 和訳

※翻訳勉強中のため、間違っている可能性があります。


https://github.com/firebase/firebase-tools/releases/tag/v7.1.1

  • Fixes issue deploying scheduled functions for non us-central project locations.
  • Cleans up unnecessary emulator JAR files when a new one is downloaded.

  • us-central 以外のプロジェクトの場所にスケジュールされた機能をデプロイする際の問題を修正しました。
  • 新しいファイルがダウンロードされたときに、不要なエミュレータJARファイルをクリーンアップします。

Written with StackEdit.

2019年8月23日金曜日

firebase-tools v7.1.0 release note 和訳

※翻訳勉強中のため、間違っている可能性があります。


https://github.com/firebase/firebase-tools/releases/tag/v7.1.0

  • Auto-download Firestore and RTDB emulators when using emulators:start.
  • Make functions:shell respect the --port argument.
  • Improve error message when firebase serve can’t acquire the right port.
  • Allow running the Firestore and RTDB emulators without a configuration.
  • Fixes bug where profiler table rendering crashes due to undefined path.
  • Allow the Firestore emulator to give more information about invalid rulesets.
  • Hot reload firestore rules on change.
  • Upgrade archiver dependency to 3.0.0.
  • Update functions init templates to v3.1.0
  • Fix emulation of https functions.
  • Fix bug where calling clearFirestoreData against the Firestore Emulator fails if more than 500 documents exist.
  • Fix multipart uploads in emulator.
  • Fixes long responses from emulator being truncated.
  • Fixes a bug where admin.firestore() and app.firestore() behaved differently.
  • Fix issue with npm 6.10 where an internal version check of the firebase-functions SDK caused a crash.
  • Fix bug in Firestore emulator where queries with startAfter had incorrect boundary behavior.
  • Fix bug where too many functions would hang the emulator.
  • Improves ability of Firebase CLI to be used with service accounts.

  • emulators:start を使うときは、FirestoreとRTDBエミュレータを自動ダウンロードします。
  • functions:shell--port 引数を尊重するようにします。
  • firebase serve が正しいポートを取得できない場合のエラーメッセージを改善しました。
  • 設定なしでFirestoreおよびRTDBエミュレータを実行することを許可します。
  • 未定義の path が原因でプロファイラテーブルのレンダリングがクラッシュするバグを修正しました。
  • Firestoreエミュレータが無効なルールセットについてより多くの情報を提供できるようにします。
  • Firestore rules の変更時にホットリロードします。
  • アーカイバの依存関係を3.0.0にアップグレードします。
  • Functions 初期化テンプレートを v3.1.0 に更新。
  • https Functionsのエミュレーションを修正しました。
  • Firestore Emulatorに対して500を超えるドキュメントが存在すると、clearFirestoreData の呼び出しが失敗するというバグを修正しました。
  • エミュレータでのマルチパートアップロードを修正しました。
  • エミュレータからの長い応答が切り捨てられるのを修正。
  • admin.firestore()app.firestore() の動​​作が異なるというバグを修正しました。
  • firebase-functions SDKの内部バージョンチェックがクラッシュを引き起こすnpm 6.10 の問題を修正しました。
  • startAfterを使ったクエリが誤った境界動作をしていたFirestoreエミュレータのバグを修正しました。
  • Functionsが多すぎるとエミュレータをハングさせるというバグを修正しました。
  • Firebase CLIがサービスアカウントで使用される機能が向上しました。

Written with StackEdit.

2019年8月19日月曜日

firebase-tools v7.0.2 release note 和訳

※翻訳勉強中のため、間違っている可能性があります。


https://github.com/firebase/firebase-tools/releases/tag/v7.0.2

  • Fixes a bug where the functions emulator did not work with firebase-functions versions 3.x.x.
  • Make the Functions emulator automatically point to the RTDB emulator when running.
  • Fixes issue where the functions runtime would not always exit on success (#1346).

  • Functions エミュレータが firebase-functions のバージョン 3.x.x で動作しなかったバグを修正しました。
  • 実行時にFunctions エミュレータが自動的にRTDBエミュレータを指すようにします。
  • Functions ランタイムが成功時に常に終了しない問題を修正しました。 (#1346)

Written with StackEdit.

2019年7月30日火曜日

firebase-tools v7.0.1 release note 和訳

※翻訳勉強中のため、間違っている可能性があります。


https://github.com/firebase/firebase-tools/releases/tag/v7.0.1

  • Fixes bug where firebase serve --only functions dropped the host argument.
  • Fixes bug where grpc-js connections used the wrong credentials.
  • Fixes bug where firebase open would return an error.
  • Sets version of inquirer package to prevent issues in firebase init.

  • firebase serve --only functions がホスト引数を削除していたバグを修正しました。
  • grpc-js 接続が間違った認証情報を使っていたバグを修正しました。
  • firebase open がエラーを返すというバグを修正しました。
  • firebase init の問題を防ぐために inquirer パッケージのバージョンを設定します。

Written with StackEdit.

2019年7月6日土曜日

firebase-tools v7.0.0 release note 和訳

※翻訳勉強中のため、間違っている可能性があります。


https://github.com/firebase/firebase-tools/releases/tag/v7.0.0

  • BREAKING The CLI no longer supports being run in Node 6 environments.
  • BREAKING RTDB Emulator comes up with open rules by default.
  • Modify RTDB emulator to publish triggers to the functions emulator.
  • Modify functions emulator to invoke RTDB emulator triggers.
  • Engines field is now required in package.json for functions deploys.
  • Fix bug where raw body was missing on HTTPS function request.
  • Fix bug with Functions Emulator and firebase-admin@8.0.0.
  • Fix req.baseUrl in Functions Emulator.
  • Add FUNCTIONS_EMULATOR environmental variable when running in Functions Emulator.
  • Set FIRESTORE_EMULATOR_HOST env var in “emulators:exec”.
  • Fix bug in printing Function logs when the text payload was undefined.
  • Update functions init templates to use firebase-functions v3 and firebase-admin v8.

  • 重要な変更 CLI は Node6 環境での実行をサポートしなくなりました。
  • 重要な変更 RTDB エミュレータはデフォルトでオープンルールを思い付きます。
  • Functions エミュレータにトリガを発行するようにRTDBエミュレータを変更します。
  • RTDBエミュレータトリガを呼び出すように Functions エミュレータを修正します。
  • Functions のデプロイには、package.json で Engines フィールドが必須になりました。
  • raw body がHTTPS関数リクエストで欠けていたバグを修正しました。
  • Functions エミュレータと firebase-admin@8.0.0 のバグを修正。
  • Functions エミュレータの req.baseUrl を修正。
  • Functions エミュレータで実行するときは、 FUNCTIONS_EMULATOR 環境変数を追加してください。
  • "emulators:exec" に FIRESTORE_EMULATOR_HOST 環境変数を設定してください。
  • テキストペイロードが未定義のときにFunctionログを印刷する際のバグを修正しました。
  • firebase-functions v3firebase-admin v8 を使うように functions init テンプレートを更新。

翻訳メモ

BREAKING重要な変更

BREAKING だけだと 壊れる とかになるが、他の Release Note 等を見てみると breaking changes という表現が使われていた。
これだと 重要な変更 といった意味になるようなのでこれを採用した。


Written with StackEdit.

2019年7月3日水曜日

firebase-tools v6.12.0 release note 和訳

※翻訳勉強中のため、間違っている可能性があります。


https://github.com/firebase/firebase-tools/releases/tag/v6.12.0

  • Set FIRESTORE_EMULATOR_HOST env var in “emulators:exec”.
  • Fixes bug in printing Function logs when the text payload was undefined.
  • Fixes bug in Firestore emulator where property paths with special characters would cause errors due to ClassNotFound exceptions.
  • Fixes bug in Firestore emulator where auto-id allocation only worked once per collection.
  • Adds REST API to Firestore emulator for setting security rules.
  • Functions emulator now limits request sizes to 10MB.
  • Fixes bug where firebase emulators:exec would succeed even if the script failed.
  • Initializing Firestore or Storage now requires a cloud resource location to be set.

  • FIRESTORE_EMULATOR_HOST 環境変数を “emulators:exec” に設定してください。
  • テキストペイロードが未定義のときにFunctionログを印刷する際のバグを修正しました。
  • 特殊文字を含むプロパティパスで ClassNotFound 例外が原因でエラーが発生するFirestoreエミュレータのバグを修正しました。
  • 自動ID割り当てがコレクションごとに1回しか機能しなかったFirestoreエミュレータのバグを修正しました。
  • セキュリティルールを設定するためのREST APIをFirestoreエミュレータに追加します。
  • Functionsエミュレータは要求サイズを10MBに制限しました。
  • スクリプトが失敗しても firebase emulators:exec が成功するというバグを修正しました。
  • FirestoreまたはStorageを初期化するには、クラウドリソースの場所を設定する必要があります。

Written with StackEdit.

2019年6月29日土曜日

firebase-tools v6.11.0 release note 和訳

※翻訳勉強中のため、間違っている可能性があります。


https://github.com/firebase/firebase-tools/releases/tag/v6.11.0

  • Fixed bug in Firestore emulator where some FieldTransforms were sending back incorrect results.
  • Fixed bug where read-only transactions would cause errors in the Firestore emulator.
  • Fixed bug where collection group query listeners would cause errors in the Firestore emulator.
  • Fixed bug where query parameters were not sent to HTTP functions.
  • Fixed bug where some HTTP methods (like DELETE) were not allowed.
  • Fixed bug where CORS headers were too restrictive.
  • Fixed bug where CSV exporting users with commas in displayName causes columns to no longer align.
  • Fixed bug where environment variables are unset for script in emulators:exec.
  • emulators:exec script now inherits stdout and stderr.
  • Improved reliability and performance of project listing for firebase use command.
  • Fixed bug where firestore:delete skips legacy documents with numeric IDs.
  • Fixed bug in Firestore emulator where it ignored x-forwarded-host header.
  • Improved Scheduled Functions deployment to keep and not recreate schedules that are already correct.

  • いくつかの FieldTransforms が誤った結果を送り返していたFirestoreエミュレータのバグを修正しました。
  • 読み取り専用トランザクションがFirestoreエミュレータでエラーを引き起こすというバグを修正しました。
  • Collection Group Query リスナーがFirestoreエミュレータでエラーを引き起こすというバグを修正しました。
  • クエリパラメータがHTTP関数に送信されなかったバグを修正しました。
  • いくつかのHTTPメソッド(DELETEなど)が許可されていなかったバグを修正しました。
  • CORSヘッダの制限が厳しすぎるというバグを修正。
  • displayName にカンマを使用してCSVをエクスポートすると、列が整列されなくなるというバグが修正されました。
  • 環境変数が emulators:exec のスクリプトに設定されていないバグを修正しました。
  • emulators:exec スクリプトは現在 stdoutstderr を継承しています。
  • firebase use コマンドのプロジェクト一覧の信頼性とパフォーマンスが向上しました。
  • firestore:delete が数値IDを持つレガシードキュメントをスキップするバグを修正しました。
  • Firestoreエミュレータが x-forwarded-host ヘッダを無視していたバグを修正しました。
  • Scheduled Functions のデプロイを改善し、すでに正しいスケジュールを保持し、再作成しないようにしました。

Written with StackEdit.

Nuxt.js v2.7.1 release note 和訳

※翻訳勉強中のため、間違っている可能性があります。


https://github.com/nuxt/nuxt.js/releases/tag/v2.7.1

Fixes

修正

  • builder: use warn only for mismatched dependencies (#5723)
  • webpack: correctly resolve consola for the client bundle (#5729)

  • builder: 依存関係が一致しない場合にのみ警告を使用する (#5723)
  • webpack: クライアントバンドル時にconsolaを正しく解決する (#5729)

Dependency Upgrades

依存関係のアップグレード



Written with StackEdit.

2019年6月20日木曜日

Nuxt.js v2.7.0 release note 和訳

※翻訳勉強中のため、間違っている可能性があります。


https://github.com/nuxt/nuxt.js/releases/tag/v2.7.0

DX Improvements 💅

SSR logs in your browser 🖥️

SSRのログをブラウザへ出力


We all know the console.log debugging method, but when working with universal applications, you have to remember that sometimes, your logs are in your terminal and not in your browser console.

console.logというデバッグ方法がありますが、ユニバーサルアプリケーションを作成している場合、ログはブラウザコンソールではなくターミナルに出力されることがあるのを覚えておかなければなりません。


This can be really annoying when developing a Nuxt.js application, starting with this version and running nuxt dev, the ssr logs are now reported to your browser console:

このバージョンから始めて nuxt devを実行して、Nuxt.jsアプリケーションを開発するとき、これは本当に厄介なことになるかもしれません、SSRのログはブラウザのコンソールに表示されるようになりました。


Detecting store/ creation 👀

store/ ディレクトリの作成を検出


Nuxt.js now detects when you created a store/ directory and will reload himself auto-magically so you don’t have to restart it anymore.

Nuxt.jsは、あなたが store/ディレクトリを作成したことを検出し、自動的に自分自身を再ロードするので、もう再起動する必要はありません。


PS: We also improved the serverMiddleware watch to restart Nuxt.js and clean their cache

PS:Nuxt.jsを再起動してキャッシュを消去するために serverMiddlewareの監視を改良しました。

Fixes ✔️

修正

  • builder: corretly detect mode of hashed plugins (#5695)
  • builder: call watch:restart after watch:fileChanged hook (#5620)
  • utils: node v8 not support dotAll in regex (#5608)
  • vue-app: properly catch component loading error (#5687) (#5688) (#5690)
  • vue-app: consider watchQuery option in routerViewKey (#5516)
  • vue-renderer: await on spa:templateParams hook (#5619)
  • webpack: set log level to warn for HardSourcePlugin (#5653)
  • vue-app: properly catch loading error in component prefetching (#5688) (#5690)
  • vue-app: avoid using aliases in templates (#5656)
  • builder: watch store dir and serverMiddleware paths (#5681)

  • builder: ハッシュプラグインのモードを正しく検出する (#5695)
  • builder: watch:fileChangedフックの後にwatch:restartを呼び出す (#5620)
  • utils: ノードv8は正規表現でdotAllをサポートしていません (#5608)
  • vue-app: コンポーネントの読み込みエラーを正しく検出 (#5687) (#5688) (#5690)
  • vue-app: routerViewKeywatchQueryオプションを検討してください(#5516)
  • vue-renderer: spa:templateParamsフックを待ってください (#5619)
  • webpack: ログレベルを HardSourcePluginに警告するように設定します (#5653)
  • vue-app: コンポーネントのプリフェッチでロードエラーを適切に検出する (#5688) (#5690)
  • vue-app: テンプレートでエイリアスを使用しない (#5656)
  • builder: storeディレクトリと serverMiddlewareパスを監視する (#5681)

Features 🚀

特徴

  • vue-app: rename transition to pageTransition and deprecate it (#5558)
  • vue-renderer/vue-app: report SSR console logs to the browser with consola (#5673)
  • webpack: suppress not found typescript warnings (#5635)
  • webpack: extendable babel.presets and babel envName (#5637)
  • configurable aliases (#5655)

  • vue-app: transitionpageTransitionに改名して非推奨になりました (#5558)
  • vue-renderer/vue-app: SSRコンソールログをconsolaでブラウザに報告する (#5673)
  • webpack: 見つからないタイプスクリプトの警告を抑制 (#5635)
  • webpack: 拡張可能なbabel.presetsとバベルenvName(#5637)
  • 設定可能なエイリアス (#5655)

Refactors 🧹

リファクタリング

  • server: exclude dist files request from browser detection (#5571)
  • vue-renderer: remove chalk in renderer (#5609)
  • vue-renderer: split renderer into ssr, spa and modern (#5559)
  • move modern detection from server to utils (#5584)

  • server: ブラウザ検出からdistファイル要求を除外する(#5571)
  • vue-renderer: レンダラーのchalkの警告を削除 (#5609)
  • vue-renderer: レンダラーをssr、spa、modernに分割 (#5559)
  • 最新ブラウザの検出をサーバーからutilsに移動 (#5584)

Examples 📚

  • auth-routes: fix typo (#5651)
  • babel-preset-app: add core-js@3 example in the readme (#5633)
  • docker: fix Dockerfile casing (#5705)

  • auth-routes: typoを修正 (#5651)
  • babel-preset-app: readmeにcore-js@3の例を追加 (#5633)
  • docker: Dockerfileのスペルを修正 (#5705)

Typescript 👷

Only for typescript users, Nuxt.js v2.7 dropped support for node < 8.6 relate to ts-loader v6

TypeScriptユーザのみ、Nuxt.js v2.7ではts-loader v6に関連してNode v8.6未満のサポートを終了しました

  • add babel config types (#5666)
  • upgrade ts-loader to v6 (#5691)
  • fix context.app type (#5701)
  • fix extendRoutes method type (#5700)
  • prevent ts-node to register twice (#5699)

  • config typesbabelを追加 (#5666)
  • ts-loaderをv6にアップグレード (#5691)
  • context.appタイプを修正 (#5701)
  • extendRoutesメソッドの型を修正 (#5700)
  • ts-nodeが2回登録されるのを防ぎます (#5699)

Written with StackEdit.

2019年6月13日木曜日

JestでVuex.Storeのテストを行う

NuxtでTypeScriptを使うときのVeux.storeの設定方法で作成したプロジェクトに Jest でのユニットテストをつけていきます。

できあがったものは以下のリポジトリになります。
https://github.com/TAC/nuxt-vuex-typescript-jest-example

Jestの追加

Nuxt を新規作成時のオプションでテストフレームワークに Jest を選択すると自動で追加されますが、それらを手動で追加してみます。

yarn add -D jest vue-jest babel-jest ts-jest eslint-plugin-jest @vue/test-utils babel-core@^7.0.0-bridge.0

Babelのバージョンが7になっているのでbabel-core@^7.0.0-bridge.0 が必要になります。
Vuex.StoreTypeScriptなのでts-jestも追加します。

Jestの設定

jest.config.js.babelrc を以下の内容で追加します。

app/jest.config.ts

module.exports = {
  moduleNameMapper: {
    '^vue$': 'vue/dist/vue.common.js',
    '^@/(.*)$': '<rootDir>/$1',
    '^~/(.*)$': '<rootDir>/$1'
  },
  moduleFileExtensions: ['js', 'ts', 'vue'],
  transform: {
    '^.+\\.js$': 'babel-jest',
    '^.+\\.ts$': 'ts-jest',
    '^.+\\.vue$': 'vue-jest'
  },
  collectCoverage: false,
  collectCoverageFrom: [
    '<rootDir>/components/**/*.vue',
    '<rootDir>/pages/**/*.vue',
    '<rootDir>/store/**/*.ts'
  ]
}

.babelrc

{
  "env": {
    "test": {
      "presets": [
        [
          "@babel/preset-env",
          {
            "targets": {
              "node": "current"
            }
          }
        ]
      ]
    }
  }
}

テストの作成

コンポーネントのテスト

まずはコンポーネントのテストから。

import Vuex from 'vuex'
import { cloneDeep } from 'lodash'
import { createLocalVue, mount } from '@vue/test-utils'
import Counter from '@/components/Counter.vue'
import counter from '@/store/modules/counter.ts'

const localVue = createLocalVue()
localVue.use(Vuex)

describe('components/Counter.vue', () => {
  let wrapper
  const initValue = 10
  beforeEach(() => {
    // create store
    const store = new Vuex.Store({
      'modules': {
        'modules/counter': cloneDeep(counter)
      }
    })

    // initialize store
    store.commit('modules/counter/SET_VALUE', initValue)

    // mount Vue Component
    wrapper = mount(Counter, {
      store: store,
      localVue
    })
  })

  describe('template', () => {
    test('snapshot correctly', () => {
      expect(wrapper.element).toMatchSnapshot()
    })
  })

  describe('script', () => {
    test('get counter value correctly', () => {
      expect(wrapper.vm.counter).toBe(initValue)
    })
    test('increment button correctly', () => {
      wrapper.find('[name="increment"]').trigger('click')
      expect(wrapper.vm.counter).toBe(initValue + 1)
    })
    test('decrement button correctly', () => {
      wrapper.find('[name="decrement"]').trigger('click')
      expect(wrapper.vm.counter).toBe(initValue - 1)
    })
  })
})

@vue/test-utilscreateLocalVueを使ってローカルコピーを作成しておきます。
Vuex.Storeには使用するstoreの定義をmodulesの該当パスにcloneDeepでコピーしてあげます。
そうすることで他のテスト時に値が汚染されなくなります。
あとは好きなテストを記述していきます。
wrapper.find()にはCSSセレクターを指定できるので、テスト対象のエレメントなどが一意になるようにnameclassを設定しておくと探しやすくなります。

Veux.Storeのテスト

次はstoreのテストです。

import Vuex from 'vuex'
import { cloneDeep } from 'lodash'
import { createLocalVue } from '@vue/test-utils'
import counter from '@/store/modules/counter.ts'

const localVue = createLocalVue()
localVue.use(Vuex)

describe('store/modules/counter.ts', () => {
  let store
  const initValue = 10
  beforeEach(() => {
    // create store
    store = new Vuex.Store({
      'modules': {
        'modules/counter': cloneDeep(counter)
      }
    })

    // initialize store
    store.commit('modules/counter/SET_VALUE', initValue)
  })

  describe('getters', () => {
    test('get value correctly', () => {
      expect(store.getters['modules/counter/value']).toBe(initValue)
    })
  })

  describe('actions', () => {
    test('increment correctly', () => {
      const addValue = 1
      store.dispatch('modules/counter/increment', addValue)
      expect(store.getters['modules/counter/value']).toBe(initValue + addValue)
    })
    test('decrement correctly', () => {
      const subValue = 1
      store.dispatch('modules/counter/decrement', subValue)
      expect(store.getters['modules/counter/value']).toBe(initValue - subValue)
    })
  })
})

ほぼコンポーネントと同じですが、作成したstoreを使用してテストしたいactionsなどを実行していく形になります。

package.jsonに以下のスクリプトを追加します。

    "test": "jest",
    "test:coverage": "jest --coverage",

これで準備が整ったので、yarn testを実行すると以下のような結果が得られると思います。

PS C:\work\repos\nuxt\jest\app> yarn test
yarn run v1.15.2
$ jest
$ C:\work\repos\nuxt\jest\app\node_modules\.bin\jest
 PASS  tests/store/modules/counter.test.js
 PASS  tests/components/Counter.test.js

Test Suites: 2 passed, 2 total
Tests:       7 passed, 7 total
Snapshots:   1 passed, 1 total
Time:        3.831s
Ran all test suites.
Done in 5.54s.

テストカバレッジも見たい時はtest:coverageを実行します。

PS C:\work\repos\nuxt\jest\app> yarn test:coverage
yarn run v1.15.2
$ jest --coverage
$ C:\work\repos\nuxt\jest\app\node_modules\.bin\jest --coverage
 PASS  tests/store/modules/counter.test.js
 PASS  tests/components/Counter.test.js
---------------|----------|----------|----------|----------|-------------------|
File           |  % Stmts | % Branch |  % Funcs |  % Lines | Uncovered Line #s |
---------------|----------|----------|----------|----------|-------------------|
All files      |    65.38 |      100 |     87.5 |    65.22 |                   |
 components    |    71.43 |      100 |      100 |    71.43 |                   |
  Counter.vue  |      100 |      100 |      100 |      100 |                   |
  Logo.vue     |        0 |      100 |      100 |        0 |             10,13 |
 pages         |        0 |      100 |      100 |        0 |                   |
  index.vue    |        0 |      100 |      100 |        0 |         8,9,10,18 |
 store         |        0 |      100 |        0 |        0 |                   |
  index.ts     |        0 |      100 |        0 |        0 |               1,3 |
 store/modules |      100 |      100 |      100 |      100 |                   |
  counter.ts   |      100 |      100 |      100 |      100 |                   |
---------------|----------|----------|----------|----------|-------------------|

Test Suites: 2 passed, 2 total
Tests:       7 passed, 7 total
Snapshots:   1 passed, 1 total
Time:        10.346s
Ran all test suites.
Done in 11.68s.

Written with StackEdit.

2019年6月4日火曜日

NuxtでTypeScriptを使うときのVeux.storeの設定方法

NuxtTypeScript で構築した時に Vuex.store の構成をどうしたらいいかを考えてみました。

結論としては vuex-class-component を使用してモジュールモードで構成する、という感じになりました。

完成したプロジェクトは以下のリポジトリになります。
https://github.com/TAC/nuxt-vuex-typescript-example

簡単なカウンターを作成しました。

環境

- version
OS Windows10
Node.js v8.15.0
Nuxt.js v2.7.1

どのモジュールがいいのか?

Vuex.store を簡潔にかけるようにするモジュールはいくつかあって、以下の2つが主に使われてるようでした。

vuex-class-component
https://github.com/michaelolof/vuex-class-component

vuex-module-decorators
https://github.com/championswimmer/vuex-module-decorators

Nuxt では store ディレクトリ以下にファイルを配置すれば自動で Vuex のモジュールモードで構築してくれます。

ただし、 store/index.ts が関数を返すとクラシックモードになり、その恩恵を受けることができません。
また、各モジュールのファイルも関数ではなくオブジェクトを返す必要があり、それができるのが vuex-class-component でした。
また、Nuxt.jsv3.0.0 ではクラシックモードが非対応になるようなので、それを考慮に入れての選択になります。

ディレクトリ構成

以下のような構成を考えてみました。

src/store/index.ts
  +- modules/
      +- counter.ts

modules に機能毎に分けたモジュールを作成していく感じです。
models とか作ってDBのテーブル毎などに分けたモジュールを作成してもいいかもしれない。

counter.ts

デコレータを使って以下のように記述します。

import {
  VuexModule,
  Module,
  action,
  getter,
  mutation
} from 'vuex-class-component'

interface Counter {
  value: number
}

@Module({ namespacedPath: 'modules/counter/', target: 'nuxt' })
export class CounterStore extends VuexModule implements Counter {
  @getter value = 0

  @mutation
  public SET_VALUE(value: number) {
    this.value = value
  }

  @action()
  public increment(value: number) {
    this.SET_VALUE(this.value + value)
  }

  @action()
  public decrement(value: number) {
    this.SET_VALUE(this.value - value)
  }
}

export default CounterStore.ExtractVuexModule(CounterStore)

ポイントは2つあって、1つは @Module のオプションに namespacedPathtarget:'nuxt' を指定するところです。
もう1つは、最後の行で ExtractVuexModule を使ってオブジェクト形式で export してあげるところです。
これで Vuex.store のモジュールモードに対応できました。

index.ts

index.ts もデコレータを使って記述しようとしたのですが、 nuxtServerInit の定義がどうしてもできなかったので、デコレータを使わずに記述します。

export const actions = {
  nuxtServerInit: ({ commit }) => {
    commit('modules/counter/SET_VALUE', 20)
  }
}

ここでは modules/counter の初期化を行ってみました。

呼び出すComponent

components/Counter.vue を作成します。

<template lang="pug">
  div [counter]
    div {{ counter }}
    div
      button(@click="increment") +
      button(@click="decrement") -
</template>

<script lang="ts">
import { Component, Vue } from 'nuxt-property-decorator'

@Component
export default class Counter extends Vue {
  get counter() {
    return this.$store.getters['modules/counter/value']
  }

  public increment() {
    this.$store.dispatch('modules/counter/increment', 1)
  }
  public decrement() {
    this.$store.dispatch('modules/counter/decrement', 1)
  }
}
</script>

呼び出す箇所は通常の使い方と同じです。

これでごちゃごちゃしがちな store 周りをスッキリとかけるようになったかと思います。

Written with StackEdit.

2019年5月23日木曜日

NuxtでTypeScriptを使えるようにする

https://ja.nuxtjs.org/guide/typescript/
基本的に上記の手順で設定していけばOK

  1. モジュールの追加
  2. nuxt.config.jsのTypeScript化
  3. nuxt-property-decoratorの導入
  4. ESLintの設定

完成したプロジェクトは以下になります。
ご査収ください。
https://github.com/TAC/nuxt-typescript-example

環境

- version
OS Windows10
Node.js v8.15.0
Nuxt.js v2.7.1

1. モジュールの追加

yarn add -D @nuxt/typescript
yarn add ts-node

上記のモジュール追加後、空のtsconfig.jsonファイルを作成して、nuxtコマンドを実行するとデフォルト値で更新されます。

2. nuxt.config.jsのTypeScript化

nuxt.config.jsnuxt.config.tsにリネームします。
そしてドキュメントの手順通り以下を追記して、コンフィグの export 方法を変更します。

import NuxtConfiguration from  '@nuxt/config'
import pkg from './package'

const nuxtConfig: NuxtConfiguration = {
...
}

export default nuxtConfig

ただ、上記の変更だけだと下記のようなエラーが出るはずです。

 ERROR  ⨯ Unable to compile TypeScript:                                                                                                                                                                 23:58:04
nuxt.config.ts:2:17 - error TS2307: Cannot find module './package'.

2 import pkg from './package'
                  ~~~~~~~~~~~
nuxt.config.ts:51:9 - error TS2532: Object is possibly 'undefined'.

51         config.module.rules.push({
           ~~~~~~~~~~~~~


  nuxt.config.ts:2:17 - error TS2307: Cannot find module './package'.

  2 import pkg from './package'
                    ~~~~~~~~~~~
  nuxt.config.ts:51:9 - error TS2532: Object is possibly 'undefined'.

  51         config.module.rules.push({
             ~~~~~~~~~~~~~

  at createTSError (node_modules\ts-node\src\index.ts:240:12)
  at reportTSError (node_modules\ts-node\src\index.ts:244:19)
  at getOutput (node_modules\ts-node\src\index.ts:360:34)
  at Object.compile (node_modules\ts-node\src\index.ts:393:11)
  at Module.m._compile (node_modules\ts-node\src\index.ts:439:43)
  at Module._extensions..js (module.js:664:10)
  at Object.require.extensions.(anonymous function) [as .ts] (node_modules\ts-node\src\index.ts:442:12)
  at Module.load (module.js:566:32)
  at tryModuleLoad (module.js:506:12)
  at Function.Module._load (module.js:498:3)

Done in 3.26s.

まずはこちらのエラー

Cannot find module './package'.

TypeScriptはデフォルトではJSONファイルのImportを許可していないのでコンフィグで設定を変更する必要があるようです。
https://www.typescriptlang.org/docs/handbook/compiler-options.html
"resolveJsonModule": truetscofig.jsonに追記します。

{
  "compilerOptions": {
    "resolveJsonModule": true
  }
}

そしてImport時には拡張子まで指定するようにすればOK

import pkg from './package.json'

続いてはこちらのエラー

Object is possibly 'undefined'.

config.moduleundefined になってしまうことがあるのが原因なので、undefined の場合の処理を追記します。
(参考サイト) https://qiita.com/dora1998/items/932506fa995962d4dc63#nuxtconfigjs–nuxtconfigts

  /*
   ** Build configuration
   */
  build: {
    publicPath: '/assets/',
    
    /*
     ** You can extend webpack config here
     */
    extend(config, ctx) {
      // Run ESLint on save
      if (ctx.isDev && ctx.isClient) {
+       if (!config.module) return
        config.module.rules.push({
          enforce: 'pre',
          test: /\.(js|vue|ts)$/,
          loader: 'eslint-loader',
          exclude: /(node_modules)/
        })
      }
    }
  }

これでひとまず起動できるようになっているはずです。

3. nuxt-property-decoratorの導入

コンポーネントの記述を楽にするためにデコレータモジュールを導入します。
ドキュメントの手順ではvue-property-decoratorとなっているが、nuxt-property-decoratorを追加します。

yarn add nuxt-property-decorator

各コンポーネントのスクリプト部分をTypeScriptにします。

page/index.vue

<script lang="ts">
import { Component, Vue } from 'nuxt-property-decorator'
import Logo from '@/components/Logo.vue'

@Component({
  components: {
    Logo
  }
})
export default class extends Vue {}
</script>

components/Logo.vue

<script lang="ts">
import { Component, Vue } from 'nuxt-property-decorator'

@Component
export default class Logo extends Vue {}
</script>

クラスの定義が必要になるので少し冗長になります。
なおnuxt.config.tssrcDirを設定している場合は、tsconfig.jsonbaseUrlをそれに合わせないとコンポーネントの読み込みに失敗します。

4. ESLintの設定

以下のモジュールを追加する。

yarn add -D @typescript-eslint/eslint-plugin

そして、デフォルトで作成されている.eslintrc.jsを以下のように修正します。

module.exports = {
  root: true,
  env: {
    browser: true,
    node: true
  },
  parserOptions: {
    parser: '@typescript-eslint/parser'
  },
  extends: [
    '@nuxtjs',
    'plugin:nuxt/recommended',
    'plugin:prettier/recommended',
    'prettier',
    'prettier/vue'
  ],
  plugins: [
    'prettier',
    '@typescript-eslint'
  ],
  // add your custom rules here
  rules: {
    '@typescript-eslint/no-unused-vars': 'error'
  }
}

package.jsonlintスクリプトにts拡張子を追加しておきます。

"lint": "eslint --ext .js,.vue,.ts --ignore-path .gitignore .",

以上でNuxtでTypeScriptを使用できるようになりました。

Cloud FunctionsもTypeScriptにする

https://firebase.google.com/docs/functions/typescript?hl=ja
こちらの手順に従って進めればOKですが、いくつか追加で行う作業が必要でした。

まずはディレクトリ構成ですが、カレントディレクトリにアプリのビルド環境がある場合、予期せぬ動作が起こるようでした。

myproject
 +- src            # Nuxt src dir
 +- package.json   # Nuxt build script
 +- tsconfig.json  # Nuxt tsconfig
 +- nuxt.config.ts
 +- functions/
      +- package.json   # functions build script
      +- tsconfig.json  # functions tsconfig

上記の構成だと Nuxt を ビルドした際に functions 以下もビルドされてしまいコンフィグ設定の違いによりエラーが出てしまいます。
なので以下のような構成で Nuxtfunctions を配置します。

myproject
 +- app
 |    +- src            # Nuxt src dir
 |    +- package.json   # Nuxt build script
 |    +- tsconfig.json  # Nuxt tsconfig
 |    +- nuxt.config.ts
 +- functions/
      +- package.json   # functions build script
      +- tsconfig.json  # functions tsconfig

それぞれのディレクトリでビルドすることで干渉しなくなります。

次に app で追加したモジュールとExpressの型定義も追加しておきます。
また、lint が通らなくなるので、@nuxt/config を追加しておきます。

yarn add -D @nuxt/typescript @types/express
yarn add ts-node @nuxt/config

そして、公式の手順にあった firebase init functions で作成された functions/src/index.ts に色々と修正をいれて最終的に以下のようになりました。

import * as functions from 'firebase-functions'
import * as express from 'express'
import NuxtConfiguration from '@nuxt/config'
const { Nuxt } = require('nuxt')

const nuxtConfig: NuxtConfiguration = {
  dev: false,
  buildDir: 'nuxt',
  build: {
    publicPath: '/assets/'
  }
}
const nuxt = new Nuxt(nuxtConfig)

async function handleRequest(req: express.Request, res: express.Response) {
  res.set('Cache-Control', 'public, max-age=600, s-maxage=1200')
  await nuxt.ready()
  return nuxt.render(req, res)
}

const app = express()
app.use(handleRequest)

exports.ssr = functions.https.onRequest(app)

functions へ移動してビルドをすると functions/lib/index.js が出力されます。

cd functions/
yarn build

あとはアプリのビルドの成果物と合わせてローカルエミュレータを起動して動作確認ができると思います。

Written with StackEdit.

2019年5月21日火曜日

firebase-tools v6.10.0 でNuxtのSSRアプリを動かすことができた!

下記の記事で動かないといってたものが動くようになりました!
https://blog.28go.jp/2019/05/firebase-tools_19.html

きっかけはこちらの記事です。
https://www.memory-lovers.blog/entry/2019/03/31/003456

https://nuxtjs.org/guide/release-notes#-strong-important-strong-migration-guide

どうやら Cloud Functions での Nuxt の呼び出し方が変わっていたようです。
上記の修正をすることで firebase-toolsv6.10.0 の ローカルエミュレータでも動作するようになりました。

ただ、functions のパッケージにいくつか追加するモジュールがあったので記載しておきます。

yarn add firebase-admin
yarn add -D firebase-functions-test

firebase-admin は使用していなくても必要になったようです。

以前検証に使ったリポジトリも修正しました。
https://github.com/TAC/nuxt-ssr-firebase-example

動かなくてもやもやしていたのでスッキリしました!

Written with StackEdit.