Vue3 + Vuex4 + Vue-Router4 + Typescript + Webpack5 でChrome拡張機能を作成する 

# はじめに

Chrome拡張機能でも、Vue.jsでページを構築したいことがありますよね。
Vue3系のエコシステムも成熟しつつあるので、もろもろVue3系で統一して、Typescriptでコードを書けるようなテンプレートを作ってみました。
どなたかのお役に立てれば幸いと思い、公開してみます。

使っているパッケージは以下の通りです。

Product name | npm package name | npm package version
:--|:--|:--
Vue.js | vue | 3.0.7
Vuex | vuex | 4.0.0
Vue Router | vue-router | 4.0.5
Typescript | typescript | 4.2.3
Pug | pug | 3.0.2
SASS/SCSS | sass-loader | 11.0.1
Webpack | webpack | 5.26.0
ESLint | eslint | 7.23.0
Prettier | prettier | 2.2.1
jQuery (※1) | jquery | 3.6.0

※1: jQueryは、主要ではない部分で1箇所だけ使っているだけなので、「Vue.jsで組むからにはjQueryは排除したい」という方は、取り除けます。

以下の環境でビルドしています。

```bash
$ node -v
v15.11.0
$ npm -v
7.7.4
$ vue -V
@vue/cli 4.5.12
$ $ git --version
git version 2.25.1
```

以下の環境で動作確認しています。

* Chrome 89.0.4389.90 (Windows10 64-bit)

---

# とりあえずソースコードを見せろ

能書きは良いから、とりあえずソースコードを見たい。という方は、以下のgithubにアクセスしてください。

https://github.com/taturou/vue3-typescript-chrome-extension

このリポジトリをcloneしてビルドすれば、Chrome拡張機能として登録して動きをみることができます。

## git clone → ビルド

以下の手順で:

```bash
$ cd (任意のディレクトリ)
$ git clone https://github.com/taturou/vue3-typescript-chrome-extension.git
$ cd vue3-typescript-chrome-extension
$ npm install
$ npm run build
```

vue3-typescript-chrome-extension ディレクトリに `./dist/` という名前のディレクトリが生成されていれば成功しています。
それがChrome拡張機能の本体です。

## Chromeへの拡張機能の登録

以下の手順で:

1. Chromeのオムニバーの一番右にある 「...」ボタン→「その他のツール」→「拡張機能」をクリックして、拡張機能ページを開く
1. 右上の「デベロッパー モード」を ON に設定する
1. [パッケージ化されていない拡張機能を読み込む] ボタンを押して、ディレクトリ選択画面を開く
1. 上で生成した `./dist/` ディレクトリを指定して [フォルダーの選択] ボタンを押す

拡張機能の一覧に「Vue3 + Typescript 1.0.0」が追加されていれば成功しています。

---

# どんなテンプレート？

vue-cli コマンドにより、Chrome拡張機能を作成するプロジェクトを生成するテンプレートです。

## 前提

* Node.js環境でビルドする
* エディタとしてVSCodeを使用する
* Chrome拡張機能のマニフェストバージョンは v2 を採用している（最新の v3 ではない）
* インターネットにつながっていなくても動作する
    * CDNからのライブラリ等の読み込みは行わない。必要なコード・アセット類は全て拡張機能自体に内包する。
* Vue用の外部ライブラリ・コンポーネントは使わない
* jQueryのプラグインは使わない

## できること

* Vue.jsによるページ構築
* Vuexによるデータ管理
    * <font color="red"><編集 2021/04/01 ここから></font>~~データは、リポジトリモジュール経由で記録するアーキテクチャとなっています。~~
        * ~~デフォルトでLocalStorageリポジトリとMockリポジトリが実装されています。~~
            * ~~LocalStorageリポジトリは、ChromeのLocalStorageにデータを永続的に記録します。複数ページでデータを共有できます。~~
            * ~~Mockリポジトリは、動作確認用です。メモリ（jsの変数）にデータを記録します。複数ページでデータを共有することはできません。~~
    * データは、repositoryモジュールからbackgroundプロセスにメッセージを投げ、そこからstorageモジュールが記録するアーキテクチャとなっています。
        * storageモジュールは、LocalStorage、chrome.storage.local、変数にデータを保存できます（選択可能）。<font color="red"><編集 2021/04/01 ここまで></font>
* Vue Routerによるページルーティング
* Webpackによるjsファイル群のバンドル
    * `.html`、`.js`、`.css` は別々に生成します。
* 静的ファイルのソース管理
    * `.html`ファイルや画像・アイコン、`.json`ファイルも `./src/` で管理し、ビルド時に `./dist/` にコピーします。
* `.json` ファイルでのコメント
    * `.json` で `//` と `/* */` コメントが使えます。`manifest.json` で便利です。
* コードを更新時の自動リビルド
    * `webpack --watch` です。
* developmentモードでのsourcemap生成
    * Chromeのdevtoolsでコードをデバッグできます。
* ESLintによるコーディングガイドラインチェック
    * `.js`、`.ts`、`.scss`、`.vue` でチェックします。
    * VSCodeでのみ確認しています。
    * <font color="red"><追記 2021/04/05 ここから></font>`$ npm run lint` により手動チェックもできます。<font color="red"><追記 2021/04/05 ここまで></font>
* PugによるHTML記述
    * `.vue`ファイルの`<template>`をPug(Jade)で書けます。
* SASS/SCSSによるCSS記述
    * `.vue`ファイルの`<script>`をSASS/SCSSで書けます。
    * もちろん、直接`.scss`ファイルを書くこともできます。
* Typescriptによるjs記述
    * `.vue`ファイルの`<script>`をTypescriptで書けます。
    * Vuex storeへのアクセスも自動的に型を生成します。自分でチマチマと型を定義する必要はありません。もちろん、moduleにも対応してます。
        * たとえば、`moduleA` モジュールに `property1` プロパティがあるとき、以下のように値にアクセスしますが、パラメータ `'moduleA/property1'` がエディタにより推測・補完されます。

```ts
// Gets the value of property1 by using the Vuex getter
const value = store.getters['moduleA/property1']
// Sets a value of property1 synchronously by using the Vuex mutation.
store.commit('moduleA/property1', value)
// Sets a value of property1 asynchronously by using the Vuex action.
store.dispatch('moduleA/property1', value)
```

<font color="red"><追記 2021/04/01 ここから></font>

* Chrome拡張機能の Manifest バージョンとして v2 と v3 を選択できる。

<font color="red"><追記 2021/04/01 ここまで></font>

<font color="red"><追記 2021/04/05 ここから></font>

* Prettierによるコード整形
    * `.js`、`.ts`、`.vue`、`.pug`、`.css`、`.scss`、`.sass`、`.json`、`.html`、`.md` を整形します。
    * VSCodeで、ファイル保存時に整形します。
    * `$ npm run format` により全ファイルを整形することもできます。

<font color="red"><追記 2021/04/05 ここまで></font>

## できないこと

* 拡張機能のリリース
    * 野良拡張機能としてビルドすることはできますが、Chromeウェブストアに登録する面倒はみません。
* 拡張機能自体のホットリロード
    * コード更新により自動リビルドしますが、ホットリロードはしません。
      拡張機能自体の更新は手動で行う必要があります。
      これは完全に個人的な要求ですが、リロードのタイミングは自分で管理したい、コードを変えるそばからリロードされると不便なので（一度対応してみましたが煩わしくて消した）。
* ~~Prettierによるコードフォーマット修正~~
    * ~~これは、単に実装していないだけなので、そのうち実装すると思います。~~
    * ~~個人的には linter で事足りてます。~~
    * <font color="red"><追記 2021/04/05 ここから></font>対応しました。Prettierに掛けてみたら、ほぼ全てのコードが修正されました。やっぱESLintと人の目だけじゃダメですね:sweat:<font color="red"><追記 2021/04/05 ここまで></font>
* 見栄えの良いページ
    * これはテンプレートなので、最低限の機能しか実装していません。
      とくにPopupページやオプションページなどの見栄えは、ほぼ素のHTMLのスタイルのままです。
      アニメーションもつけてません。
* テスト
    * 単にサボってるだけなので、そのうち実装します。

## 実装してある機能

* Popupページ
* オプションページ
* contentスクリプト - Counter
* backgroundスクリプト
* Vuex store / リポジトリ

### Popupページ

![popup-1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/58711/c95b26fa-d106-3250-4620-5fc6d5c88985.png)

拡張機能のアイコン (1) を押すと、Popupページ (2) が開きます。

以下の4つの機能から構成されています。

1. タイトル表示
1. オプションページを開く
1. カウンター
1. メモ

それぞれの機能は、オプションページの説明を参照してください。
各データは、Vuexで管理され、backgroundスクリプトを介してLocalStorageに保存されます。

#### ソースツリー

ソースツリーは、以下のような構成となっています。

```text
.
`-- src/
    `-- popup/
        |-- components/        -- Vue.jsのコンポーネントを格納する
        |   |-- Counter.vue    -- カウンターの実装
        |   |-- Memos.vue      -- メモの実装
        |   `-- Options.vue    -- オプションページを開くボタンの実装
        |-- App.vue            -- Popupページのメイン画面
        |-- index.html         -- Popupページのメイン画面
        `-- index.ts           -- index.html に App.vue をマウントする処理
```

### オプションページ

Popupページに連動して、保存してあるデータを表示します。
以下の3ページが実装されており、Vue Routerで表示を切り替えています:

* カウンター
* メモ一覧
* メモ編集

#### カウンター

![options-counter-1.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/58711/201cfb3c-5ba4-67be-45c0-639167ecba39.gif)

Popupページでカウンターの値を `+`/`-` すると、リアルタイムに反映されます。

#### メモ一覧

![options-memos-1.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/58711/e94d6a91-6a57-8e69-74f5-afb4de7e61a1.gif)

Popupページでメモを `[Add]` すると、リアルタイムに反映されます。

#### メモ編集

メモ一覧画面で `[Edit]` ボタンを押すと、メモの編集画面が開きます。

![options-memo-1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/58711/bceef63d-303b-9cb6-234a-f57ef177b530.png)

データは、Vuexで管理され、backgroundスクリプトを介してLocalStorageに保存されます。

#### ソースツリー

ソースツリーは、以下のような構成となっています。

```text
.
`-- src/
    `-- options/
        |-- components/        -- Vue.jsのコンポーネントを格納する
        |   `-- Header.vue     -- オプションページの共通ヘッダ --(2)
        |-- Pages/             -- Vue.jsのコンポーネントを格納する --(3)
        |   |-- Counter.vue    -- カウンターページの実装
        |   |-- Memos.vue      -- メモページの実装
        |   `-- Memo.vue       -- メモ編集ページの実装
        |-- App.vue            -- オプションページのメイン画面 --(1)
        |-- index.html         -- オプションページのメイン画面
        |-- index.ts           -- index.html に App.vue をマウントする処理
        `-- router.ts          -- Vue Router のルーティングテーブル
```

`App.vue` (1)、`components/Header.vue` (2)、`Pages/` (3) は、それぞれ以下の描画を担当しています。

![options-memos-6.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/58711/62eb1971-0506-f339-b53c-977b5fa4b3b4.png)


### contentスクリプト - Counter

![content-counter-1.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/58711/3feaa855-ae13-a69e-a399-fb5a5813622c.gif)

任意の（全ての）Webページに、カウンターの値を表示する小窓を生成します。
カウンターの値は、Popupページとリアルタイムに連動します。

#### ソースツリー

ソースツリーは、以下のような構成となっています。

```text
.
`-- src/
    `-- contents/
        `-- counter/
            |-- components/       -- Vue.jsのコンポーネントを格納する
            |   `-- Counter.vue   -- カウンター窓の実装
            |-- css/              -- .vue ファイル以外が使用するスタイルシート
            |   `-- index.scss    -- index.ts が追加する <div> 要素のスタイル
            `-- index.ts          -- <body> 要素への <div> の追加と、それを Counter.vue にマウントする処理
```

### backgroundスクリプト

Popupページ、オプションページ、contentスクリプトからのメッセージを受信するスクリプトです。

<font color="red"><編集 2021/04/01 ここから></font>
~~Vuexで管理するデータは、永続化のためにLocalStorageに保存しています。
しかし、contentスクリプトからはLocalStorageにアクセスすることはできないため、そこからbackgroundスクリプトにメッセージを投げ、backgroundスクリプトが代わりにLocalStorageにアクセスするアーキテクチャとなっています。~~
Vuexで管理するデータは、永続化のために`LocalStorage`か`chrome.storage.local`に保存しています。
しかし、contentスクリプトからはそれらにアクセスすることはできないため、そこからbackgroundスクリプトにメッセージを投げ、backgroundスクリプトが代わりにデータを保存するアーキテクチャとなっています。
<font color="red"><編集 2021/04/01 ここまで></font>

メッセージは以下のような入れ子構造になっており、各モジュールが自分の担当分を処理します。
これらはTypescriptの型として定義されており、VSCodeによる補完が効きます。

```ts
// 例1) Counterリポジトリの 'setCount' メソッド用のメッセージ型
type message = {
  type: 'repository',      // message/index.ts はこれを見てrepositoryモジュールに処理を渡す
  repository: {
    type: 'counter',       // message/repository/index.ts はこれを見てCounterモジュールに処理を渡す
    counter: {
      type: 'setCount',    // message/repository/Counter/index.ts はこれを見て自分の処理を実行する
      params: {
        count: number
      },
      response: number
    }
  }
}

// 例2) Memosリポジトリの 'add' メソッド用のメッセージ型
type message = {
  type: 'repository',      // message/index.ts はこれを見てrepositoryモジュールに処理を渡す
  repository: {
    type: 'memos',         // message/repository/index.ts はこれを見てMemosモジュールに処理を渡す
    memos: {
      type: 'add',         // message/repository/Memos/index.ts はこれを見て自分の処理を実行する
      params: {
        content: string
      },
      response: [StateType, number]
    }
  }
}
```

なお、型は `src/background/message/_types.ts` に定義されたジェネリック型にインタフェースを渡すことにより自動的に定義されます。
たとえば、Counterリポジトリに以下のインタフェースが定義されているとき:

```ts
interface CounterRepositoryType {
  setCount (payload: { count: number }): Promise<number>
}
```

以下の2行でbackgroundスクリプトへ渡すメッセージの型（上の例1）を定義することができます:

```ts
type counterMessageDataType = messageDataType<CounterRepositoryType>
type counterMessageType = messageType<'counter', counterMessageDataType>
```

#### ソースツリー

ソースツリーは、以下のような構成となっています。

```text
.
`-- src/
    `-- background/
        |-- message/               -- Popupページ、オプションページ、contentsスクリプトからのメッセージを受信する処理
        |   |-- repository/        -- repositoryからのメッセージを受信する処理
        |   |   |-- Counter/
        |   |   |   |-- index.ts   -- Counterリポジトリのデータを保存・読み込みする処理
        |   |   |   `-- types.ts   -- Counter用のメッセージ型定義
        |   |   |-- Memos/
        |   |   |   |-- index.ts   -- Memosリポジトリのデータを保存・読み込みする処理
        |   |   |   `-- types.ts   -- Mems用のメッセージ型定義
        |   |   |-- index.ts       -- メッセージをCounterとMemosに振り分ける処理
        |   |   `-- types.ts       -- LocalStorage用のメッセージタイプ定義
        |   |-- _types.ts          -- メッセージ型を定義するためのジェネリック型定義
        |   |-- index.ts           -- メッセージをLocalStorageに振り分ける処理
        |   `-- types.ts           -- backgroundスクリプト用のメッセージ型定義
        `-- index.ts
```

### Vuex store / リポジトリ

Popupページ、オプションページ、contentスクリプトは、Vuexによりデータを管理しています。
そして、データを保存する処理はリポジトリモジュールが担っています。
Vuex storeとデータ保存を別モジュールに分けることにより、データの保存先、保存方法が変わったときにstoreを変更しなくてもよい、リポジトリを差し替えることによりstoreのテストが容易になる、などの利点があります。

storeからリポジトリを呼び出すのは、Actoinsのみです。リポジトリの操作は非同期で行われる可能性があるためです。
storeは、生成時の状態ではデータを持っていません（空っぽ）。
そのため、Vueコンポーネントをマウントするとき、`store.despatch('xxxModule/fetch')` を呼び出して、リポジトリから最新データを取得するようにしています。
`fetch` アクションを実行することで、storeにデータがロードされ、画面がリアクティブに更新されます。

Vue3 + Vuex4 環境では、storeは `setup()` の中で `useStore()` により取得することになっています。
このとき、「`useStore(key)` を使うとTypescriptで型補完が効くよ。`key` は `InjectionKey<Store<State>>` だよ。」と [公式ページ](https://next.vuex.vuejs.org/guide/typescript-support.html#typing-usestore-composition-function) には書いてあるのですが…。
モジュールを使うときにどうすれば良いか書いてない！
なので、公式の方法を使うのは諦めて自分で型を自動生成しています。
たとえば、store.counterに以下のインタフェースが定義されているとき:

```ts
export interface StateType {
  count: number
}

export interface GettersType {
  count (state: StateType): number
}

export interface MutationsType {
  count (state: StateType, payload: { count: number }): void
}

export interface ActionsType {
  fetch (injectee: ActionContext<RootState, StateType>): Promise<void>,
  increment (injectee: ActionContext<RootState, StateType>): Promise<void>,
  decrement (injectee: ActionContext<RootState, StateType>): Promise<void>
}
```

以下の1行でStore型を定義することができます:

```ts
type CounterStoreModuleType = StoreModuleType<'counter', StateType, GettersType, MutationsType, ActionsType>
```

使うときは、以下のようにキャストすることにより、`getters`、`commit()`、`dispatch()` でパラメータをチェック・補完できるようになります。

```ts
const store = useStore() as CounterStoreModuleType

console.log(store.getters['counter/count']) // It will display '0'.
store.dispatch('counter/fetch')
console.log(store.getters['counter/count']) // It will display the current value.
store.dispatch('counter/increment')
console.log(store.getters['counter/count']) // It will display the incremented value.
```

#### ソースツリー

ソースツリーは、以下のような構成となっています。

```text
.
`-- src/
    `-- lib/
        |-- repository/                          -- Vuex storeのデータを保存するためのリポジトリ
        |   |-- Counter/                         -- store.counterモジュール用のリポジトリ
        |   |   |-- index.ts                     -- backgroundスクリプトにデータ保存メッセージを投げる処理
        |   |   `-- types.ts                     -- リポジトリのインタフェース定義
        |   |-- Memos/                           -- store.memosモジュール用のリポジトリ
        |   |   |-- index.ts                     -- backgroundスクリプトにデータ保存メッセージを投げる処理
        |   |   `-- types.ts
        |   `-- index.ts                         -- リポジトリのファクトリー
        `-- store/                               -- Vuex store
            |-- Counter/                         -- store.counterモジュール
            |   |-- actions.ts                   -- actions
            |   |-- getters.ts                   -- getters
            |   |-- index.ts                     -- モジュール定義
            |   |-- mutations.ts                 -- mutations
            |   `-- types.ts                     -- actions/getters/mutationsのインタフェース定義
            |-- Memos/                           -- store.memosモジュール
            |   |-- actions.ts                      (同上)
            |   |-- getters.ts
            |   |-- index.ts
            |   |-- mutations.ts
            |   `-- types.ts
            |-- _types.ts                        -- Store型を定義するためのジェネリック型定義
            |-- index.ts                         -- storeの本体、カスタム useStore()、useStore()用のStore型定義
            `-- types.ts                         -- Rootモジュール型定義
```

---

# テンプレートから自分のプロジェクトを作る

もし、気に入っていただければ、このテンプレートから自身のChrome拡張機能を作成していただければ。

テンプレートは、vue-cli テンプレートとなっています。
以下の手順でプロジェクトを作成してください。

```bash
$ npm install -g @vue/cli @vue/cli-init
$ vue init taturou/vue3-typescript-chrome-extension#vue-template (プロジェクトディレクトリ名)
※2 オプションを選ぶ
$ cd (プロジェクトディレクトリ名)
$ npm install
$ npm run build
```

※2: プロジェクト作成時に、以下のオプションを設定可能です。

* Product Name (not include space characters)
    * プロダクト名です。
    * 半角英数字・スペース無しで設定してください。
    * デフォルト値は、(プロジェクトディレクトリ名) です。
* Chrome extension Name
    * Chrome拡張機能の名前です。
    * Chromeに登録したときに表示されます。
* Description
    * Chrome拡張機能の説明です。
    * Chromeに登録したときに表示されます。
* Author
    * 作成者の情報です。
    * 例) `taturou <taturou@gmail.com>`
* Use an OSS License?
    * Chrome拡張機能にOSSライセンスを適応するか否かを `Y/N` で選択できます。
    * `Y` にしたときは、以下の2項目も設定可能です。
* Pick a license: (Use arrow keys)
    * 以下から選択可能です。LICENSEファイルが生成されます。
        * Apache License 2.0
        * BSD 2-Clause Simplified License
        * BSD 3-Clause New or Revised License
        * Eclipse Public License 2.0
        * GNU General Public License v3.0
        * GNU Lesser General Public License v2.1
        * MIT License
        * Mozilla Public License 2.0
* Copyright year:
    * Copyrightに記載する作成年を設定します。

<font color="red"><追記 2021/04/01 ここから></font>

* Chrome extension's manifest versions:
    * Chrome拡張機能のマニフェストバージョンを、v2 か v3 か選択できます。

<font color="red"><追記 2021/04/01 ここまで></font>

## npm scripts

ビルド用に以下のコマンドを使用可能です。

* `$ npm run build`
    * リリース用にビルドします。
    * `./dist/` にChrome拡張機能が生成されます。
* `$ npm run debug`
    * デバッグ用にビルドします。
    * sourcemap付きのコードが生成されます。
    * 生成場所は `$ npm run build` と同様です。
* `$ npm run watch`
    * コード変更時に自動的にリビルドします。
    * それ以外は `$ npm run debug` と同様です。
* `$ npm run zip`
    * `./dist/` をzip圧縮します。
    * `./dist_zip/` に圧縮ファイルが生成されます。
    * ファイル名は `(プロダクト名)-(プロダクトバージョン).zip` です。
        * プロダクト名: `package.json`の`name`プロパティの値
        * プロダクトバージョン: `package.json`の`version`プロパティの値
    * `./dist/` が存在しない場合（ビルドしていない場合）は、エラーで終了します。
* `$ npm run clean`
    * `./dist/` を削除します。

<font color="red"><追記 2021/04/05 ここから></font>

* `$ npm run lint`
    * ESLintによりコードをチェックします。
* `$ npm run format`
    * Prettierによりコードを整形します。

<font color="red"><追記 2021/04/05 ここまで></font>

---

# 課題

## 1. Vue Router使ってるページはリロード（F5キー、Ctrl+R、Cmd+R）できない

オプションページは、Vue Routerでページをルーティングしています。
そのとき、たとえばカウンターは `/options/counter` に、メモ一覧は `/options/memos` に、URLを設定しています。
しかし、オプションページはHTTPサーバが表示しているわけではなく、静的な `/options/index.html` を表示しているだけです。
そのため、jsで画面を切り替えている限りは問題ないのですが、ブラウザの機能でリロードするとページが見つからず（`/option/counter` などというファイルは存在しない）、エラー ERR_FILE_NOT_FOUND が発生します。

裏にHTTPサーバが居れば、不明なパスは index.html にリダイレクトする、とかできるのでしょうが…。
ファイルサーバ上の静的ページで Vue Router を適切に動かすためには、どうすればよいのでしょうか？

## 2. Chrome拡張機能マニフェストバージョン v3 ではない

<font color="red"><編集 2021/04/01 ここから></font>
~~今回のテンプレート作成は「とりあえず全部最新のやつで構成する」のが目的だったので、Chrome拡張機能のマニフェストも最新の v3 (MV3) で行く予定でした。
はじめはそれで作成していたのですが、以下の課題にぶつかりました。
* MV3はbackgroundスクリプトが廃止され、代わりにService workerを使うようになった。
* しかし、Service workerはLocalStorageにアクセスできない。
* なので、データ永続化機構のハブとしてのbackgroundスクリプトの代わりに、Service workerを使うことはできない。
これ、多分 自分の理解が間違っていると思うんです。
backgroundスクリプトでLocalStorageにアクセスするアーキテクチャってChrome拡張機能のお作法なので、Service workerに置き換えたからそれができなくなる、ってのは無いはず。
だけど、LocalStorageにアクセスしようとしたらエラーが発生する…。
これ、どうすれば良いのでしょうか？~~

マニフェストバージョン v3 (MV3) に対応できました。
MV3ではbackgroundスクリプトが廃止され、代わりにService workerを使うようになりました。
Service workerでは、`LocalStorage`にアクセスすることはできないのですが、代わりに`chrome.storage.local`に保存すれば良いことがわかりました。
ただ、`chrome.storage.local`の場合、Chrome devtoolsでデータを簡単に見ることができないんですよね…。
一応 "[Storage Area Explorer](https://chrome.google.com/webstore/detail/storage-area-explorer/ocfjjjjhkpapocigimmppepjgfdecjkb)" 拡張機能をインストールするとみることはできるのですが、Service workerのプロセスでは見られないし（なぜ？）、ちょっと不便ですね。
なので、このテンプレートとしては、MV2とMV3を選択できるようにしました。
<font color="red"><編集 2021/04/01 ここまで></font>

## 3. Vuexでモジュールを使うときの `useStore()` の型定義の公式のやり方がわからない

「Vuex store / リポジトリ」で書いたとおり、`useStore()` の型は自前の `StoreModuleType<>` で生成してキャストして使ってます。
でもこれ、公式のやり方があるはず。
モジュール使わないときのやり方は公式ページに書いてあるので。
ただ、Vuexのコードを読んでないのと（おい）、とりあえず力技でなんとかなったので、今回はこの方法に落ち着いています。
これも、本来はどうすれば良いのでしょうか？

## 4. "Vue.js devtools" Chrome拡張が動作しない

Vueで構築した画面をデバッグするときは "[Vue.js devtools](https://chrome.google.com/webstore/detail/vuejs-devtools/nhdogjmejiglipccpnnnanhbledajbpd/related?hl=ja)" を使うのが便利です。
が、このテンプレートで作成したページは、 Vue.js devtools が動きません（Vueページだと認識しない）。
[Vue3系用のBeta版](https://chrome.google.com/webstore/detail/vuejs-devtools/ljjemllljcmogpfapbkkighbhhppjdbg) を使ってもダメです。
これ、マジでどうすれば良いのでしょうか :sob:

---

# 与太話

最後まで読んでいただいた方、ありがとうございました。
あまりコアな部分を書いてないのに、かなり長い記事になってしまい、自分の文章力の無さを痛感している感じです:cry:

私は、普段は組み込みシステムの開発をしているので、Web関連の仕事は全くやったことがないです。
しかし、仕事で役立つChrome拡張機能を作りたく、いろいろ調べて、2週間でようやくある程度形にすることができました。
今回始めて Vue.js とか Webpack とか Typescript とか触ってみたのですが、すごいですね。超便利。
うちの会社の開発環境は C/C++ オンリーで、パッケージ管理どころか git によるバージョン管理すら使っていないので、こういう世界は本当に憧れます。
しかも、ネットで調べると情報がある！しかも日本語でも新しい情報がある！！Qiitaがある！！！
いやー、ホントに羨ましい限りです。

で、いろいろベストプラクティスとかも調べたのですが、「このアーキテクチャはいまいちだよ」「これは今の時代はこっちのほうが良いよ」「そもそも、Vue3になってもReactのほうが良いよ」みたいなのがあると、指摘していただけると、ものすごくありがたいです。

特に、Typescriptの型定義の置き場はどこが適切か、型を定義するためのジェネリック型はどこに置けばよいか、いまいち分からずに今のような形になってます。今のファイル構成は間違ってると思うので、引き続き調べようと思ってます。

あと、課題3 で「これじゃダメだと思う」とは書きましたが、Store型の自動定義は結構頑張っていろいろ調べたので、どうやって型を生成しているかを別記事にでもしてみようかと思ってます。これを解決しているのは日本語でも英語でも見つからなかったので（公式の方法があるから、だとは思いますが…）。

<font color="red"><追記 2021/03/30 ここから></font>
記事にしました。

https://qiita.com/taturou/items/e26f079fd78b4e7ead4e

<font color="red"><追記 2021/03/30 ここまで></font>

最後になりますが、Web業界って良いですね。良いと思いました。
転職したいです。
「今回、ちょっとばかり片鱗を噛ったやつが何言ってんだ」って感じですが。
フロントエンド開発で、無理なスケジュールで疲弊せずに開発できる会社があったら、教えていただけると幸いです。