Vue.jsとTypeScriptでGoogleChrome用拡張を開発する

Vue.jsのようなJSフレームワークを使い慣れていればGoogleChrome拡張を作るのも同じような感じで作っていけるので、思いの外ハードルは低いです。
特に拡張なんかは便利なものも多い反面、自分の用途にそぐわなかったりもするので、そういった時自分専用の拡張を作ってしまうのも1つの手ですね。

今回はそんなGoogleChrome拡張をVue.jsとTypeScriptを使って作るための環境をセットアップします。

GoogleChrome拡張is何?

今さら説明するまでもないと思いますが、一応軽く触れておきます。GoogleChrome(ブラウザ)の標準の機能では足りない機能を拡張して、好きにカスタマイズできます。
特定のWebサービスに限定してより使いやすくするものから、ブラウザ自体の機能強化するものもあります。

内部的にはHTMLとJavaScriptというWebサイト的に標準の技術が使われています。つまりHTMLとJSが書ければ誰でも作れるため、想像しているよりも簡単に作れます。

JSが動いているため、表示側ではReactやVue.jsのようなJS系のフレームワークを使うこともできます(もちろんそれすらも必要ないような簡易なものも多いです)。

またVivaldiのようにChromiumベースのブラウザでも基本的には動作します。

Vue.jsを使えるようにする

ではまずVue.jsを使えるようにします。

npm install -g @vue/cli @vue/cli-init

これだけですね。vue-clieとinitは現在は別々になったので、2つのインストールが必要です。特に問題なければこれらはGlobalにインストールしておきます。

スターターテンプレートキットを使う

有志が作られたボイラープレートがあるのでそれを使わせていたただきます。
参考: Kocal/vue-web-extension: 🛠️ A boilerplate for quickly starting a web extension with Vue, webpack 4, ESLint and more!

(下記my-awesome-extensionはプロジェクト名です。開発される時は適宜読み換えてください)

vue init kocal/vue-web-extension my-awesome-extension
# 対話的に初期設定を尋ねられますので適宜設定します
cd my-awesome-extension
npm insall

これだけで初期設定の済んだプロジェクトができあがります。とっても楽ですね。

1点注意としては、これはChrome拡張には新規タブを拡張するタイプのものもあって、それには対応していません。ですが、ここまでセットアップされていれば新規タブ用に設定変更するの簡単なので後述します。

いったん動かしてみる

ここでHello worldレベルまでできているのでいったん動作を確認してみます。ボイラープレートのREADMEに書いてありますが、動かす方法はいくつかあります。

開発中に一番使うであろうwatch:devでやってみましょう。

npm run watch:dev

問題なければ/distディレクトリに開発用にビルドされます。

Google Chromeの拡張の追加機能をします。
chrome://extensionsにアクセスして、右上の「デベロッパーモード」をオンにします。

「パッケージ化されていない拡張機能を読み込む」ボタンをクリックして先程ビルドされた/distディレクトリを選択します。

そうするとすぐ下のインストールされた拡張一覧にプロジェクト名の拡張が追加されるとともにアラートダイアログで「Hello World!」と表示されたはずです。

これはボイラープレートで生成された初期ファイルのBackground.jsに書かれていたものが呼ばれたことで表示されています。
ここまで出ればひとまず動作確認OKです。

TypeScriptで書けるようにする

このボイラープレートはTSまで対応しているわけではないので自信でTS対応します。
といっても難しいものではなくよくあるVue.jsプロジェクトのTS対応と同じです。

npm install --save-dev typescript ts-loader

Vue.js用の型定義を手作業で作る必要があるので、名前はなんでも良いのですが今回はvue-shims.d.tsとしてプロジェクトルートに置きます。
内容は

declare module "*.vue" {
  import Vue from "vue";
  export default Vue;
}

と記載しておきます。

tsconfigも必要なので、続けてtsconfig.jsonを作って下記のように記載しました(内容は好みの設定による部分もあるので、サンプルです)

{
  "files": [
    "vue-shims.d.ts"
  ],
  "compilerOptions": {
    "target": "es5",
    "module": "CommonJS",
    "esModuleInterop": true,
    "moduleResolution": "node",
    "noImplicitReturns": true,
    "outDir": "./dist/",
    "sourceMap": true,
    "strict": true,
    "baseUrl": ".",
    "paths": {
      "@/*": [
        "src/*"
      ],
      "~/*": [
        "src/*"
      ]
    }
  },
  "include": [
    "./src/**/*.ts",
    "./src/**/*.vue",
    "vue-shims.d.ts"
  ],
  "exclude": [
    "node_modules",
    "dist"
  ]
}

そしてVue.js内でTSが使えるようにwebpack.config.jsresolveとrule定義にts-loaderを追記しましょう。

const config = {
  resolve: {
    extensions: [`.js`, `.ts`, `.vue`, `css`],
  module: {
    rules: [
      // ↓この部分
      {
        test: /\.ts$/,
        use: [
          {
            loader: `ts-loader`,
            options: {
              appendTsSuffixTo: [/\.vue$/],
            },
          },
        ],
      },
    ]
  }
}

もしコード中に

import SomeComponent from './components/SomeComponent'

みたいな行があれば

import SomeComponent from './components/SomeComponent.vue'

のように.vueと拡張子が必要になります。

ESlintのTypeScript対応

もしESlintを有効にしていたら、TS用に対応します。

npm install --save-dev @typescript-eslint/eslint-plugin @typescript-eslint/parser

ですね。通常のVue.js+TSと同様なため細かい設定は割愛します。

NewTab用の拡張に対応する

ボイラープレートで作られる拡張は、Chromeの上部のアドレスバー横にあってクリックすると動作や設定のポップアップが開く仕様のものです。

Chrome拡張ではこのほか、新規タブのスピードダイアル表示を拡張機能で上書きするものがあります(有名どころだとmomentのようなもの)。

これに対応するには、いくつか方法がありますが、今回は/srcに中にポップアップと同様にディレクトリを切ってやってみます。

まず/src/tabディレクトリを作成します。

次にその中にtab.tsを作ります。内容はひとまず

import Vue from 'vue'
import App from './App.vue'

/* eslint-disable no-new */
new Vue({
  el: `#app`,
  render: h => h(App),
})

としておきます。

App.vue同様に作り

<template>
  <div>
    <p> Hello World!
  </div>
</template>

<script lang="ts">
export default {
  data() {
    return {}
  },
}
</script>

とでもしておきましょう。

そしてこれらを読み込む、tab.htmlを作り

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Awesome NewTab</title>
  <link rel="stylesheet" href="tab.css">
  <% if (NODE_ENV === 'development') { %>
  <!-- Load some resources only in development environment -->
  <% } %>
</head>
<body>
  <div id="app">

  </div>
  <script src="tab.js"></script>
</body>
</html>

そしてwebpack.config.jsにエントリーポイントとして追加します。

const config = {
  entry: {
    background: `./background.ts`,
    'popup/popup': `./popup/popup.ts`,
    'tab/tab': `./tab/tab.ts`, // これを追記
  },
}

これで、tab回りのファイルがbuildされるので、最後に拡張としての設定を記述してある/src/manifest.json

{
  "chrome_url_overrides": {
    "newtab": "tab/tab.html"
}

を追加します。

ここまでやったら、一度ビルドしなおしてchromeからも拡張を更新します。通常の開発であればHMRが効き、更新やビルドしなおしが必要ありませんが、今回はビルド設定そのものを変更しているため必要です。

Chromeで新しいタブを開いてHello Worldが確認できればOKです。

あらためて動機紹介

余談ですが、これをやってみた動機をお話します。
web1week という1週間でWebサービスを作るイベントを見て参加できるか検討していました。

今回のお題は「Like」ということで考えたところ、以前作った好きな画像を背景にしたMarkdownメモGoogleChrome拡張のYANTANをリメイクしつつ機能拡張しようと思い立ちました。

せっかくなので環境構築から見直したんですが、ボイラープレートの出来があまりにも良かったので記事にした次第です。

肝心の企画への参加具合はタイムアウトでフィニッシュです。これは参加する前から想像ついていたんですが、個人的な開発に向かうメンタルとプライベートで時間が作りにくいことなどで間に合わないと踏んでいました。

というか、今よく考えると「Webサービス」なのでそもそも拡張は対象外でしたね。

とはいえ一度手がけたのとアイデア自体はあるのでこまごまと継続していこうと思います。

YANTAN、気になった方は使っていただいてフィードバックいただけたら嬉しいです。

僕のReleaseNote[0.38.8] & 僕のRoadMap[0.38.9]Gridsome開発における小ワザ4選