GoでChrome拡張を作りました!
🤔ん?と思った方もいらっしゃると思いますが、正確にいうと
Go+ WebAssemblyでChrome拡張を作りました!
お気付きの方、そうです。wasmを使いました。
作ったもの
どういうものを作ったかというと
の記事に載ってる選択したGoのコードをThe Go Playgroundで簡単に実行できるChrome拡張に
Goのフォーマット機能を追加しました!!
Demo
追加機能
-
選択したコードのフォーマットが崩れていたらフォーマットしてくれる
-
選択ミスした(Goとしての構文が崩れている)場合、教えてくれる(コンテキストメニュー内にエラーメッセージを出してくれる)
GitHubはこちら
ダウンロードはこちら
Go + WebAssemblyとは
-
Go1.11から実験的に導入された
-
syscall/jsパッケージでJavaScript側の操作も可能
-
GOOS=js GOARCH=wasm
でgo build
すると.wasm
ファイルが生成される -
テストも
GOOS=js GOARCH=wasm go test -exec="$(go env GOROOT)/misc/wasm/go_js_wasm_exec"
のようにexec
で実行環境を渡してあげれば可能
詳しくはこちらを
構造
ディレクトリ構造
.
├── background
│ ├── background.js // 新しいタブでPlaygroundを開く
│ ├── formatter.go // フォーマットする関数を生成・登録
│ ├── init_go.js // Goのインスタンス生成
│ └── wasm_exec.js // Go + wasmを実行
├── contents
│ └── contents.js // 選択した文字列をChromeAPIでbackground.jsに送る
└── manifest.json
ソースコード
contents
document.addEventListener("selectionchange", () => {
chrome.runtime.sendMessage({ code: window.getSelection().toString() });
});
background.js
に選択した文字列をChromeAPIで送ります
background
package main
import (
"go/format"
"syscall/js"
)
type error interface {
Error() string
}
// 与えられた文字列をフォーマットする関数
func formatter(this js.Value, args []js.Value) interface{} {
code := args[0].String()
newCode, err := format.Source([]byte(code))
if err != nil {
return map[string]interface{}{"output": err.Error(), "err": true}
}
return map[string]interface{}{"output": string(newCode), "err": false}
}
// jsのプロパティとして登録
func registerCallback() {
js.Global().Set("formatter", js.FuncOf(formatter))
}
func main() {
c := make(chan struct{}, 0)
registerCallback()
<-c // ブロックしてmain関数を終わらせないようにしている
}
Goのコードをフォーマットする関数です。syscall/js
を用いて関数を定義しています。go-wasmでは、main関数を終了させないためにあえてチャネルでブロックさせます。main関数が終了してしまうと、せっかくjsに登録した関数も呼べなくなってしまいます。
const id = "clip_go";
const title = "Clip Go";
const type = "normal";
const contexts = ["selection"];
let code;
chrome.runtime.onMessage.addListener(message => {
// フォーマット
const result = formatter(message.code);
// フォーマット結果からコンテキストメニューを更新
if (result.err) {
chrome.contextMenus.update(id, {
title: `${title} [Error] ${result.output}`,
enabled: false
});
} else {
code = result.output;
chrome.contextMenus.update(id, {
title: title,
enabled: true
});
}
});
// コンテキストメニューにClip Goを定義
chrome.contextMenus.create({
id: id,
title: title,
type: type,
contexts: contexts
});
// Clip Goがクリックされると新しいタブでThe Go Playgroundを表示
chrome.contextMenus.onClicked.addListener(() => {
chrome.tabs.create({ url: "https://play.golang.org/" }, tab => {
chrome.tabs.executeScript(tab.id, {
code: `document.getElementById("code").value = \`${code}\`;document.getElementById("run").click();`
});
});
});
background.js
は以下の役割を担っています。
- コンテキストメニューに
Clip Go
を登録 -
contents.js
から送られてきた文字列をGoで作った関数に渡して、フォーマット結果からコンテキストメニューの状態を更新 -
Clip Go
がクリックされると、新しいタブでThe Go Playgroundを開き、フォーマットした文字列を貼って実行する
const go = new Go();
WebAssembly.instantiateStreaming(fetch("formatter.wasm"), go.importObject).then(
result => {
go.run(result.instance);
}
);
Goのインスタンスを生成
インスタンス化するためにはwasm_exec.jsが必要となります。
manifest
{
"manifest_version": 2,
"name": "Clip Go",
"author": "ramenjuniti",
"description": "This is a chrome extension for running a selected golang code in The Go Playground.",
"version": "1.1.0",
"content_scripts": [
{
"matches": ["<all_urls>"],
"js": ["contents.bundle.js"]
}
],
"background": {
"scripts": ["background.bundle.js"],
"persistent": false
},
"permissions": [
"activeTab",
"contextMenus",
"tabs",
"https://play.golang.org/"
],
"content_security_policy": "script-src 'self' 'wasm-eval'; object-src 'self'",
"icons": {
"16": "icon16.png",
"32": "icon32.png",
"48": "icon48.png",
"128": "icon128.png"
}
}
wasmを利用する場合manifest.json
に
"content_security_policy": "script-src 'self' 'wasm-eval'; object-src 'self'"
を追加しないといけないみたい
その他のコードはこちらをhttps://github.com/ramenjuniti/clipgo
つらみ
formatter.wasm
のサイズが3.7MBで、すごくでかくなってしまった。
Go + wasmではここらへん問題はまだまだあるみたい
まとめ
-
GoでChrome拡張は作れる
-
Goでしかできないことをブラウザに持ち込める
-
まだまだ課題もある
みなさんも是非、GoでChrome拡張を作ってみてはいかがでしょうか
Clip Goぜひ使ってみて下さい!!