詩と創作・思索のひろば

ドキドキギュンギュンダイアリーです!!!

Fork me on GitHub

DevTools上でesbuildするChrome拡張を書いた

社内ではドキュメントの共有に Scrapbox が活発に使われており、するといきおい UserCSSUserScript もさかんである。具体的には、/customize という共有のプロジェクトがあってみんなの自慢の装飾やカスタマイズが共有されている。これを個々人で import して使うんである。

こんな感じ。

f:id:motemen:20220225232835p:plain:w400

自分の場合は /motemen/UserCSS/common に常に適用したいスタイルを書いておいて各プロジェクトから読み込んでいる。このページからさらに、共有プロジェクトや他人の個人プロジェクトページからよさそうな設定を import している次第。

つまりは多段インポート。こういうことを続けていると、だんだんと読み込みの遅さが気になってくる。こういうのはバンドルすればいいのだけど、巷のツールを普通に使うことはできない。インポートしてるリソースに認証がかかっているからだ。

とするとブラウザの上でバンドラを動かしたくなる。認証情報の利用やオリジン越えが発生するので、今回は拡張で実装することにした。

バンドラには esbuild を利用する。別に何でもいいのだけど、WebAssembly で動かす API があるというので触ってみたかった。

chrome-extension-esbuild

GitHub - motemen/chrome-extension-esbuild

この拡張は DevTools にパネルを一枚追加する。バンドルしたい JS や CSS を開いてインスペクタを開き、esbuild タブから Build ボタンを押せばリソースを解決してバンドルしてくれる。

Screencast

URL.createObjectURL() で生成した URL は Chrome だと名前を付けて保存できなかったので、data: URL を作っている。長さに制限があるかもしれない。

Manifest V3 で WebAssembly を使う

Chrome 拡張はそろそろ Manifest V3 というやつに移行するようアナウンスされているのでこれを使う。

通常の popup 内で WebAssembly を使うことはできず、sandbox 内で呼び出す必要があるっぽい。manifest.json にこういう指定をする必要があった。

  "content_security_policy": {
    "sandbox": "sandbox allow-scripts; script-src 'self' 'unsafe-eval'; worker-src blob:"
  },

esbuild のプラグインを書く

あとは普通に sandbox 内で esbuild の API を呼び出せばいい。import される JS や CSS を fetch() 経由で読み込むためプラグインを定義するが、これはドキュメントにある HTTP Plugin がほぼそのまま使えた。認証情報を利用した fetch は sandbox 内で行えないので親フレームに依頼する。

import した先でさらに別の import がなされていることもあるので、URL を resolveDir に含めておき、その後の相対パスの解決に利用している。

ログは content script で吐いている

最初、パネル内にログを表示しようとしていたのだけど見た目を作るのが面倒になってしまった。考えたら DevTools を開いてるんだからそこに流したらいいじゃん、ということで content script 経由で console.log() することにした。DevTools を開いたときだけスクリプトを注入するなどしたほうがいいのかもしれない。このへんは secretlint/webextension を参考にしている。

SlackやGoogle Docsにページへのリンクを共有するなら圧倒的にcocopyが楽

Scrapbox のような Wiki 的なツールでは URL にページ名が入ることが多く、URL を見るだけでどんな内容なのか想像がついてよい一方で、こういう URL を Slack や Google Docs のような別の場所に共有するとパーセントエンコーディングされた URL になってしまい意味がわからなくなる。日本語を書いていることだけが分かる状態。

f:id:motemen:20220217190426p:plain
マルチバイトしかないと本当にわからないね

Slack がアクセスできない URL だと、プレビューも展開してくれないしね。かといってデコードした状態の URL を貼っても、変なところで途切れたりする。

f:id:motemen:20220217190239p:plain
・(中黒)でリンクが途切れている

文字は難しい……。URL の解釈はものによって異なってくるのもまた困る。これはプレーンな文字列を渡しているのでこういう困難が出てくるのであって、最近はクリップボードでリッチなコンテンツを受け渡しすることができるので、これを使ってみればいいのではないか。Slack のような最近のアプリは、Google Docs や GitHub などのウェブアプリも含めて画像やリンクの貼り付けにフツーに対応している。

Chrome からページの情報をコピーする拡張として cocopy というものがある。これに最近リッチテキストをコピーできる機能がベータとして追加されたのでこれを使ってみる。この拡張については以下の記事を参照してください。

js を書いて URL やページの内容を加工してコピーできる Chrome 拡張ココピーのご紹介 - ぽ靴な缶

機能を追加したのはこの Pull Request だ。おれの PR だ!

cocopy は JavaScript を書くことで、ページの情報からコピーするデータを生成できる。リッチテキストをコピーさせるにはこういう感じのを書く(インタフェースは変わるかもとのこと):

(page) => {
  return {
    html: render('<a href="{{&url}}">{{title}}</a>', page),
    text: page.title,
  };
}

これで現在のページタイトルをリンクテキストに、ページの URL を指すようなリンクを生成できる。これならリンクも途切れない!

実装には Clipboard.write() を使っていて、これに MIME タイプとともに Blob を与える。text/html と HTML 片を与えるとリンクなどのリッチテキストを生成できるらしい。text/plain のバージョンも一緒にセットしておかないと Slack では貼り付けできないのでそちらも一緒に生成するようにしている。

ちなみに以下のような設定にすればリッチリンクとして取り回ししつつ、プレーンテキストしか受け付けないエディタ(iA Writer など)では Markdown で貼り付けることができてさらに便利。ただ GitHub など、ものによってはリッチコンテンツとプレーンテキストを混ぜて使っているようで、これが意図通りに動かないこともある。その場合は Shift+Cmd+V などしてプレーンテキストを貼り付けるとよろしい。

(page) => {
  return {
    html: render('<a href="{{&url}}">{{title}}</a>', page),
    text: render('[{{&title}}]({{&url}})', page),
  };
}

早朝の薄氷をウェブで割る

薄氷をぴしぴし踏んで老詩人 ―― 中村苑子

すでに立春も過ぎましたが、まだまだ寒い日も多く春が待ち遠しい日々です。朝がた家の外に出ると、道ばたの水たまりに薄く氷が張っており、前の晩の寒さが思いやられつつ、ひび割れた様子を見ては、すでに登校している生徒たちがここを通ったことにも気づきます。

そんな気持ちを体験できるページを作りました。その名もウェブ薄氷(うすらい)。

ウェブ薄氷

早朝に氷が張り、最初の一人だけがこれを割れます。残念ながら昼になると溶けてしまいますが。


実装はドメインからも分かるとおり Cloudflare Workers。氷を割った時刻を KV に保存している。 最初はただひとつの状態だけを持つ実装にしていたけど、やっていたら request.cf という特別なオブジェクトから クライアントのアクセス元の情報も 国・地域・都市のレベルでそれぞれ取れるようだったので、その粒度で状態を共有するようにした。名前をキーにしているから変なバッティングもあるけどそれも味ということで。エッジごとに独立したキャッシュを持ってたら面白いと思ったけど、Workers ではそういうキャッシュは使えなそう? だったので見送り。

Cloudflare Workers は初めて使ってみたけど wrangler の体験がよくて大変やりやすかった。ふつうの JavaScript における開発体験と地続きにちょっとしたものが書けるのはいい。テストのことを考えると miniflare を最初から入れておいたほうがよいと思うが、このへんは現在開発中の wrangler 2 で統合されるらしい。あらかた開発してから気づいた。あとコールドスタートがないのもいいね。こういうちょっとしたものでいちいち待たされるのはよくないだろうから。

そして一番大変だったのは JavaScript でタイムゾーンを気にした時刻の取り扱いをすることだった……。

GitHub - motemen/webusurai

はてなで一緒に働きませんか?