Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationEventAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
7
Help us understand the problem. What is going on with this article?

More than 1 year has passed since last update.

@ramenjuniti

GoでChrome拡張を作った話

GoでChrome拡張を作りました!

🤔ん?と思った方もいらっしゃると思いますが、正確にいうと

Go+ WebAssemblyでChrome拡張を作りました!

お気付きの方、そうです。wasmを使いました。

作ったもの

どういうものを作ったかというと 

:point_up:の記事に載ってる選択したGoのコードをThe Go Playgroundで簡単に実行できるChrome拡張に
Goのフォーマット機能を追加しました!!

Demo

こんな感じで動きます
clipgo_demo1.1.0min.gif

追加機能

  • 選択したコードのフォーマットが崩れていたらフォーマットしてくれる

  • 選択ミスした(Goとしての構文が崩れている)場合、教えてくれる(コンテキストメニュー内にエラーメッセージを出してくれる)

GitHubはこちら

ダウンロードはこちら

Go + WebAssemblyとは

  • Go1.11から実験的に導入された

  • syscall/jsパッケージでJavaScript側の操作も可能

  • GOOS=js GOARCH=wasmgo 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

全体の流れ
clipgo_flow.png
簡単な図はこんな感じです

ソースコード

contents

contents.js
document.addEventListener("selectionchange", () => {
  chrome.runtime.sendMessage({ code: window.getSelection().toString() });
});

background.jsに選択した文字列をChromeAPIで送ります

background

formatter.go
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に登録した関数も呼べなくなってしまいます。

background.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を開き、フォーマットした文字列を貼って実行する
init.go
const go = new Go();

WebAssembly.instantiateStreaming(fetch("formatter.wasm"), go.importObject).then(
  result => {
    go.run(result.instance);
  }
);

Goのインスタンスを生成

インスタンス化するためにはwasm_exec.jsが必要となります。

manifest

manifest.json
{
  "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ぜひ使ってみて下さい!!

7
Help us understand the problem. What is going on with this article?
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
7
Help us understand the problem. What is going on with this article?