Amplify + Vue.jsのクイックスタートを試してみた

# 背景・目的
フロントエンドアプリケーションについて、今まであまり触れてきませんでした。今回は、AWS Amplifyを使用してVue.jsを動かしてみます。

# まとめ
下記に特徴を簡単にまとめます

| 特徴| 説明|
|:--|:--|
| Vueとは| ・JavaScriptフレームワーク<BR>・宣言的でコンポーネントベースのプログラミングモデル
| 2つのコア機能 |・宣言的レンダリング<BR>・リアクティビティ | 
| Vueの活用方法| ・静的なHTMLを拡充<BR>・Webコンポーネントとして埋め込む<BR>・SPA （シングルアプリケーション）<BR>・SSR (サーバサイドレンダリング)<BR>・SSG （静的サイトジェネレーション）<BR>・デスクトップ、モバイル、WebGL、ターミナルをターゲットにする開発|
| 単一ファイルコンポーネント|・SFCと略される<BR>・コンポーネントのロジック（JavaScript）、テンプレート（HTML）、スタイルシート（CSS）を単一のファイルに収めたもの|
| 2つのAPIスタイル |・Options API<BR>・Composition API|
|Options API|「コンポーネントのインスタンス」（サンプルに見られる this) を中心とするもの|
|Composition API|リアクティブな状態変数を関数のスコープ内で直接宣言し、複数の関数の組み合わせによって状態を組み立てて複雑な処理を扱おう、という考え方|

# 概要
## Amplifyについて
Amplifyについては、以前、下記の記事で簡単にまとめていますので割愛しますが、
ざっくりいうと、フロントエンジニアがAWSでフルスタックアプリケーションを素早く構築する専用ツールと機能を備えたサービスになります。

https://qiita.com/zumax/items/b0a5e02c625a98bef3b9

## Vueとは

下記を基に整理します。

https://ja.vuejs.org/guide/introduction.html

> Vue（発音は /vjuː/、view と同様）は、ユーザーインターフェースの構築のための JavaScript フレームワークです。標準的な HTML、CSS、JavaScript の上に構築され、あらゆる複雑さのユーザーインターフェースを効率的に開発するのに役立つ、宣言的でコンポーネントベースのプログラミングモデルを提供します。

- JavaScriptフレームワーク
- 宣言的でコンポーネントベースのプログラミングモデル

> 最小限のサンプルは、次のようになります:
```Js
import { createApp } from 'vue'

createApp({
  data() {
    return {
      count: 0
    }
  }
}).mount('#app')
```

```html
<div id="app">
  <button @click="count++">
    Count is: {{ count }}
  </button>
</div>
```

> 上のサンプルは、Vue が備える次の 2 つのコア機能を示すものです:
> - 宣言的レンダリング: Vue では、標準的な HTML を拡張したテンプレート構文を使って、HTML の出力を宣言的に記述することができます。この出力は、JavaScript の状態に基づきます。

> - リアクティビティー: Vue は JavaScript の状態の変化を自動的に追跡し、変化が起きると効率的に DOM を更新します。

- 2つのコア機能
    - 宣言的レンダリング
        - 標準的なHTMLを拡張したテンプレート構文を使って、HTMLをの出力を宣言的にかける
    - リアクティビティ
        - JavaScriptの状態の変化を自動的に追跡し、変化が起きると効率的にDOMを更新する

### プログレッシブフレームワーク​
> Vue は、フロントエンド開発に必要な一般的な機能のほとんどをカバーするフレームワークであり、エコシステムでもあります。しかし、Web はきわめて多様です。私たちが Web で構築するものは、形態の点でも規模の点でもそれぞれ大きく異なります。Vue はそのことを念頭に置いて、柔軟性を提供する設計、そして段階的に適用できる設計となっています。Vue はユースケースに応じて、以下のようにさまざまな方法で活用することができます:


> - ビルドステップなしで静的な HTML を拡充する
> - 任意のページに Web コンポーネントとして埋め込む
> - シングルページアプリケーション（SPA）
> - フルスタック / サーバーサイドレンダリング（SSR）
> - Jamstack / 静的サイトジェネレーション（SSG）
> - デスクトップ、モバイル、WebGL、さらにはターミナルをターゲットとする開発

Vueの活用方法
- 静的なHTMLを拡充
- Webコンポーネントとして埋め込む
- SPA （シングルアプリケーション）
- SSR (サーバサイドレンダリング)
- SSG （静的サイトジェネレーション）
- デスクトップ、モバイル、WebGL、ターミナルをターゲットにする開発

### 単一ファイルコンポーネント
> ビルドツールが利用可能な Vue プロジェクトでは、単一ファイルコンポーネントと呼ばれる、HTML に似たファイル形式の Vue コンポーネントがよく利用されます（*.vue ファイルとしても知られ、「SFC」と略されます）。Vue の SFC は、その名前が示す通り、コンポーネントのロジック（JavaScript）、テンプレート（HTML）、およびスタイル（CSS）を単一のファイルに収めたものです。先ほどのサンプルコードを SFC 形式で書いたものを以下に示します:

- 単一ファイルコンポーネントと呼ばれるVueコンポーネントがよく利用される
- SFCと略される
- コンポーネントのロジック（JavaScript）、テンプレート（HTML）、スタイルシート（CSS）を単一のファイルに収めたもの

下記にサンプルを記載します。

```vue
<script>
export default {
  data() {
    return {
      count: 0
    }
  }
}
</script>

<template>
  <button @click="count++">Count is: {{ count }}</button>
</template>

<style scoped>
button {
  font-weight: bold;
}
</style>
```

### 2 つの API スタイル
> Vue コンポーネントを作成する際は、Options API、そして Composition API と呼ばれる 2 種類の異なる API スタイルが利用できます。

下記の2つのAPIスタイルが利用できる
- Options API
- Composition API

#### Options API
> Options API では、data、methods、mounted といった数々のオプションからなる 1 つのオブジェクトを用いてコンポーネントのロジックを定義します。これらのオプションによって定義されたプロパティには、コンポーネントのインスタンスを指す this を使って、関数内から次のようにアクセスできます:

- 数々のオプションからなる1つのオブジェクトを用いてコンポーネントのロジックを定義する

```vuejs
<script>
export default {
  // data() で返すプロパティはリアクティブな状態になり、
  // `this` 経由でアクセスすることができます。
  data() {
    return {
      count: 0
    }
  },

  // メソッドの中身は、状態を変化させ、更新をトリガーさせる関数です。
  // 各メソッドは、テンプレート内のイベントハンドラーにバインドすることができます。
  methods: {
    increment() {
      this.count++
    }
  },

  // ライフサイクルフックは、コンポーネントのライフサイクルの
  // 特定のステージで呼び出されます。
  // 以下の関数は、コンポーネントが「マウント」されたときに呼び出されます。
  mounted() {
    console.log(`The initial count is ${this.count}.`)
  }
}
</script>

<template>
  <button @click="increment">Count is: {{ count }}</button>
</template>
```

#### Composition API

> Composition API では、インポートした各種 API 関数を使ってコンポーネントのロジックを定義していきます。SFC において、Composition API は通常、\<script setup> と組み合わせて使用します。setup という属性を付けることで、Vue にコンパイル時の変形操作を実行してほしいというヒントが伝えられます。これにより、定型的な書式の少ない Composition API を利用することができます。たとえば、\<script setup> のなかで宣言したインポート、トップレベルの変数、トップレベルの関数は、テンプレート内で直接使うことができます。

- インポートした各種API関数を使ってコンポーネントのロジックを定義していく

> 先ほどと同じコンポーネントを、テンプレート部分は同一のまま、Composition API と \<script setup> に置き換えたサンプルとして以下に示します:

```vuejs
<script setup>
import { ref, onMounted } from 'vue'

// リアクティブな状態
const count = ref(0)

// 状態を変更し、更新をトリガーする関数。
function increment() {
  count.value++
}

// ライフサイクルフック
onMounted(() => {
  console.log(`The initial count is ${count.value}.`)
})
</script>

<template>
  <button @click="increment">Count is: {{ count }}</button>
</template>
```

# 実践
下記を基に試してみます。

https://docs.amplify.aws/vue/start/quickstart/

## 1. Create the repository
テンプレートを元にリポジトリを作成します。

1. Quickstartのページで、「Cerate repository from template」をクリックします

    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206276/f83767eb-29b5-fab5-d2fe-330635a6c69d.png)

1. 「Create repository」をクリックします
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206276/921ea387-939e-b01c-4fe9-541ec9c5cde1.png)

1. 自分のGitHubアカウントに、リポジトリができます


## 2. Deploy the starter app
1. AWSにサインインします
1. Quickstartのページで下記のリンクをクリックします 

    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206276/a4ab2c7a-5211-d872-a8da-b76e650e4db3.png)
    ※ URL(https://console.aws.amazon.com/amplify/create/repo-branch)

1. GitHubを選択し、「次へ」をクリックします
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206276/c146b6d7-adfe-3424-5fd1-1a54b8c62ee9.png)

1. リポジトリを選択し、「Install & Authorized」をクリックします
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206276/8411314f-140b-56fe-6de3-f506a1f84e3b.png)

1. リポジトリとブランチを選択し、「次へ」をクリックします
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206276/51c078c4-09fb-fc0d-528d-365f61329354.png)

1. 下記を入力し、「次へ」をクリックします
    - アプリケーションの名前
    - 自身のサイトをパスワードで保護（任意です）
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206276/250fac59-1092-5d06-e084-fa5314f9418a.png)

1. 確認画面で、内容に問題なければ、「保存してデプロイ」をクリックします。（通常、デプロイまで2〜5分程度かかるとのこと）
1. 画面遷移します。

    <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206276/cbcf98af-adef-2005-e85f-f55543ae172f.png" width=300 />
1. 作成されました
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206276/6fd0e01d-2a28-d9de-2c9e-eabd14ae6a91.png)


## 3. View deployed app
1. 「デプロイされたURLにアクセス」をクリックします
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206276/0605f616-e632-2952-d09d-293e14ab981a.png)

1. 先ほどパスワードを設定したので入力します
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206276/4bdf7008-9e1c-0683-8517-7bcf4189e8c5.png)

1. 画面が表示されました。「+new」をクリックします
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206276/81c24749-4609-cd01-051f-788147a47eba.png)

1. ポップアップが表示されるのでToDoを追加します
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206276/23201d43-54d6-1ed6-cbf3-7184ee332f0b.png)

1. Amplifyコンソールのナビゲーションペインで「データマネージャー」を選択します
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206276/931cbf1d-a6df-9618-2ac4-6e097956e356.png)

1. 登録されていました
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206276/815f5742-316f-57a3-b4cf-5044d769567a.png)


### インストールされたディレクトリ構成
    ```
    ├── amplify/ # Folder containing your Amplify backend configuration
    │   ├── auth/ # Definition for your auth backend
    │   │   └── resource.tsx
    │   ├── data/ # Definition for your data backend
    │   │   └── resource.ts
    |   ├── backend.ts
    │   └── tsconfig.json
    ├── src/ # Vue code
    │   ├── assets/ # Styling for your app
    │   ├── components/ # UI code to sync todos in real-time
    │   ├── App.vue # UI layout
    │   └── main.tsx # Entrypoint of the Amplify client library
    ├── package.json
    └── tsconfig.json
    ```


## 4. Set up local environment
フロントエンドに機能を追加するため、ローカル開発環境を設定します。

1. デプロイ画面で「①デプロイされたバックエンドリソース」＞「②amplify_outputs.json」をダウンロードをクリックします
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206276/5fa4e388-e57a-8ef7-10cc-9531fadbb01a.png)


:::note info
amplify_outputs.jsonは、バックエンドのエンドポイント情報、公開可能なAPIキー、認証フロー情報などが含まれています
:::


3.git cloneします
```
git clone git@github.com:XXXXXX/amplify-vue-template.git
```

4.ダウンロードした、amplify_outputs.jsonをリポジトリ直下に移動します

## 5. Implement delete functionality
削除機能を実装します。

1. 修正します
    ```bash
    $ ls -l ./src/components/Todos.vue 
    -rw-r--r--  1 XXXX  XX  1397  8 10 15:01 ./src/components/Todos.vue
    $ cat ./src/components/Todos.vue 
    <script setup lang="ts">
    import '@/assets/main.css';
    import { onMounted, ref } from 'vue';
    import type { Schema } from '../../amplify/data/resource';
    import { generateClient } from 'aws-amplify/data';
    
    const client = generateClient<Schema>();
    
    // create a reactive reference to the array of todos
    const todos = ref<Array<Schema['Todo']["type"]>>([]);
    
    function listTodos() {
      client.models.Todo.observeQuery().subscribe({
        next: ({ items, isSynced }) => {
          todos.value = items
         },
      }); 
    }
    
    function createTodo() {
      client.models.Todo.create({
        content: window.prompt("Todo content")
      }).then(() => {
        // After creating a new todo, update the list of todos
        listTodos();
      });
    }
        
    // fetch todos when the component is mounted
     onMounted(() => {
      listTodos();
    });
    
      
    function deleteTodo(id: string) {
        client.models.Todo.delete({ id })
    }
    
    </script>
    
    <template>
      <main>
        <h1>My todos</h1>
        <button @click="createTodo">+ new</button>
        <ul>
          <li 
            v-for="todo in todos" 
            :key="todo.id"
            @click="deleteTodo(todo.id)"
            >
            {{ todo.content }}
          </li>
        </ul>
        <div>
          🥳 App successfully hosted. Try creating a new todo.
          <br />
          <a href="https://docs.amplify.aws/gen2/start/quickstart/nextjs-pages-router/">
            Review next steps of this tutorial.
          </a>
        </div>
      </main>
    </template>
    $ git diff
    diff --git a/src/components/Todos.vue b/src/components/Todos.vue
    index d1f7fad..adc6c6a 100644
    --- a/src/components/Todos.vue
    +++ b/src/components/Todos.vue
    @@ -31,6 +31,11 @@ function createTodo() {
       listTodos();
     });
     
    +  
    +function deleteTodo(id: string) {
    +    client.models.Todo.delete({ id })
    +}
    +
     </script>
     
     <template>
    @@ -40,7 +45,9 @@ function createTodo() {
         <ul>
           <li 
             v-for="todo in todos" 
    -        :key="todo.id">
    +        :key="todo.id"
    +        @click="deleteTodo(todo.id)"
    +        >
             {{ todo.content }}
           </li>
         </ul>
    $
    ```

1. ローカルサーバを起動します
    ```
    $ npm run dev
    
    > amplify-vue-template@0.0.0 dev
    > vite
    
    
      VITE v5.3.4  ready in 1803 ms
    
      ➜  Local:   http://localhost:5173/
      ➜  Network: use --host to expose
      ➜  press h + enter to show help
    ```
1. localshot:5173 にアクセスします
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206276/98c3f5c8-fb6a-d6e7-8d24-e32cd7232f6c.png)

1. 削除前
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206276/34c34451-925a-e508-efb4-17ac3ba0b360.png)

1. test2をクリックすると削除されました
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206276/5e091f7a-7ca4-eebe-65d3-d8b58878ec7f.png)

## 6. Implement login UI
スターターアプリケーションには、amplify/auth/resource.tsファイルで定義された事前構成済みの認証バックエンドがすでにある。ログインメカニズムをサポートできるように拡張できる。

1. Authenticator UIコンポーネントを使用する
    ```
    $ npm add @aws-amplify/ui-vue
    ```

1. src/App.vueを修正します
    ```
    <script setup lang="ts">
    import Todos from './components/Todos.vue'
    
    import { Authenticator } from "@aws-amplify/ui-vue";
    import "@aws-amplify/ui-vue/styles.css";
    
    </script>
    
    <template>
      <main>
        
        <authenticator>
          <template v-slot="{ signOut }">
            <Todos />
            <button @click="signOut">Sign Out</button>
          </template>
        </authenticator>
    
      </main>
    </template>
    
    ```
1. ローカルで動作確認するため、サーバを起動します
    ```
    $ npm run dev 
    ```
1. localshot:5173 にアクセスします。変更が反映されました
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206276/c6880a79-5275-1743-1a09-8d5dabc2513f.png)

1. コミットとPushします

1. デプロイ中になりました
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206276/cb4f5330-48e1-02b5-0b88-c3161e7baccf.png)

1. デプロイ済になったので、ドメインのリンクをクリックします
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206276/9f186e59-163a-6ce2-916d-7dd929360f29.png)

1. 表示されました
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206276/553227c4-33a1-d5b4-af8e-200c79ede06d.png)

# 考察
今回、Vue.jsをAmplifyで動かしてみました。次回は別の題材で動かしてみようと思います。

# 参考

https://docs.amplify.aws/vue/start/quickstart/

https://qiita.com/zumax/items/b0a5e02c625a98bef3b9

https://qiita.com/zumax/items/ecf31737e26af08f2445