Chrome Extensionsをwepback + TypeScriptで開発する

Blog
Info

webpackの代わりにViteを使う方法は、次のページを参照のこと。
Chrome Extensions を Vite + TypeScript で開発する

Chrome Extensions(以下、Chrome拡張)をTypeScriptで開発するためのwebpackの設定やファイル構成について記載しています。

この記事では、Chrome拡張の作り方については説明しません。作成に必要なファイルや作成したChrome拡張の読み込み方は、Chrome Extension - Getting Started Tutorialを参照してください。

動作を確認した環境

  • Node.js v12.14.0
  • TypeScript v3.8.3
  • webpack v4.42.1
  • TypeScript v3.8.3

プロジェクト作成

  1. Chrome拡張のプロジェクトを作成します。

    mkdir chrome-extension-sample && cd $_
    npm init -y
    
  2. webpackやwebpackのプラグインをインストールします。

    npm install --save-dev webpack webpack-cli copy-webpack-plugin
    
  3. TypeScriptや、TypeScriptをwebpackで利用するためのts-loaderをインストールします。

    npm install --save-dev typescript ts-loader
    
  4. Chromeの型定義をインストールします。

    npm install --save-dev @types/chrome
    

ファイル構成

次のファイル構成となるように、必要なファイルを作成します。

.
├── node_modules
├── public # Chrome拡張に必要なファイル
   ├── image
   └── icon128.png # Chrome拡張のアイコン
   └── manifest.json # Chrome拡張の設定ファイル
├── src # トライスパイル対象のファイル
   └── background.ts # ブラウザ起動時に読み込まれるファイル
├── package.json
├── package-lock.json
├── tsconfig.json # TypeScriptの設定
└── webpack.config.js # webpackの設定

tsconfig.json

{
  "compilerOptions": {
    "target": "es6",
    "module": "commonjs",
    "strict": true,
    "rootDir": "src",
    "esModuleInterop": true,
    "typeRoots": [ "node_modules/@types"]
  },
  "exclude": [
    "node_modules"
  ]
}

webpack.config.js

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

module.exports = {
  mode: process.env.NODE_ENV || "development",
  entry: {
    background: path.join(__dirname, "src/background.ts"),
  },
  output: {
    path: path.join(__dirname, "dist/js"),
    filename: "[name].js",
  },
  module: {
    rules: [
      {
        test: /\.ts$/,
        use: "ts-loader",
        exclude: /node_modules/,
      },
    ],
  },
  resolve: {
    extensions: [".ts", ".js"],
  },
  plugins: [
    new CopyPlugin([{ from: ".", to: "../" }], { context: "public" })
  ],
};
  • トランスパイル対象のファイルをentryで指定します。
  • トランスパイル後のJavaScriptファイルをdist/jsの下に出力します。
  • CopyPluginを使って、「public」ディレクトリに置いたアイコンファイルやmanifest.jsonをコピーします。

background.ts

今回は、選択した文字列をGoogle検索するコンテキストメニュー(右クリックしたときに開くメニュー)を実装します。

const openTab = (query?: string) => {
  if(query) {
    chrome.tabs.create({ url: `https://www.google.com/search?q=${query}` });
  }
}

chrome.runtime.onInstalled.addListener((): void => {
  chrome.contextMenus.create({
    id: "sample",
    title: "選択した文字列を検索する",
    contexts: ["selection"]
  });
});

chrome.contextMenus.onClicked.addListener((info, tab): void => {
  openTab(info.selectionText);
});

manifest.json

{
  "manifest_version": 2,
  "name": "chrome-extension-sample",
  "description": "A sample Chrome Extension.",
  "version": "0.0.1",
  "icons": { "128": "image/icon128.png" },
  "background": {
    "scripts": ["js/background.js"],
  },
  "permissions": ["contextMenus", "tabs"]
}
  • manifest.jsonはwebpack実行時に「dist」ディレクトリの下にコピーされるので、各ファイルのパスは、「dist」ディレクトリからの相対パスを指定します。

ビルド

productionモードでビルドします。

npx webpack --mode production

ビルド後のファイル構成は次のようになります。

.
├── dist
   ├── image
   └── icon128.png
   ├── js
   └── background.js
   └── manifest.json
├── node_modules
├── public
   └── ... ...
├── src
   └── ... ...
├── package-lock.json
├── package.json
├── tsconfig.json
└── webpack.config.js

動作確認

  1. Chrome Extension - Getting Started Tutorialの記載の手順で、Chrome拡張を読み込みます。 読み込みするディレクトリは「dist」です。

  2. 新しくタブを開きます(既存のタブの場合は、リロードしてください)。

  3. Webページの文字を選択し、右クリックします。「選択した文字列を検索する」をクリックします。

  4. 選択した文字についてGoogle検索した結果が新しいタブで表示されます。