Chrome/Firefox両対応の拡張機能(アドオン)を簡単に作る方法
HTML Minify Clipboardという Firefox アドオンを作りました。
このアドオンのソースコードはChrome拡張機能としても動作します。
今回作成してみて、Chrome/Firefoxなど様々なブラウザで使える拡張機能の開発がとても簡単だったので、その作り方を紹介します。
ひな型作成
アドオンを作るにあたり、ブラウザの拡張機能を開発するためのひな型とそれをビルドするための仕組みをあらかじめ構築してくれるCLIツールを使いました。
これを使えば、必要なコードの追加とビルドコマンドを実行するだけで拡張機能が作れるのでとても楽でした。
Generator: WebExtension
まず、グローバルに以下をインストールします。
$ npm install -g yo generator-web-extension
拡張機能を開発するディレクトリを作成し(ここではhtml-minify-clipboard
)、そのディレクトリの中でyo web-extension
を実行します。
$ mkdir html-minify-clipboard
$ cd html-minify-clipboard
$ yo web-extension
実行後、ひな型に必要な情報を聞かれるので、自分の作りたいものに応じて入力していきます。
? What would you like to call this extension? (html minify clipboard)
? And how would you call it if you only had 12 characters (short_name)? (html minify)
? How would you like to describe this extension? (Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusm
od tempor incididunt ut labore et dolore magna aliqua.)
拡張機能名や説明など入力できますが、app/_locales/en/messages.json
で後から編集可能なので、そのままエンターキーを押して進めます。
? Would you like to use UI Action? (Use arrow keys)
> No
Browser
Page
? Would you like to override a chrome page? (Use arrow keys)
> No
Bookmarks Page
History Page
Newtab Page
今回作成した拡張機能は、選択中のテキストについて処理を行うものなのでNo
を選択してエンターキーを押します。
? Would you like more UI Features? (Press <space> to select, <a> to toggle all, <i> to invert selection)
>(*) Options Page
( ) Devtools Page
( ) Content Scripts
( ) Omnibox
オプションページから設定を変更できるようにするために Options Page
のみチェックしました。
? Would you like to use permissions? (Press <space> to select, <a> to toggle all, <i> to invert selection)
>( ) ActiveTab
( ) Alarms
( ) Bookmarks
( ) BrowsingData
( ) BrowserSettings
( ) ContextMenus
( ) ContextualIdentities
(Move up and down to reveal more choices)
パーミッションは後から指定するので何も選択しませんでした。
変更は、app/manifest.json
に {"permissions": []}
の配列内に指定します。
? Would you like to install promo images for the Chrome Web Store? (y/N)
デフォルトのN
を選択
これだけでひな型が完成します。以下のようなファイルが作成されます。
html-minify-clipboard
+ app
+ _locales
+ en
messages.json
+ images
icon-16.png
icon-128.png
+ pages
options.html
+ scripts
background.js
options.js
+ styles
options.css
manifest.json
+ node_modules
.gitignore
package-lock.json
package.json
README.md
上記のうち、開発が必要なファイルを紹介します。
app/_locales/en/messages.json
拡張機能名など編集できます。公開するときに書き換えましょう。
app/pages/options.html
オプションページを設定します。
app/scripts/background.js
app/scripts/options.js
拡張機能で使用するスクリプトです。
app/scripts
に js
ファイルを格納しておくと、webpack
によりビルドされ自動的にコピーされます。
background.js
に拡張機能で動かしたいコードを記述します。
options.js
にはオプションページ内で動かしたいコードを記述します。
app/manifest.json
拡張機能で使用するリソースやパーミッションを編集します。
開発
このひな型を元に開発していきます。HTML Minify Clipboardは
- 選択中の文字列の上で右クリックしてメニューを開く
- メニュー内の
Minify the selected HTML codes
をクリックする - 選択中の文字列をHTML Minifierを使って圧縮する
- 処理した文字列をクリップボードにコピーして
done
と表示されるアラートを出す
ということを行っています。
これらをどのように実装しているか説明していきます。
まずapp/script/background.js
で選択中の文字列の上で表示するメニューにMinify the selected HTML codes
項目を出すようにします。
browser.contextMenus.create({
title : "Minify the selected HTML codes",
type : "normal",
contexts : ["selection"],
onclick : function(){
saveToClipboard();
}
});
browser.contextMenus.create
でメニューに項目を追加できます。
contexts : ["selection"]
で文字列が選択中の時に表示できるようにしています。
onclick
でクリックしたときに saveToClipboard()
を呼び出すようにしています。
async function saveToClipboard() {
const tabId = browser.tabs.getCurrent().id;
let target = "failed", selected = "", results, code;
try {
code = "typeof window.minify === 'function';";
results = await browser.tabs.executeScript(tabId, {code});
if (!results || results[0] !== true) {
const file = 'scripts/htmlminifier.js';
await browser.tabs.executeScript(tabId, {file});
}
code = `window.getSelection().toString();`;
results = await browser.tabs.executeScript(tabId, {code});
selected = results[0];
results = await browser.storage.sync.get(defaults);
let params = "{";
for(const label of Object.keys(results)) {
params += "\""+label+"\":"+results[label].toString()+",";
}
params += "}";
selected = selected.replace(/\`/mg, "\\`");
code = "window.minify(`"+selected+"`,"+params+");"
results = await browser.tabs.executeScript(tabId, {code});
if (results && results[0] && results[0] !== true) {
target = results[0];
}
execCommandCopy(target);
code = "alert(\"done\");";
browser.tabs.executeScript(tabId, {code});
} catch(error) {
console.error('Failed to copy text: ' + error);
execCommandCopy(target);
code = "alert(\""+target+"\");";
browser.tabs.executeScript(tabId, {code});
}
}
browser.tabs.executeScript
でWebページ上でJavaScriptを実行できます。
例えば
code = `window.getSelection().toString();`;
results = await browser.tabs.executeScript(tabId, {code});
selected = results[0];
で、Webページ内の選択中の文字列を取得することができます。
外部のJSライブラリは
const file = 'scripts/htmlminifier.js';
await browser.tabs.executeScript(tabId, {file});
あらかじめapp/scripts/htmlminifier.js
をコピーしておき、上記のように呼び出すことができます。
webpack でビルドされるので、async/await
が使えてコードがすっきり書けました。
オプションページは以下のように作成しました。
<!DOCTYPE html>
<html>
<head><title>Options</title></head>
<body>
<hr>
<div id="options"></div>
<div id="status"></div>
<button id="save">Save</button>
<script src="../scripts/options.js"></script>
</body>
</html>
<div id="options"></div>
にapp/scripts/options.js
のコードで設定項目を追加して<button id="save">Save</button>
で設定を保存します。
function restore_options() {
var defaults = {};
for (var label of Object.keys(options)) {
defaults[label] = options[label].default;
}
var browser = browser || chrome;
browser.storage.sync.get(defaults, function(items) {
var div = document.getElementById('options');
for (var label of Object.keys(options)) {
var input = document.createElement("input");
input.type = "checkbox";
input.id = label
input.checked = items[label];
div.appendChild(input);
var elem = document.createElement("label");
elem.innerHTML = "<b>"+label+"</b>";
div.appendChild(elem);
div.appendChild(document.createElement("br"));
var span = document.createElement("span");
span.innerHTML = options[label].description;
div.appendChild(span);
var hr = document.createElement("hr");
div.appendChild(hr);
}
});
}
function save_options() {
var opts = {}
for (var label of Object.keys(options)) {
opts[label] = document.getElementById(label).checked;
}
var browser = browser || chrome;
browser.storage.sync.set(opts, function() {
var status = document.getElementById('status');
status.textContent = 'Options saved.';
setTimeout(function() {
status.textContent = '';
}, 750);
});
}
document.addEventListener('DOMContentLoaded', restore_options);
document.getElementById('save').addEventListener('click', save_options);
restore_options()
で div#options
に設定項目をロードします。
saveボタンを押すとsave_options()
が呼ばれます。
これまでのコードを動かすにはapp/manifest.json
にパーミッションを設定する必要があります。
{
"name": "__MSG_appName__",
"short_name": "__MSG_appShortName__",
"description": "__MSG_appDescription__",
...
"permissions": [
"clipboardRead",
"clipboardWrite",
"activeTab",
"storage",
"contextMenus",
"tabs",
"<all_urls>"
]
}
大体このような感じで実装しています。
実際のコードは以下にあります。上記では一部省略しているコードもあるので、詳しくは以下を参照してください。
https://github.com/uedayou/html-minify-clipboard
ビルド
実装できたら後はビルドするだけです。
Chrome拡張機能用ビルドコマンド
$ npm run build chrome
Firefoxアドオン用ビルドコマンド
$ npm run build firefox
ビルドするとdist
ディレクトリにchrome
やfirefox
ディレクトリが、また、packages
ディレクトリに以下のような zip ファイルが作成されます。
html-minify-clipboard.v1.0.1.chrome.zip
動作確認
Chromeで動作確認をしてみます。
Chrome でchrome://extensionsを開きます。
ディベロッパーモード
を有効にすると、パッケージ化されていない拡張機能を読み込む
というボタンが表示されるので、それをクリックします。
先ほどビルドしてできたchrome
ディレクトリを指定すると、拡張機能がChrome上に読み込まれると思います。
これで、通常の拡張機能と同じように動かすことができます。
拡張機能のサンプルコード
この記事では、選択中のテキストを処理する拡張機能の作り方を紹介しました。
そのほかのさまざまな機能のサンプルコードは以下のリポジトリが充実しているので参考にしてみてください。
Generator: WebExtensionでひな型を作って、サンプルコードをいろいろ組み合わせるだけでも簡単に様々なことができると思います。
webextensions-examples
https://github.com/mdn/webextensions-examples
Discussion