Chrome拡張機能の作り方

Chrome extension

2022.06.02

Google Chromeの拡張機能を作りました。


必要なファイル

  • manifest.json ... chrome拡張機能の設定ファイル
  • content-script.js ... HTMLへの要素の取得、追加を行う
  • background.js (optional) ... 外部との通信等、権限が必要な処理を行う

ファイル分割やライブラリを使いたいため、webpackで上のファイルをdistに生成するように設定しました。

undefined
const path = require("path");
const CopyWebpackPlugin = require("copy-webpack-plugin");

module.exports = {
  mode: process.env.NODE_ENV || "development",
  entry: {
    "content-script": path.join(__dirname, "src/content-script.js"),
    background: path.join(__dirname, "src/background.js"),
  },
  output: {
    path: path.join(__dirname, "dist/js"),
    filename: "[name].js",
  },
  module: {
    rules: [
      {
        exclude: /node_modules/,
      },
    ],
  },
  resolve: {
    extensions: [".js"],
  },
  plugins: [
    new CopyWebpackPlugin({ patterns: [{ from: "public", to: "../" }] }),
  ],
  devtool: "cheap-module-source-map",
};

package.jsonのscriptsを以下のように設定しました。

JSON
{
    ....,
    "scripts": {
        "build": "rm -rf dist && npx webpack --mode production",
        "dev": "rm -rf dist && npx webpack --watch --mode development",
        "test": "node src/background.js"
    }
}

devではファイルの変更を検知してただちにdistに反映されます。


作ったものの紹介

大学の授業の演習システムで解答を自動で埋める拡張機能を作りました。 S(Z) plus S(S(S(Z))) is S(S(S(S(Z)))) 上記のような文字列の導出木を指定された導出システムに従って埋める問題になっています。上のプログラム? は直感的には 1+3=4 を表しており、これをいくつかの導出規則、例えば S(n_1) plus n_2 is S(n_3) => n_1 plus n_2 is n_3 等の規則に従って導出木を埋める、というのが演習の内容です。詳細はプログラミング言語の基礎概念 をご確認ください。 プログラム本体はgithubにあります。


処理内容

この拡張機能はコンパイラやインタプリタと同じようなことを行っています。構文解析等はlex-bnfというライブラリを採用しました。他のライブラリも試しましたが、字句解析と構文解析を一度に行えることと、BNF記法に基づいたsyntaxがわかりやすいのが理由です。逆に使いづらい点としてはドキュメントと利用者が少なく(JavaScriptで構文解析をしたい人などそうそういないので当然ですが)、左再帰は自分で文法を変更する必要があるなどが挙げられます。lex-bnfのドキュメントでは構文解析時にプログラムの実行を行っていますが、今回はそれよりも複雑な動作をするので木構造を返すようにしています。 一例ですが今回は S(S(Z)) plus Z is S(S(Z)) というプログラムに対応する木(オブジェクト)として下のようなものを返すようにしました。

undefined
{
  type: 'plus',
  left: {
    type: 'succ',
    next: { type: 'succ', next: [Object], content: 'S(Z)' },
    content: 'S(S(Z))'
  },
  right: { type: 'zero', content: 'Z' },
  value: {
    type: 'succ',
    next: { type: 'succ', next: [Object], content: 'S(Z)' },
    content: 'S(S(Z))'
  },
  content: 'S(S(Z)) plus Z is S(S(Z))'
}

[Object]となっている部分には type 'zero'またはtype 'succ'のオブジェクトが入っています。


まとめ

今回でScript言語はコンパイラやインタプリタを書くのに向いていないと痛感しました。OCamlのようなパターンマッチが使える言語が大学のインタプリタ演習で使われている理由がよく分かりました。


拡張機能の作成には以下のサイトを参考にしました。

[TypeScript]Chrome拡張機能を作るための準備
Chrome Extensions を TypeScript で開発する