一瞬で終わるTypeScriptでChrome拡張機能開発ハンズオン



今回は、TypeScriptを使って、ブラウザのツールバーに現在時刻を表示するシンプルなChrome拡張機能を開発するハンズオンを書きました。
このハンズオンでは以下のことを学べます
- Chrome拡張機能の開発方法
- Chrome拡張機能の動作確認方法

以下のことは含んでおりません。
- 開発した拡張機能を公開する方法

また、ソースコードは以下にあります。
https://github.com/msdsm/chrome-extension-ts

## 前提条件
- Node.jsとnpmがインストールされていること
- 基本的なHTMLとJavaScriptの知識
- Chromeブラウザがインストールされていること

## Chrome拡張機能の基礎知識

### Chrome拡張機能とは

Chrome拡張機能は、GoogleのChromeブラウザの機能を拡張するためのプログラムです。ユーザーインターフェースの変更、ウェブページの内容の修正、ブラウザの動作の変更など、様々な目的で利用されています。

### 拡張機能開発に必要なこと

1. manifest.json: 拡張機能の「設計図」となるJSONファイルで、拡張機能の基本情報、必要な権限、使用するファイル等を定義します
2. HTML/CSS/JavaScript: ユーザーインターフェースの構築と機能の実装
3. アイコン: 拡張機能を識別するためのアイコン画像（複数サイズ）

### 拡張機能の主要コンポーネント
- Manifest: 拡張機能の設定ファイル（manifest.json）
- ポップアップ: アイコンクリック時に表示されるHTML画面
- バックグラウンドスクリプト: 拡張機能のバックグラウンドで動作するスクリプト
- コンテンツスクリプト: Webページに直接作用するスクリプト（今回は使用しません）

## 開発環境の準備

まずは、プロジェクトのディレクトリを作成し、必要なパッケージをインストールしましょう。

```bash
# プロジェクトディレクトリを作成
mkdir chrome-time-display
cd chrome-time-display

# npm初期化
npm init -y

# TypeScriptとWebpackのインストール
npm install --save-dev typescript webpack webpack-cli ts-loader copy-webpack-plugin
```

### 各パッケージの役割
- webpack: モジュールバンドラー。複数のJSファイルを一つにまとめる
- webpack-cli: コマンドラインからWebpackを使うためのツール
- ts-loader: WebpackでTypeScriptを扱うためのローダー
- copy-webpack-plugin: ビルド時に必要なファイルをコピーするためのプラグイン

## TypeScript設定ファイルの作成

TypeScriptの設定ファイル（`tsconfig.json`）を作成します。

```json
{
  "compilerOptions": {
    "target": "es6",
    "module": "commonjs",
    "strict": true,
    "esModuleInterop": true,
    "outDir": "./dist",
    "sourceMap": true
  },
  "include": [
    "src/**/*"
  ]
}
```

### `tsconfig.json`の各設定の解説

- target: コンパイル後のJavaScriptのバージョン（ES6=ECMAScript 2015）
- module: モジュールシステムの種類（CommonJS=Node.jsで使われる形式）
- strict: 厳格な型チェックを有効にする
- esModuleInterop: CommonJSモジュールをES Modulesとしてインポート可能にする
- outDir: コンパイル後のファイルの出力先
- sourceMap: デバッグ用にソースマップを生成
- include: コンパイル対象のファイル（`src`ディレクトリ内の全てのファイル）

## Webpackの設定

Webpackの設定ファイル（`webpack.config.js`）を作成します。

```javascript
const path = require('path');
const CopyPlugin = require('copy-webpack-plugin');

module.exports = {
  mode: 'production',
  devtool: 'source-map',
  entry: {
    background: './src/background.ts',
    popup: './src/popup.ts',
  },
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: '[name].js',
  },
  module: {
    rules: [
      {
        test: /\.ts$/,
        use: 'ts-loader',
        exclude: /node_modules/,
      },
    ],
  },
  resolve: {
    extensions: ['.ts', '.js'],
  },
  plugins: [
    new CopyPlugin({
      patterns: [
        { from: 'src/manifest.json', to: 'manifest.json' },
        { from: 'src/popup.html', to: 'popup.html' },
        { from: 'src/icons', to: 'icons' }
      ],
    }),
  ],
};
```

### `webpack.config.js`の各設定の解説

- mode: ビルドモード
- devtool: ソースマップの生成方法。
- entry: ビルドの開始点となるファイル。複数指定可能（`background.ts`と`popup.ts`）
- output: 出力先と出力ファイル名の設定
- module.rules: ファイルの種類ごとの処理方法（`.ts`ファイルはts-loaderで処理）
- resolve.extensions: モジュール解決時に自動的に解決する拡張子
- plugins: 追加の処理を行うプラグイン（今回はファイルコピー用）

## プロジェクト構造の作成

必要なディレクトリとファイルを作成します。

```bash
# ソースコードディレクトリとアイコンディレクトリを作成
mkdir -p src/icons
```

## manifest.jsonの作成

Chrome拡張機能のマニフェストファイル (`manifest.json`)を作成します。


```json
{
  "manifest_version": 3,
  "name": "現在時刻表示",
  "version": "1.0",
  "description": "ブラウザのツールバーに現在時刻を表示します",
  "action": {
    "default_popup": "popup.html",
    "default_icon": {
      "16": "icons/icon16.png",
      "48": "icons/icon48.png",
      "128": "icons/icon128.png"
    }
  },
  "background": {
    "service_worker": "background.js"
  },
  "permissions": []
}
```

### manifest.jsonの各設定の解説

- manifest_version: マニフェストのバージョン（Chrome拡張機能ではV3が最新）
- name, version, description: 拡張機能の基本情報
- action: ブラウザのツールバーに表示されるボタンの設定
  - default_popup: クリック時に表示されるHTMLファイル
  - default_icon: 表示されるアイコン（複数サイズを指定）
- background: バックグラウンドで常に動作するスクリプト
  - service_worker: Manifest V3からはService Workerとして実装する必要がある
- permissions: 拡張機能に必要な権限（今回は特になし）

## アイコンの準備

拡張機能のアイコンを用意します。
適当なアイコンを作成または入手して、以下のサイズで準備します。
- 16×16ピクセル（`src/icons/icon16.png`）
- 48×48ピクセル（`src/icons/icon48.png`）
- 128×128ピクセル（`src/icons/icon128.png`）

## ポップアップHTMLの作成

ポップアップのHTMLファイル (`src/popup.html`)を作成します。これは拡張機能アイコンをクリックしたときに表示されるUIです。


```html
<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <style>
    body {
      width: 200px;
      font-family: Arial, sans-serif;
      text-align: center;
      padding: 10px;
    }
    #clock {
      font-size: 24px;
      margin: 10px 0;
      font-weight: bold;
    }
    #date {
      font-size: 14px;
      color: #666;
    }
  </style>
</head>
<body>
  <h3>現在時刻</h3>
  <div id="clock"></div>
  <div id="date"></div>
  <script src="popup.js"></script>
</body>
</html>
```

### popup.htmlの解説

- シンプルなHTMLで時刻と日付を表示するための要素を定義
- スタイルはインラインで直接定義（小規模なプロジェクトでは簡潔さのためにこの方法も有効）
- 重要: `popup.js`を読み込んでいますが、これは`popup.ts`がコンパイルされたものが自動的に配置されるファイル

## TypeScriptファイルの実装

### バックグラウンドスクリプト

バックグラウンドスクリプト (`src/background.ts`)を作成します。今回は特に複雑な処理は行いませんが、将来的な拡張のためのテンプレートとして作成します。


```typescript
// バックグラウンドスクリプト
// この拡張機能では特に何もしません。
// より複雑な機能を追加する場合に使用します。
console.log('バックグラウンドスクリプトが読み込まれました');
```

### background.tsの解説

- Chrome拡張機能のManifest V3ではバックグラウンドスクリプトはService Workerとして実行される
- Service Workerには以下の制約がある:
  - DOMアクセス不可（document, windowオブジェクトがない）
  - `eval()`などの動的コード実行が制限されている（Webpackの開発モードではこれが問題になる）
  - バックグラウンドで間欠的に起動するため、永続的な状態保持には注意が必要

### ポップアップスクリプト

ポップアップスクリプト (`src/popup.ts`) を作成します。これが今回の拡張機能の主要な機能を実装する部分です。


```typescript
// 時計と日付を表示する要素を取得
const clockElement = document.getElementById('clock') as HTMLDivElement;
const dateElement = document.getElementById('date') as HTMLDivElement;

// 現在時刻を更新する関数
function updateTime(): void {
  const now = new Date();
  
  // 時、分、秒を取得（2桁表示に整形）
  const hours = now.getHours().toString().padStart(2, '0');
  const minutes = now.getMinutes().toString().padStart(2, '0');
  const seconds = now.getSeconds().toString().padStart(2, '0');
  
  // 年、月、日を取得
  const year = now.getFullYear();
  const month = (now.getMonth() + 1).toString().padStart(2, '0'); // 月は0から始まるので+1
  const day = now.getDate().toString().padStart(2, '0');
  
  // 曜日を取得
  const weekdays = ['日', '月', '火', '水', '木', '金', '土'];
  const weekday = weekdays[now.getDay()];
  
  // 画面に表示
  clockElement.textContent = `${hours}:${minutes}:${seconds}`;
  dateElement.textContent = `${year}年${month}月${day}日(${weekday})`;
}

// 初回実行
updateTime();

// 1秒ごとに時刻を更新
setInterval(updateTime, 1000);
```

### popup.tsの解説

- TypeScriptの型アサーション（`as HTMLDivElement`）を使って、HTMLの要素の型を明示
- 時刻フォーマット処理での`padStart`メソッドは数値を2桁表示に整形
- `updateTime`関数で現在時刻と日付を取得して表示
- `setInterval`で1秒ごとに時刻を更新（リアルタイム表示を実現）
- TypeScriptの利点: コード補完、型安全性、バグの早期発見など

## package.jsonの更新

package.jsonにビルドスクリプトを追加します。

```json
// package.jsonのscriptsセクションを更新
"scripts": {
  "build": "webpack --config webpack.config.js",
  "watch": "webpack --watch --config webpack.config.js"
}
```

### スクリプトの役割

- build: 一度だけビルドを実行
- watch: ファイルの変更を監視して、変更があるたびに自動的にビルドを実行（開発時に便利）

## npm run buildでの処理の流れ

`npm run build`を実行すると、以下の処理が行われます：

1. Webpackの起動: `webpack.config.js`の設定に基づいてWebpackが起動
2. TypeScriptのコンパイル: ts-loaderによって`.ts`ファイルが`.js`ファイルにコンパイル
3. バンドル処理: 依存関係を解決して複数のJSファイルを一つにまとめる
4. 最適化: production modeの場合、コードの最適化（圧縮等）が行われる
5. ファイルコピー: CopyPluginによって指定されたファイル（manifest.json, popup.html, iconsディレクトリ）がdistディレクトリにコピー
6. 出力: 最終的なファイルがdistディレクトリに出力される

## ビルドと実行

拡張機能をビルドします。

```bash
# ビルド実行
npm run build
```

ビルドが成功すると、`dist`ディレクトリに以下のファイルが生成されます：
- `background.js`: コンパイルされたバックグラウンドスクリプト
- `popup.js`: コンパイルされたポップアップスクリプト
- `manifest.json`: コピーされたマニフェストファイル
- `popup.html`: コピーされたHTMLファイル
- `icons/`: コピーされたアイコンファイル
- 各種ソースマップファイル（`.js.map`）


## Chrome拡張機能としてインストール

1. Chromeブラウザで `chrome://extensions` を開きます
2. 右上の「デベロッパーモード」をオンにします
3. 「パッケージ化されていない拡張機能を読み込む」をクリックします
4. プロジェクトの `dist` ディレクトリを選択します

これで拡張機能がインストールされ、ブラウザの右上に拡張機能のアイコンが表示されます。

## 動作確認方法

1. ブラウザの右上に表示された拡張機能のアイコンをクリックします
2. ポップアップが開き、現在時刻と日付が表示されます
3. 時刻が1秒ごとに自動的に更新されることを確認します


![スクリーンショット 2025-04-22 2.42.49.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3876904/849c8db5-9dc9-4ad7-82fa-e737336c9fff.png)


## おわりに

これで、TypeScriptを使った簡単なChrome拡張機能の開発が完了しました。このプロジェクトで学んだ内容は、より複雑な拡張機能開発にも応用できます。