Gridsome(Vue.js)にAmplifyを導入する方法
May 02, 2020

DEVELOPMENT

web
node
AWS
Amplify
Cognito
Vue
Gridsome

はじめに

GridsomeReact製の静的サイトジェネレータのGatsbyを参考に作られた、Vue.jsの静的サイトジェネレータです。

これら静的サイトジェネレーターはHeadlessCMSからGraphQLにてデータを取得し、コード側とデータを切り離して運用することのできるJAMStackの構成を構築するのに不可欠です。

今回これら静的サイトジェネレーターにログイン認証機能が必要になったので、簡単に実装したいと思ったことろ、AWSAmplifyAuthCognito)を使うのがベストかと思い、導入してみましたが、意外と手こずりましたので、方法をご紹介したいと思います。

前提

  • Gridsomeでプロジェクト開始済み
  • AWSアカウントを持っている

Gridsome

Amplifyの設定

下記に詳細書いてますが、ざっくりここではコマンドだけ載せておきます。

$ npm install -g @aws-amplify/cli
  • ここでlocalから、AWSへアクセスするIAMの設定をします。
$ amplify configure

GridsomeへAmplifyを読ませる

ここが苦労しました、Try & Errorの末、入れられました

  • はじめにGridsomeのDir構造の紹介
- src/
  | - components/ // コンポーネント
  | - layouts/
       | - Default.vue //  全体のLayoutです
  | - pages/ // 各ページのファイルを作成します。
       | - Index.vue // トップページ
  | - templates/ // ブログ等、localのmarkdownファイルからブログ詳細ページ等の元となるvue component
  | - favicon.png
  | - main.js // 最初に呼ばれるjsファイル
  | - aws-exports.js // Amplify initしてpushすれば出来るファイル 主にCognito のpoolキーなど
- static/
- gridsome.config.js // configファイルで、pluginを読ませたり
- gridsome.server.js // サイトをジェネレートする時の内容等を書きます。contentfulなどのheadlessCMSからデータを取得し、記事分のページを作成する指示もここ
.
.
  • main.js にAWSのDocument通り読ませてみる
~
import Amplify, { Auth } from 'aws-amplify';
import awsconfig from './aws-exports';
~
export default function (Vue, { router, head, isClient }) {

  Amplify.configure(awsconfig);
~
~
  • 一旦走らせてみる
 ERROR  Failed to compile with 175 errors                                                                                                                                                       16:45:58

 error  in ./node_modules/@aws-amplify/pubsub/node_modules/graphql/index.mjs

Can't reexport the named export 'BREAK' from non EcmaScript module (only default export is available)

 error  in ./node_modules/@aws-amplify/pubsub/node_modules/graphql/index.mjs

Can't reexport the named export 'BreakingChangeType' from non EcmaScript module (only default export is available)

 error  in ./node_modules/@aws-amplify/pubsub/node_modules/graphql/index.mjs

Can't reexport the named export 'DEFAULT_DEPRECATION_REASON' from non EcmaScript module (only default export is available)

 error  in ./node_modules/@aws-amplify/pubsub/node_modules/graphql/index.mjs

Can't reexport the named export 'DangerousChangeType' from non EcmaScript module (only default export is available)

 error  in ./node_modules/@aws-amplify/pubsub/node_modules/graphql/index.mjs
  • 鬼のエラーです。よく読むと、すべて、mjsファイルだし、graphqlがなんちゃらと。

原因追求

ということですので、外部ライブラリのロードを除外しましょう。

rules: [ // fixes https://github.com/graphql/graphql-js/issues/1272
  {
    test: /\.mjs$/,
    include: /node_modules/,
    type: 'javascript/auto'
  }
]

これなんですが、Gridsomeの場合、webPackのConfigなんてどこにあるんあろう?

Gridsomeの場合どこに書く?

わからず、ちょっと途方にくれましたが公式に掲載がありました。

~
~
module.exports = {
~
~
  ],
  configureWebpack: {
    module:{
      rules: [ // fixes https://github.com/graphql/graphql-js/issues/1272
        {
          test: /\.mjs$/,
          include: /node_modules/,
          type: 'javascript/auto'
        }
      ]
    }
  }
}
  • これでAmplifyのロードが出来るようになりました。

とりあえずLogin画面の表示

どこか適当なページにAmplify提供のAuthのログイン画面を出してみる

  • pages/Index.vue
<template>
~
<amplify-authenticator>
  <h1>indexページ</h1>
</amplify-authenticator>
~
.
</template>
<script>
import '@aws-amplify/ui-vue'
</script>
  • ログインページが表示されたかと思います。
  • ログインするとindexページが表示されるという感じですね

Login認証の有無で、ルーティングする

例えば、ログインしていなくても見れるページと、していないと見れないページがあると思います。

この場合、ログイン状態をstore(vuex)でstateを保持して、それにより、ログイン認証が必要なページにアクセスした際、ログインしていなければLoginページに飛ばすなどの処理が必要になると思います。

でも認証が必要なページにひたすら、<amplify-authenticator />を入れるという手も有りにはありですが、ログイン有無で表示が変わるヘッダーのナビゲーションなどの対応もあるので、storeでちゃんと状態を管理し、routerで弾こうと思いました。

でもここで疑問、静的サイトジェネレーターってrouting管理どこでやってんの?

libraryの内部でやってます

厳しい…と思いきや、翌々main.jsを覗いてみると

~
export default function (Vue, { router, head, isClient }) {
~
~
  • router って変数あるではないか
  • これをいじればどうにか出来そうということでやってみます
  • ファイルの中身全部のせます
// This is the main.js file. Import global CSS and scripts here.
// The Client API can be used here. Learn more: gridsome.org/docs/client-api

import DefaultLayout from '~/layouts/Default.vue'

import Buefy from 'buefy'
import 'buefy/dist/buefy.css'

import Amplify from 'aws-amplify'
import '@aws-amplify/ui-vue'
import aws_exports from './aws-exports'

import VueLazyload from 'vue-lazyload' // 画像のlazy load
import { library } from '@fortawesome/fontawesome-svg-core'
import { fas } from '@fortawesome/free-solid-svg-icons'
import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome'

import Login from './pages/Login.vue'
import store from './store.js'

// Amplify読み込み
import { AmplifyEventBus, AmplifyPlugin } from 'aws-amplify-vue'
import * as AmplifyModules from 'aws-amplify'


function getUser() {
  return Amplify.Auth.currentAuthenticatedUser().then((data) => {
    if (data && data.signInUserSession) {
      store.commit('setUser', data);
      return data;
    }
  }).catch(() => {
    store.commit('setUser', null);
    return null;
  })
}

export default function (Vue, { router, head, isClient }) {

  let user

  Amplify.configure(aws_exports)

  Vue.use(AmplifyPlugin, AmplifyModules)

  // ユーザー管理
  getUser().then((user) => {
    if (user) {
      router.push({path: '/'});
    }
  })
  // ログイン状態管理
  AmplifyEventBus.$on('authState', async (state) => {
    if (state === 'signedOut'){
      user = null;
      store.commit('setUser', null);
      router.push({path: '/login/'});
    }
    else if (state === 'signedIn') {
      user = await getUser();
      router.push({path: '/'});
    }
  })

  // リダイレクト設定
  router.beforeResolve(async (to, from, next) => {
    if (to.matched.some((record) => {
      return record.path == "/cart"
    })) {
      user = await getUser();
      if (!user) {
        return next({
          path: '/login/'
        })
      }
      return next()
    }
    return next()
  })

  // load library.
  library.add(fas)

  Vue.use(Buefy, { // css framework
    defaultIconComponent: 'vue-fontawesome',
    defaultIconPack: 'fas',
  })
  Vue.use(VueLazyload) // 画像のlazy load
  Vue.component('font-awesome-icon', FontAwesomeIcon) // font icon
  // Set default layout as a global component
  Vue.component('Layout', DefaultLayout)

}
  • ここですね

    router.beforeResolve(async (to, from, next) => {
  • router.beforeResolveでどのパスのときに、認証が必要かのジャッジをして、getUserでログインされてなければ、login/に飛ばすという処理です。
  • routingのmetaデータにauthが必要かどうかみたいな処理を良くすると思いますが、静的サイトジェネレーターの場合、セットできないので、name か pathで処理をしようと思いましたが、debugした所、routingのnameキーも持っていなかったので、とりあえずpathで検証してます(今回はcartページが認証してないと見れない設定)
  • 前後のコードは、loginの有無をチェックして、状態をstoreに保存してます。
  • あと、cssにBuefyのロードと、fontのfont-awesomeのロードをしてます。
  • src/store.js 追加
import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

export default new Vuex.Store({
  state: {
    user: null
  },
  mutations: {
    // ユーザー情報保存
    setUser(state, user) {
    state.user = user
    },
  },
  actions: {
  },
  modules: {
  }
})

Loginページを用意する

あとは、ログインページ用のcomponentを用意するだけです。 最悪、以下でもOKじゃないでしょうか

<template>
  <amplify-authenticator />
</template>

まとめ

いかがでしたでしょうか。

  • Amplifyのロード
  • 静的サイトジェネレーターでのroutingのカスタマイズ

この部分についての対応方法を記載しました。 おそらくGatsbyでも同じ感じで行けるかと思います。というかもう少し楽かもしれません。

明日も頑張りましょう

うかい / 株式会社UKAI
うかい@代表取締役兼エンジニア株式会社UKAI
Nobuyuki Ukai

株式会社UKAI 代表取締役CEO。建築を専攻していたが、新卒でYahoo!Japanにエンジニアとして入社。その後、転職、脱サラ、フリーランスエンジニアを経て、田舎へ移住し、ゲストハウスの開業、法人成り。ゲストハウス2軒、焼肉店、カフェ、食品製造業を運営しています。詳細はこちらから

🙏よかったらシェアお願いします🙏
WRITTEN BY
うかい / 株式会社UKAI
うかい@代表取締役兼エンジニア株式会社UKAI

Nobuyuki Ukai

株式会社UKAI 代表取締役CEO。建築を専攻していたが、新卒でYahoo!Japanにエンジニアとして入社。その後、転職、脱サラ、フリーランスエンジニアを経て、田舎へ移住し、ゲストハウスの開業、法人成り。ゲストハウス2軒、焼肉店、カフェ、食品製造業を運営しています。詳細はこちらから

CONACT
入力して下さい
Slack からもどうぞ

※お気軽にどうぞ!(6月20日まで有効!お早めに)

COPYRIGHT © 2020 UKAI CO., LTD. ALL RIGHTS RESERVED.