webpack/babelでWebExtensionsの開発環境を整える

    webpack screenshort

    Firefox はバージョン 48 から WebExtensions のサポートを始め、そして 2017 年 11 月にリリースされるバージョン 57 で、旧 API を使った Add-ons のサポートを打ち切ります。 この意向に嘆く Firefox ユーザも少なくないでしょうが、Mozilla も脆弱性の温床だった旧 Add-ons API を早く捨てたかったんじゃないでしょうか。 Microsoft Edge も WebExtensions をサポートしているそうで、より WebExtensions の機運が高まってます。

    この記事では webpack と babel を使ったモダンな、WebExtensions の開発環境を構築する方法を紹介します。 完成品は GitHub で公開してます。

    ビルド環境を整える

    ビルド環境は、webpack と babel を使って ES2015 の JavaScript をビルドして、バンドルされた JavaScript を出力したいと思います。 まず npm で必要なパッケージをインストールします。

    npm init
    npm install -D webpack babel-loader babel-core babel-preset-es2015

    次に webpack.config.js をプロジェクトのルートに配置します。

    const path = require("path")
    
    const src = path.resolve(__dirname, "src")
    const dist = path.resolve(__dirname, "build")
    
    module.exports = {
      entry: {
        content: path.join(src, "content"),
        background: path.join(src, "background"),
      },
    
      output: {
        path: dist,
        filename: "[name].js",
      },
    
      module: {
        loaders: [
          {
            test: /\.js$/,
            exclude: /node_modules/,
            loader: "babel-loader",
            query: {
              presets: ["es2015"],
            },
          },
        ],
      },
    
      resolve: {
        extensions: [".js"],
      },
    }

    この設定で、background.jscontent.js の 2 つの JavaScript を生成します。 content.jsは content scripts と呼ばれるもので、HTML 上でロードされるファイルです。 background.js は background scripts と呼ばれるもので、contet scripts と独立したプロセスで動作します。

    webpack はsrc/content, src/backgroundに配置した javaScript をビルドして、バンドルされた JavaScript をbuildディレクトリに生成します。 空の JavaScript を配置して、webpack がビルドできるようにします。

    mkdir -p src/{background,content}
    touch src/{background,content}/index.js

    webpack が正常にビルドできるか確認します。 ビルドするとbuildディレクトリにcontent.jsbackground.jsが生成されるはずです。

    node_modules/.bin/webpack

    開発用にpackage.jsonにビルドスクリプト"start": "webpack -w --debug"を登録しましょう。

    マニフェストファイルを配置する

    WebExtensions はマニフェストファイルが必要になります。 プロジェクトのルートにmanifest.jsonをファイルを作ります。

    {
      "manifest_version": 2,
      "name": "Web Extension Template",
      "description": "Web Extensions tutorial for Google Chrome/Chromium and Firefox.",
      "version": "1.0.0",
      "content_scripts": [
        {
          "matches": ["http://*/*", "https://*/*"],
          "js": ["build/content.js"]
        }
      ],
      "background": {
        "scripts": ["build/background.js"]
      }
    }

    そしてブラウザでプラグインをロードします。 Firefox の場合はabout:configを開き「Load Temporary Add-ons」からロードします。 Google Chrome/Chromium はchrome://extensionsを開き「Load unbacked extension...」からロードします。 これでエラーが表示されなければ OK です。

    WebExtensions の開発を始める

    ここまでで必要な環境は揃いました。 簡単な WebExtensions を記述してみましょう。 まずは先程登録したnpm startwebpackを起動してます。 これで webpack がファイル変更を検知して、自動でビルドします。

    h/lキーでタブの切り替えをできる簡単な WebExtensions を作ってみます。 まずはsrc/content/index.jsからです。

    //  src/content/index.js
    window.addEventListener("keypress", e => {
      if (e.key === "h") {
        chrome.runtime.sendMessage({ type: "tabs.prev" })
      } else if (e.key === "l") {
        // L
        chrome.runtime.sendMessage({ type: "tabs.next" })
      } else {
        return
      }
    
      e.stopPropagation()
      e.preventDefault()
    })

    content scripts からはwindowや DOM 操作ができます。 そしてchromeオブジェクトで WebExtensions API にアクセスできます。 この例ではキーが押されるとchrome.runtime.sendMessage()を呼び出して、background scripts にメッセージを送ります。

    つぎに background scripts 側です。 chrome.runtime.onMessage.addListener()で、メッセージのイベントリスナを登録します。 この例ではメッセージを受け取った気にタブを切り替える処理を記述しています。

    //  src/background/index.js
    function selectPrevTab(current) {
      chrome.tabs.query({ currentWindow: true }, tabs => {
        let index = (current.index - 1 + tabs.length) % tabs.length
        chrome.tabs.update(tabs[index].id, { active: true })
      })
    }
    
    function selectNextTab(current) {
      chrome.tabs.query({ currentWindow: true }, tabs => {
        let index = (current.index + 1) % tabs.length
        chrome.tabs.update(tabs[index].id, { active: true })
      })
    }
    
    chrome.runtime.onMessage.addListener((request, sender) => {
      switch (request.type) {
        case "tabs.prev":
          selectPrevTab(sender.tab)
          break
        case "tabs.next":
          selectNextTab(sender.tab)
          break
      }
    })

    以上で全ての手順は終了です。 WebExtensions も基本的に JavaScript なので、モダンな環境で開発を始められます。 また ESLint や mocha といった既存の資源を使って、普通の JavaScript と同じように開発を進めることができます。


    Profile picture

    Shin'ya Ueoka

    B2B向けSaaSを提供する会社の、元Webエンジニア。今はエンジニアリング組織のマネジメントをしている。