JavaScript中級者がChrome拡張を作る実況

# はじめに
本記事は，自称JavaScript中級者の私が初めてのChrome拡張を作る模様を実況したものとなります．実況なので，細かい解説は割と省いています．JavaScriptの基本的な文法を抑えてる人が「大体こんな流れでChrome拡張を作るんだ〜」ってわかるような，一歩踏み出すきっかけになればいいなと思います．
なお，本記事は実際に自分が作りながら平行して執筆しているため，話し言葉多め，リアリティが増し増しになっているかもシレマセン．ご容赦を．

# Step0. 何を作るか決める
当然，何を作るかはっきりしなければ作るものも作れません．先へ進めません．
ここでは，「最近マーケティング等の分野の記事で~~多用~~乱用されているカタカナ言葉(横文字)をどうにかする拡張機能」を作ろうと思います．
~~イケてる感を出したいのか，「日本語でええやんけ！」って単語までカタカナになっていると，まぁ読みにくいこと．それか素直に全部英語で書いてくれ．「エビデンス」とか「根拠」でええやん．~~おっと愚痴がすべりました．
以前から構想はあったのですが，友人からも「それ欲しい」と言われたので重い腰を上げて作ることにしました．

# Step1. 作り方の概要を知る
いざ「よっしゃ拡張機能作るぞ！」となっても，作り方など一切知りません．そこで「chrome 拡張機能 作り方」とググって一番上に出てきた記事を読みます．私の場合は↓コチラが出てきました．先人の知恵はすばらしい．ありがたく読ませて頂きます．
https://qiita.com/RyBB/items/32b2a7b879f21b3edefc
こちらによれば，今回必要なものは

* JavaScriptファイル(実際に動作するプログラムを書く)
* manifest.json(拡張機能全般に関する情報を書く)
* 拡張機能のアイコン画像

の3つになりそうです．(他にもHTML，CSSなど，Webページの表示で一般的に使われているファイルも使えるようですが，今回は不要と判断しました．)
JavaScriptファイルは，今あるプログラミングの知識を総動員すれば多分作れるし，manifest.jsonは https://qiita.com/mdstoy/items/9866544e37987337dc79 を読んでなんとなくわかった気がしたし．あれ…？思ってたより簡単じゃね…？

# Step2. JavaScriptファイルを作る
manifest.jsonは後回しにして，とりあえず動作する部分を作ろうと思います．
## Step2-1 動作を設計する
まずはどういう仕組みでカタカナ言葉を変換するか考えます．パッと思いついたのは，以下のような実装です．

1. カタカナ言葉を変換する辞書(連想配列)を作っておく．
2. 読み込んだhtmlの中からカタカナの単語を見つける．ここで，カタカナの単語とは「カタカナで始まり，カタカナ以外の文字が現れるまでの文字列」とします．正規表現が使えそうですね．2つ以上のカタカナの単語が連続していたりすると取り逃しが出そうですが，まあそれくらいは許容範囲としましょう．
3. 2.で見つけたカタカナの単語が辞書内にあれば置き換える．

もうちょっと高速で，良い実装もありそうですが，とりあえずこれを採用しようと思います．辞書作りが大変そうだな〜と思いましたが，後押ししてくれた友人がノリノリで協力してくれるようなので，ありがたく力を借りることにします．

## Step2-2 設計通りにJavaScriptを書く
まず，変換用の辞書について．これはjsにベタ書きでいいでしょう．

```JavaScript
const katakanaEliminateDictionary = {
    "エビデンス": "根拠",
    "サクセス": "成功",
    ...
}
```

うんうん，カタカナ言葉を撲滅しようという意気が伝わってくる命名ですね．

次に，「カタカナの単語を全て見つけ，辞書内にあれば置き換える」という部分ですね．html内のタグを再帰的に探索し，innerTextに正規表現や```String.replace(target, alternative)```を用いることで実装しようと思います．
ここの実装でちょっと躓いて，1時間くらいかかってしまいましたが，無事に動作するようになって安心しました．ポイントは，再帰探索&置換で帰りがけ探索を用いることで，葉要素から置換を行うことのようです．

ともあれ，書いたコードをブラウザのコンソールに貼り付けることで，辞書内の単語を撲滅することに成功しました．本当は適当なカタカナ言葉乱用サイトでの動作例を載せたいところですが，サイト作成者さんに怒られそうなので自重しておきます．

# Step3 manifest.jsonを作る
基本的な事項はStep1でも紹介したhttps://qiita.com/mdstoy/items/9866544e37987337dc79 を参考に．
ブラウザ上で右クリックした時のメニューからカタカナ言葉撲滅関数を実行できるといいなと思い調べてみたところ，↓のサイトが参考になったので，こちらの記述も参考にJSファイルを書き直しつつ，manifest.jsonを書き進めます．
https://karupoimou.hatenablog.com/entry/2019/04/22/040546

しかし，これだと現在開いているタブのページのDOMの書き換えができない模様．どうやら，Content Scriptsというものが関わっているらしく，↓の記事を参考に無理やりDOMの書き換えをするようにしました．
https://mem-archive.com/2019/12/09/post-2164/

これで，**「ページ上で右クリックすると，メニュー内でカタカナ言葉撲滅関数を実行できる」**という挙動を実現できました．

# 完成形
結果，出来上がったファイルたちがコレ．chromeで[拡張機能の設定](chrome://extensions/) を開き，```パッケージ化されていない拡張機能を読み込む```から，下の例の```extension```フォルダを開くことで導入できました．
多くの記事を横断して読みながら作ったため，もしかしたら無駄な部分があるかもしれません．

```
extension/
|
├ manifest.json
├ app.js
└ wrapper.js
```

```manifest.json
{
    "manifest_version": 2,
    "name": "カタカナ言葉撲滅Chrome拡張",
    "version": "1.0.0.0",
 
    "description": "カタカナ言葉が乱用されているサイトが読みにくかったので，撲滅してくれる拡張をつくりました．",
    "icons": {
        "128": "icon128.png"
    }, 

    "author": "AuthorName",
    "background": {
      "scripts": ["app.js"]
    },
    "content_scripts":[
        {
          "matches":[
            "http://*/*",
            "https://*/*"
          ],
          "js":[
            "app.js"
          ]
        }
      ],
    "permissions" : [
        "contextMenus",
        "http://*/*",
        "https://*/*",
        "tabs"
    ],
    
    "homepage_url": "http://path/to/homepage",
    "offline_enabled": true
  }
```

```app.js
const katakanaEliminatorDictionary = {
    "エビデンス": "根拠",
    "サクセス": "成功",
    ...
}
const katakanaEliminatorKeys = Object.keys(katakanaEliminatorDictionary);
const targetPattern = "[\u30A1-\u30F6ー]+"; //全角カタカナと"ー"の正規表現
let htmlElement = document.documentElement;

function replacePattern(element){
    //子要素に対して帰りがけ探索的に再帰呼び出し
    for(let i = 0; i < element.children.length; i++){
        replacePattern(element.children[i]);
    }
    
    //innerText内のカタカナの単語をすべて検索
    //カタカナがなくなるまで繰り返す
    
    //辞書内に無い単語で無限ループしないために，どこまで見たのか記憶する
    let searchedIndex = 0;
    console.log("replacing...");
    while (true) {
        let katakanaWords = element.innerText.substr(searchedIndex).match(targetPattern);
        if(katakanaWords){
            console.log(katakanaWords[0]);
            if (katakanaEliminatorKeys.includes(katakanaWords[0])){
                element.innerText = element.innerText.replace(
                    katakanaWords[0],
                    katakanaEliminatorDictionary[katakanaWords[0]]);
            }
            searchedIndex += katakanaWords.index + katakanaWords[0].length
        } else {
            break;
        }
    }
    return;
}

function funcWrapper(){
    replacePattern(htmlElement);
}
chrome.contextMenus.create({
    "title": "カタカナ言葉を撲滅する",
    "type": "normal",
    "onclick": function(){
        chrome.tabs.executeScript(null, { file: './wrapper.js' }, () => {});
    }
});
```

```wrapper.js
funcWrapper(); //コレだけでOK
```

# あとがたり
ここまでおつきあいくださりありがとうございました．Step2で1時間半，Step3では結構苦戦して2時間半くらいかかってしましましたが，どうにか休日の半日で完成させることができてホッとしております．
JavaScriptを知っていたので実装自体はあまり苦労しませんでしたが，これを拡張機能として動かすところではやはり分からないことが多くて苦労しました．
これで，意味わからないカタカナ言葉ばかりのサイトにというイシューに対するソリューションをドラスティックにリリースできてサティスファイドになりました．