Vite + Vue3 + Pinia + TypeScriptで拡張機能を作成した

# 環境
- pinia: 2.0.16
- vue: 3.2.37
- typescript: ~4.7.4
- vite: 3.0.1
- eslint: 8.5.0
- prettier: 2.5.1


# はじめに
タイトルにある通りVue3+TypeScriptでchrome拡張を作成しました。
VueとTypeScriptの相性はあまり良くないイメージがあるのですが、Vue3では割と気持ちよく使えるなという印象です。
参考までに実際に作成したものを貼っておきます。

https://chrome.google.com/webstore/detail/create-groups-in-netflix/bicnagkkmhehedgogcopdadkakldblfk

# Viteについて
「ヴィート」と読むらしいです。ずっと「ばいと」って呼んでました...人に言う前に気づけて良かった...
ちなみに語源はフランス語の「素早い」という意味の単語でして、その名の通り非常に高速なビルドツールです。
概要について以下公式まま引用:
> Viteは、現代の Web プロジェクトのために、より速く無駄のない開発体験を提供することを目的としたビルドツールです。2 つの主要な部分で構成されています:
> - 非常に高速な Hot Module Replacement (HMR) など、ネイティブ ES モジュールを利用した豊富な機能拡張を提供する開発サーバ。
> - Rollup でコードをバンドルするビルドコマンド。プロダクション用に高度に最適化された静的アセットを出力するように事前に設定されています。

詳しくは公式ドキュメント見るのが一番だと思います。詳しく説明する自信がないわけではないです。断じて。

https://ja.vitejs.dev/

# Vue3について
20年にリリースされ、Vue2から結構書き方が変わりました。
ここではVue3についての記事ではないので詳しい説明は省きますが、一番大きな変更点は`setup`だと思います。
ざっくり説明すると、setup内に全ての処理を書いていくイメージです。これがTypeScriptと相性が良いです。
```vue:公式例(Composition API)：
<script setup lang="ts">
// 変数
const msg = 'Hello!'

// 関数
function log() {
  console.log(msg)
}
</script>

<template>
  <div @click="log">{{ msg }}</div>
</template>
```
こちらも以下公式ドキュメント参照下さい。
個人的に、Vueの公式ドキュメントってかなり分かりやすいと思っています。

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

# Piniaについて🍍
~~Vueにおける状態管理と言えばVuexが有名だと思います。
PiniaはVuex5の内容が盛り込まれた状態管理ライブラリでVuex5で統合されると言われています。~~
[公式](https://github.com/vuejs/vuex)によると、Vueの公式の状態管理ライブラリはPiniaに変更されたようです。
> Vueの公式の状態管理ライブラリがPiniaに変更されました。PiniaはVuex 5のRFCに記載されているVuex 5とほぼ同じ、または強化されたAPIを持っています。PiniaはVuex 5の名前を変えたものだと思えばよいでしょう。PiniaはVue 2.xでも動作します。

以下がVuex4以前との大きな違いになります。
- mutations が無くなった。
- TypeScriptのフルサポート
- モジュールのネストを禁止
- ネームスペースの廃止

簡単な使い方については以下のとおりです。具体的な使用方法は今回は省きます。
```typescript:pinia
import { defineStore } from 'pinia'

export const useCounterStore = defineStore('counter', {
  state: () => {
    return { count: 0 }
  },
  // could also be defined as
  // state: () => ({ count: 0 })
  actions: {
    increment() {
      this.count++
    },
  },
})
```
```vue:vue
<script setup lang="ts">
import { useCounterStore } from '@/stores/counter'

const counter = useCounterStore()
counter.count++
counter.$patch({ count: counter.count + 1 })
counter.increment()
</script>
```

こちらも公式サイトがありますので確認してみてください。

https://pinia.vuejs.org/introduction.html


# 手順

## Viteで開発テンプレート作成
早速<b>Vite</b>さんを使います。以下のコマンドで高速にテンプレートを作成できます。
実行後は画面表示に従って作成していけばOKです。
```shell
# npm 6.x
$ npm create vite@latest my-vue-app --template vue-ts

# npm 7+ は追加で 2 つのダッシュが必要:
$ npm create vite@latest my-vue-app -- --template vue-ts

# yarn
$ yarn create vite my-vue-app --template vue-ts
```
一旦テンプレートの作成ができたら、`npm install` `npm run dev`で動作確認しておきましょう。
ここでViteの素早さを実感できると思います。ストレスフリーです。

## Piniaのインストール🍍

まずはプロジェクトにpiniaを入れます。

```shell
# npmの場合
$ npm install pinia
# yarnの場合
$ yarn add pinia
```

続いて`main.ts`でPiniaインスタンスを生成します。

```ts:src/main.ts
import App from "./App.vue";
import { createApp } from "vue";
import { createPinia } from "pinia";

const pinia = createPinia();
const app = createApp(App);
app.use(pinia);
```

#### Did you forget to install pinia?

createPiniaをしていないとこのエラーを吐きます。

しかし、間違いなく書いているのにいつかこのエラーを見ることになるかもしれません。
原因としてはいくつかありますが、私が陥った一例を共有します。

#### TypeScriptファイルで直接Storeを呼び出していた
どういうことかというと下記をみてください。
```typescript:src/ts/sample.ts
import { useCounterStore } from "@/stores/counter";

//　エラーの原因
const store = useCounterStore();

const sampleFunc = ():string => {
    ...適当な処理
    return "hogehoge";
}
```
コンポーネントのsetupの外から`useCounterStore`を呼んでいるのが原因でした。
Piniaがセットアップされる前に呼び出されてしまっているということですね。
同じエラーに遭遇したらこの線も考えてみてください。

https://github.com/vuejs/pinia/discussions/971

## ストアをリアクティブに変更・参照する
Piniaはちゃんとコンポーネント内で使用してあげれば仲良くなれます。が、リアクティブ性を保つには少し注意が必要になります。
以下の例を見てください。
```typescript:App.vue
import { toRefs } from "vue";
import { useCounterStore } from "@/stores/counter";

const store = useCounterStore();
// リアクティブ性が失われている
const { count, increment } = toRefs(store);
```
上記の様に分割代入してしまうとリアクティブ性が失われてしまいます。なのでリアクティブ性を保持したまま分割代入をしたい場合は、`storeToRefs`を使用します。
```typescript:App.vue
import { storeToRefs } from "pinia";

const store = useCounterStore();
// リアクティブ性は保持される
const { count, increment } = storeToRefs(store);
```

## なんやかんや書いていく

ここまで準備できれば、後は実現したい機能に向けてVue＆TypeScriptで書いていくだけです。
VueとTypeScriptの詳しい書き方等はここでは書きません、というか書く自信がありませんので、とても参考になる記事をいくつか貼っておきます。

https://reffect.co.jp/vue/vue3-typescript

https://qiita.com/irico/items/33744e15a4e0ca52d6bc

https://qiita.com/uhyo/items/e2fdef2d3236b9bfe74a

後はやっぱり公式ドキュメント見るのが一番だとは思います。

https://v3.ja.vuejs.org/guide/typescript-support.html

## Viteでビルド

実現したい機能が完成したら`npm run build`します。
ビルドの設定などは`vite.config.ts`で出来ます。参考までに私のを張っておきます。
```typescript:vite.config.ts
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'

// https://vitejs.dev/config/
import { fileURLToPath, URL } from "node:url";

import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";

// import vueJsx from "@vitejs/plugin-vue-jsx";

import path from "path";

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [vue()],
  server: {
    host: true,
    port: 3000,
  },
  define: {
    "process.env": {},
  },
  build: {
    rollupOptions: {
      output: {
        entryFileNames: `[name].js`,
        chunkFileNames: `[name].js`,
        assetFileNames: `[name].[ext]`,
      },
    },
    outDir: "./extension/js",
    lib: {
      entry: path.resolve(__dirname, "src/main.ts"),
      name: "main",
      formats: ["umd"],
    },
    cssCodeSplit: false,
  },
  resolve: {
    alias: {
      "@": fileURLToPath(new URL("./src", import.meta.url)),
    },
  },
});
```

## manifest.jsonの作成
拡張機能開発において必須のファイルになります。名の通り拡張機能のマニフェストをJson形式で記載します。
manifest.jsonにはV2とV3がありますが、これから作成するのであればV3で必ず書きましょう。
またすでに公開しているものでもV2で書かれているいるものは`2023年6月`には動かなくなるみたいです。未対応の方はお早めに！

以下サンプルを記載しておきます。
```json:manifest.json
{
  "manifest_version": 3,
  "name": "Vue3 - Chrome拡張サンプル",
  "version": "1.0",
  "description": "Vue3でChrome拡張を作成したサンプルです。",
  "permissions": ["activeTab", "scripting", "tabs", "storage"],
  "host_permissions": [
    "https://sample/*"
  ],
  "background": {
    "service_worker": "./background.js",
    "type": "module"
  },
  "content_scripts": [
    {
      "matches": [
        "https://sample.com/*",
      ],
      "css": ["./js/style.css"]
    }
  ],
  "icons": {
    "16": "./icon/icon16.png",
    "48": "./icon/icon48.png",
    "128": "./icon/icon128.png"
  }
}
```
作成出来たら、こんな感じで先ほどのビルド先にコピーして下さい。
```
├── dist
│   ├── icon
│   └── js
|   └── manifest.json
```
ここまでできたら、`7Zip`等のツールを使って、ビルドファイルをZipに圧縮しておいてください。ストアへの公開で必要になります。

manifest.jsonの詳しい書き方は以下。

https://qiita.com/shiro1212/items/12f0a767494a7b2ab0b3



## 拡張機能を読み込む

ここまできたらあと少しです。以下ステップを踏めばとりあえず拡張機能を動作することができます。

1. 自分の拡張機能ページを開きデベロッパーモードをオンにする。
1. 「パッケージ化されていない拡張機能を読み込む」をクリックする。
1. 先ほどビルドしたファイルを選択する（Vueデフォルトだと`dist`ファイルになります）

拡張機能が読み込めなかった場合は、`manifest.json`の内容を見直し修正後、再度同じ操作を実施してください。

## ストアへ公開するぞ！

後はストアに公開するだけです。
ストアに公開するには、「デベロッパー ダッシュボード」なるものに登録が必要になります。

https://developers-jp.googleblog.com/2020/04/chrome.html

一応登録料がかかるのですが、１度きり約５００円のお支払いでずっと公開し続けることができます。かなり良心的です。

https://developer.chrome.com/docs/webstore/register/

登録が完了したら、ダッシュボードへ移動し`+新しいアイテム`⇒Zipに圧縮済みのビルドファイルを選択。
![qiita.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/838672/74025ea0-2a35-4cf0-6254-00fbb5f61181.png)

後は、公開情報やらプライバシーへの取り組みやらの必須項目に入力をしていき、`下書き保存`⇒`審査のため送信`を選択すればOKです。

しっかり必要事項が入力できていれば、早ければ１日以内には公開されています。やったぜ。

## できたもの

ちなみにこんな感じで私自身が作成したもの。

https://chrome.google.com/webstore/detail/create-groups-in-netflix/bicnagkkmhehedgogcopdadkakldblfk

Netflixの拡張機能で、映画やドラマを自由にカテゴライズし保存できるといったものです。
もしこの記事が少しでもお役に立てたのならば、是非使ってみてください。そして贅沢をいうとフィードバックもお待ちしております。

## VSCodeの設定

ついでにVscodeの拡張機能まわりで私が若干はまったことを共有させて頂きます。
これまでVue2で開発されてきた方は`Vetur`を使用されていたと思いますが、Vue3では`Volar`なるものを使用します。

https://marketplace.visualstudio.com/items?itemName=vue.volar

インストールしていない方は忘れずしておきましょう。


...あれ..？まだエラーがでてるぞ？？となった方は、私と同じミスしている可能性があります。
#### `Vetur`と`Volar`が両方有効になっている
競合して上手く機能が働いてくれないみたいです。`Vetur`はDisable(Workspace)しておきましょう。

# 最後に

ここまで読んでくださり誠にありがとうございます。
Qiita初投稿だったので、拙いところが散見されると思います。申し訳ございません。
間違っているところや仕様が変更されているものがございましたら、コメント頂けると幸いです。

では最後に、しつこいですが...これ使ってください！！！

https://chrome.google.com/webstore/detail/create-groups-in-netflix/bicnagkkmhehedgogcopdadkakldblfk


# 他参考サイト
- [Vue - 公式](https://v3.ja.vuejs.org/)
- [Vite - 公式](https://ja.vitejs.dev/)
- [Pinia - 公式](https://pinia.vuejs.org/)
- [Vite + Vue3 + TypeScriptの開発体験 - Zenn](https://zenn.dev/hctaw_srp/articles/5f2e2a6470d2ec)
- [Vue.js を使ってメインページ側で動作する Chrome 拡張機能を開発する方法 - to-me-rrow](https://r17n.page/2019/10/09/chrome-extension-with-vue/)
- [Pinia + Vue 3 + TypeScript による次世代のグローバルステート管理 - Zenn](https://zenn.dev/sa2knight/articles/74f066b7be24c3)