chrome拡張機能は、chromeウェブストアでダウンロードできますが、自分で拡張機能を作成をしてローカルで使用したり、chromeウェブストアに公開して一般のユーザーに使ってもらうことができます。

初めはGoogleの社員のような選ばれた人達だけがchrome拡張機能を作っているのかと思いましたが、本当に誰でも作って公開できることを知って驚きました。
今回は、作成のポイントをざっくり書いていこうと思います。
とは言え、私もJavaScriptの勉強を始めたばかりで、functionの書き方やループ処理や配列、非同期処理等の基本的なJavaScriptの知識をまだ理解していない状態でchrome拡張機能だけを作成したので間違っていることが多々あると思います。
他の方の記事やchrome拡張機能開発の公式のマニュアルも合わせて確認しながら自分の知識にしていってもらえればと思います。
逆に超初心者だからこそ伝わりやすいこともあるかと思いますのでなるべく初心者の方にも分かりやすく説明したいと思います。
chrome拡張機能は、HTMLやCSS、JavaScriptのファイルによって作成されています。
HTMLやCSSはオプション画面やポップアップ画面の表示に、JavaScriptはwebページのDOMの操作に用います。
オプション画面やポップアップ画面などを用意しない場合は、JavaScriptファイルのみで作成することもできます。
ブラウザベースの作業に限定されますが業務の効率化にぜひ作成してみてください。
用意するファイルの構成 Manifest V3対応
まずは、用意するファイルの構成を紹介します。ここでは私がchromeウェブストアで公開しているmemo memoという簡易的なメモの拡張機能のファイル構成を元に紹介させていただきます。







今回は丁寧にhtmlやcss、Javascript毎にフォルダ分けしましたがファイルが少なければmanifest.jsonと同じ階層に直接ファイルを配置しても良いかと思います。
{
"manifest_version": 3,
"version": "0.1",
"name": "memo memo",
"description": "シンプルなメモ帳",
"icons": {
"128": "images/icon-large.png",
"32": "images/icon.png"
},
"action": {
"default_title": "memomemo",
"default_icon": "images/icon.png",
"default_popup": "html/popup.html"
},
"permissions": ["storage"]
}


コードの解説
それでは、manifest.jsonの”action”である右上の拡張機能アイコンをクリックした際に表示される”default_popup”: “html/popup.html”のhtmlファイルを作成します。


<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<link rel="stylesheet" href="../css/bootstrap.min.css">
</head>
<body style="width: 400px; height: 500px;">
<div class="card text-dark bg-light mb-3">
<div class="card-header"><img src="../images/memomemo.svg" width="20" height="20" alt="chromeアイコン" style="margin-right: 5px;"> </i>memo memo</div>
<div class="card-body">
<!-- タブリスト一覧 -->
<ul class="nav nav-tabs" id="myTab" role="tablist">
<li class="nav-item" role="presentation">
<button class="nav-link active" id="FreeMemo-tab" data-bs-toggle="tab" data-bs-target="#FreeMemo" type="button" role="tab" aria-controls="FreeMemo" aria-selected="true">
FreeMemo
</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link" id="TableMemo-tab" data-bs-toggle="tab" data-bs-target="#TableMemo" type="button" role="tab" aria-controls="TableMemo" aria-selected="false">
TableMemo
</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link" id="BoxMemo-tab" data-bs-toggle="tab" data-bs-target="#BoxMemo" type="button" role="tab" aria-controls="BoxMemo" aria-selected="false">
BoxMemo
</button>
</li>
</ul>
<!-- タブリスト一覧 ここまで -->
<!-- タブの中身 -->
<div class="tab-content" id="myTabContent">
<!-- FreeMemoタブの中身 -->
<div class="tab-pane fade show active" id="FreeMemo" role="tabpanel" aria-labelledby="FreeMemo-tab">
<div class="mb-3" style="margin-bottom: 0rem!important;">
<!-- <label for="exampleFormControlTextarea1" class="form-label">メモ帳</label> -->
<textarea class="form-control" id="FreeMemo_textarea" rows="15"></textarea>
</div>
</div>
<!-- TableMemoタブの中身 -->
<div class="tab-pane fade" id="TableMemo" role="tabpanel" aria-labelledby="TableMemo-tab">
<table class="table" style="margin-top: 1rem;">
<thead>
<tr>
<th scope="col">#</th>
<th scope="col">
<div class="mb-3" style="margin-bottom: 0rem!important;"><input type="text" class="form-control" id='thead1'> </div>
</th>
<th scope="col">
<div class="mb-3" style="margin-bottom: 0rem!important;"><input type="text" class="form-control" id='thead2'> </div>
</th>
<th scope="col">
<div class="mb-3" style="margin-bottom: 0rem!important;"><input type="text" class="form-control" id='thead3'> </div>
</th>
</tr>
</thead>
<tbody>
<tr>
<th scope="row">1</th>
<td>
<div class="mb-3" style="margin-bottom: 0rem!important;"><input type="text" class="form-control" id='row1-1'> </div>
</td>
<td>
<div class="mb-3" style="margin-bottom: 0rem!important;"><input type="text" class="form-control" id='row1-2'> </div>
</td>
<td>
<div class="mb-3" style="margin-bottom: 0rem!important;"><input type="text" class="form-control" id='row1-3'> </div>
</td>
</tr>
<tr>
<th scope="row">2</th>
<td>
<div class="mb-3" style="margin-bottom: 0rem!important;"><input type="text" class="form-control" id='row2-1'> </div>
</td>
<td>
<div class="mb-3" style="margin-bottom: 0rem!important;"><input type="text" class="form-control" id='row2-2'> </div>
</td>
<td>
<div class="mb-3" style="margin-bottom: 0rem!important;"><input type="text" class="form-control" id='row2-3'> </div>
</td>
</tr>
<tr>
<th scope="row">3</th>
<td>
<div class="mb-3" style="margin-bottom: 0rem!important;"><input type="text" class="form-control" id='row3-1'> </div>
</td>
<td>
<div class="mb-3" style="margin-bottom: 0rem!important;"><input type="text" class="form-control" id='row3-2'> </div>
</td>
<td>
<div class="mb-3" style="margin-bottom: 0rem!important;"><input type="text" class="form-control" id='row3-3'> </div>
</td>
</tr>
<tr>
<th scope="row">4</th>
<td>
<div class="mb-3" style="margin-bottom: 0rem!important;"><input type="text" class="form-control" id='row4-1'> </div>
</td>
<td>
<div class="mb-3" style="margin-bottom: 0rem!important;"><input type="text" class="form-control" id='row4-2'> </div>
</td>
<td>
<div class="mb-3" style="margin-bottom: 0rem!important;"><input type="text" class="form-control" id='row4-3'> </div>
</td>
</tr>
<tr>
<th scope="row">5</th>
<td>
<div class="mb-3" style="margin-bottom: 0rem!important;"><input type="text" class="form-control" id='row5-1'> </div>
</td>
<td>
<div class="mb-3" style="margin-bottom: 0rem!important;"><input type="text" class="form-control" id='row5-2'> </div>
</td>
<td>
<div class="mb-3" style="margin-bottom: 0rem!important;"><input type="text" class="form-control" id='row5-3'> </div>
</td>
</tr>
</tbody>
</table>
</div>
<!-- TableMemoタブの中身 -->
<div class="tab-pane fade" id="BoxMemo" role="tabpanel" aria-labelledby="BoxMemo-tab" style="margin-top: 1rem;">
<div class="mb-3" style="border-bottom: solid 1px #dddddd; padding-bottom: 10px;"> <label for="exampleFormControlTextarea1" class="form-label"><input type="text" class="form-control" id='BoxMemo_Title1' style="width: 60%;"></label> <input
type="text" class="form-control" id='BoxMemo_Content1'> </div>
<div class="mb-3" style="border-bottom: solid 1px #dddddd; padding-bottom: 10px;"> <label for="exampleFormControlTextarea1" class="form-label"><input type="text" class="form-control" id='BoxMemo_Title2' style="width: 60%;"></label> <input
type="text" class="form-control" id='BoxMemo_Content2'> </div>
<div class="mb-3" style="border-bottom: solid 1px #dddddd; padding-bottom: 10px;"> <label for="exampleFormControlTextarea1" class="form-label"><input type="text" class="form-control" id='BoxMemo_Title3' style="width: 60%;"></label> <input
type="text" class="form-control" id='BoxMemo_Content3'> </div>
<div class="mb-3" style="border-bottom: solid 1px #dddddd; padding-bottom: 10px;"> <label for="exampleFormControlTextarea1" class="form-label"><input type="text" class="form-control" id='BoxMemo_Title4' style="width: 60%;"></label> <input
type="text" class="form-control" id='BoxMemo_Content4'> </div>
<div class="mb-3" style="border-bottom: solid 1px #dddddd; padding-bottom: 10px;"> <label for="exampleFormControlTextarea1" class="form-label"><input type="text" class="form-control" id='BoxMemo_Title5' style="width: 60%;"></label> <input
type="text" class="form-control" id='BoxMemo_Content5'> </div>
</div>
</div>
<!-- 保存ボタン -->
<!-- <div class="col-12"> -->
<button type="button" class="btn btn-primary" id='save'>保存する</button>
<!-- </div> -->
</div>
</div>
<script src="../javascript/popup.js"></script>
<script src="../javascript/bootstrap.min.js"></script>
</body>
</html>
お次は、popup.htmlでユーザーが入力して保存ボタンを押した際にchrome.storage.local.setを使ってlocalにユーザーが入力した値を保存する処理と次回ポップアップを表示した際に前回chrome.storage.local.setを使ってlocalに保存した値をchrome.storage.local.getを使用して初期値としてフォームに入力する処理をpopup.jsに記載します。
chrome.storage.local.については、https://developer.chrome.com/docs/extensions/reference/storage/こちらのchrome公式APIリファレンスのドキュメントをご覧ください。
簡単に説明するとchromeのAPI を使用してkeyとvalue形式でユーザーのchromeブラウザに値を保存できる機能になります。


popup.htmlにscript src=”../javascript/popup.js”でpopup.htmlファイルへのpopup.jsファイルの適用も忘れずにお願い致します。
// オプション画面の初期値を設定するために
//chrome.storage.local.getでlocalに保存した値をget(取得)する
chrome.storage.local.get(null, ((data) => {
console.log(data)
//FreeMemo
if (data.FreeMemo_textarea) {
document.getElementById('FreeMemo_textarea').value = data.FreeMemo_textarea;
}
//TableMemo
if (data.thead1) {
document.getElementById('thead1').value = data.thead1;
}
if (data.thead2) {
document.getElementById('thead2').value = data.thead2;
}
if (data.thead3) {
document.getElementById('thead3').value = data.thead3;
}
if (data["row1-1"]) {
document.getElementById('row1-1').value = data["row1-1"];
}
if (data["row1-2"]) {
document.getElementById('row1-2').value = data["row1-2"];
}
if (data["row1-3"]) {
document.getElementById('row1-3').value = data["row1-3"];
}
if (data["row2-1"]) {
document.getElementById('row2-1').value = data["row2-1"];
}
if (data["row2-2"]) {
document.getElementById('row2-2').value = data["row2-2"];
}
if (data["row2-3"]) {
document.getElementById('row2-3').value = data["row2-3"];
}
if (data["row3-1"]) {
document.getElementById('row3-1').value = data["row3-1"];
}
if (data["row3-2"]) {
document.getElementById('row3-2').value = data["row3-2"];
}
if (data["row3-3"]) {
document.getElementById('row3-3').value = data["row3-3"];
}
if (data["row4-1"]) {
document.getElementById('row4-1').value = data["row4-1"];
}
if (data["row4-2"]) {
document.getElementById('row4-2').value = data["row4-2"];
}
if (data["row4-3"]) {
document.getElementById('row4-3').value = data["row4-3"];
}
if (data["row5-1"]) {
document.getElementById('row5-1').value = data["row5-1"];
}
if (data["row5-2"]) {
document.getElementById('row5-2').value = data["row5-2"];
}
if (data["row5-3"]) {
document.getElementById('row5-3').value = data["row5-3"];
}
//BoxMemo
if (data.BoxMemo_Title1) {
document.getElementById('BoxMemo_Title1').value = data.BoxMemo_Title1;
}
if (data.BoxMemo_Content1) {
document.getElementById('BoxMemo_Content1').value = data.BoxMemo_Content1;
}
if (data.BoxMemo_Title2) {
document.getElementById('BoxMemo_Title2').value = data.BoxMemo_Title2;
}
if (data.BoxMemo_Content2) {
document.getElementById('BoxMemo_Content2').value = data.BoxMemo_Content2;
}
if (data.BoxMemo_Title3) {
document.getElementById('BoxMemo_Title3').value = data.BoxMemo_Title3;
}
if (data.BoxMemo_Content3) {
document.getElementById('BoxMemo_Content3').value = data.BoxMemo_Content3;
}
if (data.BoxMemo_Title4) {
document.getElementById('BoxMemo_Title4').value = data.BoxMemo_Title4;
}
if (data.BoxMemo_Content4) {
document.getElementById('BoxMemo_Content4').value = data.BoxMemo_Content4;
}
if (data.BoxMemo_Title5) {
document.getElementById('BoxMemo_Title5').value = data.BoxMemo_Title5;
}
if (data.BoxMemo_Content5) {
document.getElementById('BoxMemo_Content5').value = data.BoxMemo_Content5;
}
}))
//保存ボタンを押した際の処理
document.getElementById("save").onclick = function() {
console.log('success');
//chrome.storage.local.setでstorage.localに値をsetする 同じkeyの場合は前回setされたものを上書きする
chrome.storage.local.set({
//FreeMemo
'FreeMemo_textarea': document.getElementById('FreeMemo_textarea').value,
//TableMemo
'thead1': document.getElementById('thead1').value,
'thead2': document.getElementById('thead2').value,
'thead3': document.getElementById('thead3').value,
'row1-1': document.getElementById('row1-1').value,
'row1-2': document.getElementById('row1-2').value,
'row1-3': document.getElementById('row1-3').value,
'row2-1': document.getElementById('row2-1').value,
'row2-2': document.getElementById('row2-2').value,
'row2-3': document.getElementById('row2-3').value,
'row3-1': document.getElementById('row3-1').value,
'row3-2': document.getElementById('row3-2').value,
'row3-3': document.getElementById('row3-3').value,
'row4-1': document.getElementById('row4-1').value,
'row4-2': document.getElementById('row4-2').value,
'row4-3': document.getElementById('row4-3').value,
'row5-1': document.getElementById('row5-1').value,
'row5-2': document.getElementById('row5-2').value,
'row5-3': document.getElementById('row5-3').value,
//BoxMemo
'BoxMemo_Title1': document.getElementById('BoxMemo_Title1').value,
'BoxMemo_Content1': document.getElementById('BoxMemo_Content1').value,
'BoxMemo_Title2': document.getElementById('BoxMemo_Title2').value,
'BoxMemo_Content2': document.getElementById('BoxMemo_Content2').value,
'BoxMemo_Title3': document.getElementById('BoxMemo_Title3').value,
'BoxMemo_Content3': document.getElementById('BoxMemo_Content3').value,
'BoxMemo_Title4': document.getElementById('BoxMemo_Title4').value,
'BoxMemo_Content4': document.getElementById('BoxMemo_Content4').value,
'BoxMemo_Title5': document.getElementById('BoxMemo_Title5').value,
'BoxMemo_Content5': document.getElementById('BoxMemo_Content5').value,
});
window.alert('保存しました。')
};
// })
まとめ
CSSに関しては、今回bootstrap5というデザインテンプレートのフレームワークを使用しています。今人気らしいので勉強しておいて損はないかと思います。デザインに自信のない方であれば大幅に時間短縮ができるのでありがたいですね。
次回は、今回紹介できなかったchrome拡張機能において重要なservice_workerとメッセージパッシングについて紹介しようと思います。
コメント