DATE : 2021/05/07 (Fri)
はじめに
5/4に、ブラウザのタブを前や次の空白タブに移動するFirefox拡張「Go to empty tab」を公開しました。ソースコード一式は、GitHubに公開しています。
本記事では、開発の振り返りとして、公開までの経緯を書いていきます。具体的には、以下のことについて記載します。
- 開発の動機
- 開発期間
- これまでの経験
- 採用した言語、ツール、フレームワーク
- 開発の記録
開発の動機
私はFirefoxを主に使用しています。
Firefoxはタブを大量に開いても軽いので、タブをつい開きすぎてしまいます。新しいWebページを開くときもタブを閉じるのが面倒なので、とりあえず空白タブを開いて、タブ間に区切りを入れて新しいタブを開いています。
タブ間の区切りとして空白タブを入れている関係上、閲覧のためにも、タブの整理のためにも、いま開いているタブから空白タブに瞬時に切り替えることができれば便利だと思いました。
そのような拡張機能がないか探してみましたが、検索には引っかかりませんでした。そこで、ないなら作るの精神のもと、拡張機能の開発を思い立ちました。
開発期間
開発を思い立ったのは、2021/2/28のことでした。
しかし、私生活が忙しかったり、他のことを優先していた結果、プロジェクトのディレクトリを作ったのは4/4になりました。
ですので、実質1ヶ月程度で開発を行ったことになります。もっとも、コードの大部分は、最後の数日の間に仕上げました。
これまでの経験
拡張機能の開発にはWeb技術、つまりHTML、CSS、JavaScriptを用いることはすでに知っていました。
しかし、それらをある程度書いたのは2015年ごろが最後で、ここ最近はJavaやC++、Pythonばかり書いていました。つまり、Web技術からはすっかり離れていて、浦島太郎状態だったのです。当時は、ECMAScript 2015で導入された機能や構文を使っておらず、「JavaScript: The Good Parts ―『良いパーツ』によるベストプラクティス」などの本を読んでコードを書いていました。
ですので、まずはECMAScript 2015以降のJavaScriptについて調査するところから始めました。
ECMAScript 2015では、糖衣構文ではあるものの、クラスが定義できるようになっています。また、モジュールシステムも整備されています。ただ、モジュールシステムについては、CommonJSなどを使用した、モジュールシステムが言語機能に導入される前の仕組みを解説した情報もあり、少々混乱しました。
また、ECMAScript 2015ではPromiseが、ECMAScript 2017ではasync/await構文が導入され、非同期処理が簡潔に書けるようになっています。以前に書いたコードでは、コールバックからさらにコールバックを呼ぶことでコードの可読性が低下する、コールバック地獄になっていたことを思い出しました。以前、JavaでRxJavaを用いたリアクティブプログラミングを試したこともあったため、RxJSの採用も考えましたが、今回は採用を見送りました。そこまで高度なイベント処理は必要なく、async/await構文でじゅうぶん簡潔に記述できると予想できたためでした。
ビルドツールも充実しています。当時の私は、JavaScriptはブラウザ上でコンパイルせずに動作することもあり、JavaScriptファイルに対してビルドツールを使うという発想はありませんでした。昨今のビルドツールでは、JavaScriptファイルをひとつに結合したり、minifyなどの最適化を施すことができたりなど、大幅に進化しています。ここまで来ると、コンパイラ言語と変わらない部分もあるように思いました。
採用した言語、ツール、フレームワーク
よく分からない分野については、まずは長い物には巻かれよ、ということで、「State of JavaScript 2020」の利用度を基本に、あとは好みで決めました。
Node.js/npm
Node.jsのLTS版である14系をインストールしました。何はともあれ、Web技術を用いた開発は、最近はこれがないと始まらない印象を受けました。
web-ext
拡張機能の開発を行うには、web-extが必要です。このツールで、拡張機能の実行やビルドを実行します。
webpack
開発中のタスクを自動化する観点から、ビルドツールを探したところ、webpack(https://webpack.js.org/)を採用しました。複数のファイルをひとつに結合したり、minifyなどの最適化を行うのであれば、個々の作業を行うコマンドを手打ちで実行するよりも、ビルドツールのコマンドひとつで実行できたほうが便利だからです。
また、web-extのプラグインもインストールしました。
TypeScript
小規模な拡張機能なので、JavaScriptを用いて実装しても良いと初めは考えていました。しかし、どうせビルドをかけるなら、より堅牢な代替言語が使用できないか、検討を始めました。問題は後で見付かれば見付かるほど修正に時間がかかるため、ビルドの時点やコードを書いている最中に問題を検出できるなら、それに越したことはありません。
そのため、TypeScriptを採用しました。
JEST
書き捨てのスクリプトやごく小規模なツールを除いて、可能な場合はテスト駆動開発を行ってきました。最低限の品質を確保できるという利点もありますが、動作するコードが少しずつできあがっていくので、開発のモチベーションを保ちやすい点が自分にとっては大きいと思います。
そのため、ユニットテストフレームワークとしてJESTを採用しました。
ESLint
静的コード解析とスタイルチェックを同時にできるのでESLintを採用しました。
規模が小さい拡張機能ですが、実行前にバグを検出できるということ、またJavaScriptらしいコードを書きやすくなるため、採用しました。
スタイルには、「Airbnb JavaScript Style Guide」を採用しました。採用についてこだわりは特になく、ESLintの初期化時に、使用するスタイルガイドとして初めに選択されていたためです。
開発の記録
拡張機能の開発は、次のように進めていきました。
- ドキュメントを読む
- ソフトウェアライセンスを選択
- タブの情報を出力する拡張機能を作成
- テストコードを書く
- 拡張機能のコードを書く
- アイコンを作成
- AMOで公開
ドキュメントを読む
拡張機能の作り方や、タブ関係のAPIについて公式のドキュメントを調査しました。例えば、次のページが役に立ちました。
拡張機能の入門には「Your first extension」を参照しました。実際にサンプルの拡張機能を作りながら、学ぶことができました。
タブ関係のAPIについては「Working with the Tabs API」を参照しました。
TypeScriptやJEST、webpackを使用した拡張機能の例として、「Rikaichamp!」を参考にしました。
また、各種ツールのドキュメントも参照しました。
TypeScriptの学習には、「プログラミングTypeScript――スケールするJavaScriptアプリケーション開発」を読みました。JavaScriptの知識を前提として、TypeScriptに特化した記述が多めになっており、参考になりました。
ソフトウェアライセンスを選択
開発した拡張機能はOSSとして公開するつもりでしたので、今回はMITライセンスを選択しました。
これまではソースファイルのヘッダにライセンスヘッダを入れていましたが、今回はSPDXを使用しました。これで、ファイルを開いた直後の画面がライセンスヘッダに占領されることがなくなり、見通しが良くなったように思います。ただ、SPDX側としては、ライセンスヘッダがライセンスに定義されている場合は、それも入れることを推奨しています。MITライセンスでは、ライセンスヘッダは定義されていないので、SPDX short-form identifiersのみ記載しています。
タブの情報を出力する拡張機能を作成
こうして拡張機能を開発する準備は整いましたが、疑問がひとつありました。
そもそも、空白タブはどのように見付ければ良いのでしょうか。タブに紐付くURLを見れば良さそうですが、Firefox上で新規タブを開いても、アドレスバーには何も表示されません。
そこで拡張機能の開発の練習がてら、タブの情報をブラウザーコンソールに出力する拡張機能を開発しました。その結果、タブに紐付くURLが以下のいずれかの場合に、空白タブだとわかりました。
- about:blank
- about:newtab
これで、拡張機能を開発する準備が整いました。
テストコードを書く
テスト駆動開発を行うため、まずは簡単なテストコードを書きました。エディタの補完機能を利かせるために、メインコード側は中身が空の関数や型を定義し、テストコードを書きながらそのインタフェースを調整していきました。
ここで問題になるのは、Firefoxが提供するAPIをテストコード上で扱う方法です。例えば、タブ関連のAPIや、DOMがそれに当たります。
今回は、その部分はテストコードの対象外としました。もちろん、モックオブジェクトなどの方法でそれらのコードをシミュレートすることはできます。しかし、テストコードを書くほど時間的コストをかける必要はないと判断しました。その部分と、切り替え先の空白タブを見付ける処理は明確に分離することができ、またFirefoxが提供するAPIとの接合部分はごくシンプルなものだったためです。
結果、切り替え先の空白タブを見付ける処理についてのみテストコードを書くことにしました。今後、Firefoxが提供するAPIとの接合部分が複雑になれば、今回は対象外としたその部分についてもテストコードを書くかもしれません。
拡張機能のコードを書く
テストコードを書き足しながら、拡張機能のメインコードも書き足していきました。
JavaScriptは久しぶりで、TypeScriptは初めてだったため、簡単なロジックミスをテストコードが検出することも何度かありました。小さい拡張機能ながら、これだけでも十分にテストコードの効果があったと思います。デバッグも、修正ができたか確認するのも、テストコードを実行するコマンド一発でできました。これでテストコードがなければ、Firefox上で毎回拡張機能を操作しながら、デバッグを繰り返すことになっていたでしょう。テストコードのおかげで、時間が節約できたと思います。
拡張機能の開発者は私一人ですが、次にいつこの拡張機能を編集するかわかりません。将来の自分のためにも、モジュールからexportされる型や関数には、ドキュメントコメントを記載しました。形式は、TSDocに従いました。
ところで、拡張機能の中では、enumが適していそうな部分があります。しかし、TypeScriptのenumには問題が指摘されているため、Union Typesを用いて定義しました。
アイコンを作成
これで拡張機能は一通りできあがりました。公開するうえで、デフォルトアイコンのままでは他の拡張機能と区別が付けづらくなるため、アイコンを作成することにしました。
アイコンの形式には、SVGを選びました。複雑なアイコンを作るつもりはなかったことと、解像度に合わせてアイコンの画像ファイルを用意するのが面倒に思ったためです。
アイコンは、Google Fontsで提供されているタブのアイコンを加工して作成しました。
アイコンのライセンス確認すると、 Apache License Version 2.0で、帰属の表示は必須ではないとのことでした。
初めはDraw.ioを使用してアイコンを加工しようと思いました。しかし、画像サイズをピクセル単位で指定できませんでした。そのため、Inkscapeを使用しました。
AMOで公開
これで一通り拡張機能の公開の準備が整ったので、AMOへ登録を行いました。なお、AMOとは、アドオンが配布される公式サイトaddons.mozilla.orgのことです。
AMOでは、以下の2種類から配布方法を選択できます。
- 自主配布
- AMO上では、拡張機能を登録した自分自身のみインストールできます。
- AMO上で公開
- アドオンマネージャーから閲覧できるようになり、誰でも拡張機能をインストールできます。ただし、審査に合格する必要があります。
なお、審査のために、ソースコード一式のアップロードが必要な場合があります。minifyなどの最適化や、webpackなどを使用してファイルを結合した場合は、必要となります。また、アップロードする場合、READMEファイルか、別途表示されるページに、拡張機能をビルドする方法やテストする方法を記載する必要があります。自主配布では不要かと思いましたが、実際には登録を求める内容が表示されました。
今回はwebpackを使用してファイルの結合とminifyを行っているため、ソースコード一式のアップロードを行いました。
まずは、自主配布の方法を試しました。バージョン0.0.1は、このために登録しました。これで実際に、AMO上からアドオンをインストールして正常に動作するか確認できました。
5/4の17時頃に、公開のためにバージョン0.0.2をアップロードしました。審査待ちの状態となり、57個中57個と、拡張機能の審査を待機する列の長さが表示されました。
そして、21時頃に審査に合格し、拡張機能が公開されました。