LT:UserScriptを支える技術
タイトルは最終的な内容によって変える
とおもったが、情報の弁当箱は好きじゃないtakker.icon やるならページを分けるだろうなあ
書き進まないなーtakker.icon
井戸端に載せて、いろいろ聞くか
06:49:03 といいつつ、1時間使ってたくさん書いてしまった……
自己紹介
takker.icontakker
2020-07くらいからscrapboxを使い始めた
UserScriptをたくさん作っているらしい
LT枠はもう埋まってしまいましたが、飛び入りLT枠があるとのことなので、あわよくば発表してみたいという思いから資料を作ってみました
最近作ったもの
scrapboxのUserScriptについて
よく使う方法
開発環境
今まで作ってきたもの
など
原点
/yutaro/yutaro.iconさんが作成した、絵文字の入力補完script
自分で読んで理解したい!
Firefoxでは動かなかったので、Firefoxでも動くように改造する必要があったという事情もある
UserScriptで使う技術
基本的に、JavaScriptで実装できるものならなんでもできる
Reactを読み込んで、scrapbox上で自前のエディタを動かすことも技術的には可能
ここでは、身近な手法から紹介していく
PageMenuとPopupMenu
PageMenu
動的にtitleを変える機能がなくなってしまった
復活してほしい
PopupMenu
動的にボタンを切り替える
最近、mobile版でもPopupMenuが出るようになった
便利takker.icon*2
意外と知らない関数が生えていたりする
PageMenuのaddItemのonClickでMouseEventを取得できるのが好きtakker.icon
まあバグだと思うけど……
dom操作
このあたりから、複雑な機能も作れるようになってくる
よく使うDOMへのショートカット
文字の位置検知
.c-から取得できる
リンクや数式の取得
.cursor-lineの状態でDOM構造が変わるため、すべてのケースに対応できるよう組むさいは工夫が必要
文字入力
rest api
wrapperを作った
wrapperを作った理由
URLの組立やレスポンスの処理が特殊なAPIがあるため
そのまま使うのは面倒
TypeScriptで型付けしたかった
もちろん、あくまで内部APIなので、いつ仕様が変わってもおかしくない
preact
複雑なDOM操作が増えてきたら、これを使う
reactはファイルサイズが無駄に大きいので、preactを使っている
TODO:ここ要確認
scrapbox本体もReactではなくPreactにすればいいのになーと日頃から思っているtakker.icon
まあpackageの依存関係上無理なんだろうけど
CSSをカプセル化するのとidのダブりを防ぐのが一応目的
意味のあることかは不明
逆にUserCSSでスタイルを変えられないなどのデメリットがある
まあただの好みである
web socket
ここから上級者向け
任意のprojectの任意のページのCRUDが可能になる
scrapbox.ioのweb socketの通信内容を調べて、模倣した
途中でsocket.ioを使っていることに気づいてからは、CDNで導入したsocket.ioを使うようにした
黒魔術
Reactで管理しているDOMには、__ReactFiberなどReactが生やした内部データがある
とれるもの
カーソル操作
選択範囲操作
これを自前で取得するのは地獄
カーソル位置や選択範囲の変更時に発火するlistenerを登録できる
これが一番便利
選択範囲内の文字列は、PCからなら#text-inputから取得できるが、mobileだと取得できない
自分で実装しようとすると非常に大変
当たり判定の計算
太字記法などで文字の大きさが変わると、位置計算が
カーソル移動の検知方法
カーソルにfocusがあるかどうかでDOMの状態が変わるので、一筋縄では行かない
かなり不安定なハック
いつscrapbox側で仕様が変わるかわからない
おそらくReact Hooksで書き換えられたら死ぬ
いつ消えるかわからないhackに依存するのはよくないのだが……メインのUserScriptで深く依存してしまっている
仕様変わって使えなくなったら発狂する自信がある
以上の手法を一つのrepoにしてgit管理している
Denoから使える
node向けpackageは作っていない。
作る予定も特にない
外部ライブラリを使う
CSPで使えるものが限られている
script-src
classic scriptで読み込めるもの
cdnjsやlocation記法に使うgoogleのコードなども読める ESModuleで読み込めるもの
scrapbox.ioのみ
ここでcdnjsも使えるようになれば、globalに変数を露出させずに済むのだが……
worker-src
scrapbox.ioのみ
connect-src
scrapbox.io, storage.google.com, cdnjs, upload.gyazo.comなど
使う方法
CSPで許可されているものを<script>から読み込む
コードブロック記法に全部張り付ける
Preactなど小さめのライブラリは直接張り付けられる
katexやsocket.ioなど数百KBあるライブラリは、CDN経由で読み込むのが無難
bundleする
esbuildはnode.js型のmodule解決しかできないので、そのままでは使えない
$ deno bundle <URL> | esbuild --minify
その後、いろいろ設定してbundleしたくなったので、esbuildにpluginを入れたDeno scriptを作った
いろいろ設定
一部のURL(画像URLやprivate project内のソースコード)をbundleからはずす
sourcemapをつける
どんなに長いコードでもscrapboxに一発で取り込めて便利
web browser上で動くbundler
リンクを踏むだけで実行できるのが最大の特徴
スマホでも動く!
Deno scriptはスマホだと動かせない
これが不満だったtakker.icon
スマホからUserScriptを更新できるようになった
UIが雑だったりバグが残っていたりするが、普段使っている範囲で問題は起きていないので、放置している
これも不便に慣れてしまうということなのだろうなあ
コードブロックへの張り付け
小さいコードはそのまま張り付ければいい
大規模なUserScriptを作っていると、minifyしても一発で張り付けられないようになる
回避策
分割して張り付ける
例:ScrapBubbleの最新版(TODO: リンク貼る)は2回に分けて貼り付ける
より巨大になると張り付けが大変
何回も張り付けなければならない
scrapboxが重くなる
最悪途中でクラッシュする
ブラウザでのrenderingを介さず、直接scrapbox.ioのDBに格納する
構文解析やrenderのコストを省けるので、1MBのコードでもいけるはず
うっかりコードを張り付けたページを開いてしまうと(お察し下さい)
閲覧者のブラウザを壊さないよう、使う際はprivate projectやbundle専用projectを作ってそちらに置くのがおすすめ
コードの張り付けテクニック
何度も張り直す場合は、undoして前回のコードを消すと楽
うっかりページ遷移してundoできなくなってしまったら、がんばって手作業で消すしかない
外部APIを使う
上述したようにCSPが厳しい
何の方法もなく使えるのはGyazoのupload APIしかない
初出はしーなるすさん
これを知ったことで、UserScriptでできることがかなり広がった
作ったもの
これら2つは、後に公式で同様のものが実装された
ただし、外部リンク記法のほうは公式実装にバグがあるので、tampermonkey実装を使った方がいい scrapboxに埋め込む
iframe-srcが厳しいため、埋め込めるものはほとんどない
そもそもscriptで埋め込んでも、UserScriptを入れている人にしか効果がない
回避策:画像で埋め込む
アスキーアートを画像にするserverless function
画像にすればだいたいなんでもできる
出来ないこと
SVG内で外部リソースを読み込む
CSSやfontファイル、画像ファイルなどは読み込めない
たとえ同ドメインであっても不可
あらかじめdata URLなので埋め込むしかない
スクロールなどの動的操作
リアルタイム更新
最速でもタブの再読込のとき
実装
vercelとdenoで作っている
deno deployも使ってみる?
突然サービスを終了しないかが心配
それなりに長く使えるところでdeployしたい
まあそんなこといったらvercelだっていつまで無料で使えるかわからないが……
不可能なこと
だとtakker.iconが考えていること
UserScriptの開発方法
最初期
直接コードブロックに書き込んでいた
自分のページのscript.jsからimportする
書き込みとリロードをひたすら繰り返して試す
Hot reload?そんなものはない
開発しているUserScript以外も毎回script.jsで読み込んでいた
最初の頃はこれでもよかったが、次第にUserScriptが肥大化し、読み込みに時間がかかるようになった
開発しているUserScriptを試したいだけなのに、毎回他のUserScriptの読み込みに数十秒とられることになった
不便に慣れてしまうタイプなのか、この状態で1年以上UserScriptを作り続けていた
さすがに我慢できなくなって、開発モードを導入した
bundle
ESModuleで大量のUserScriptを読み込んでいた
結果、UserScriptを全部読み込み終わるまでに、毎回30秒以上かかるようになってしまった
ページの切り出し等で新しいタブが開かれる度に、UserScriptの読み込みが走る
大量のリクエストが一気に飛び、ブラウザが重くなる
bundleして高速化を試みた
数秒で読み込み終わるようになった。快適
文法チェック、型チェック
Scrapboxのコードブロックは文法チェックも型チェックもしてくれない
最初の頃は大変だった
単純なスペルミスも括弧抜けも検知できない
importして初めてエラーがでる
エラーの原因を探すことからはじまる
スペルミスを直すためだけに何度もreloadする羽目になる
文法エラーを全部直して、ようやく実行時エラーの修正に移る
ここでも何度も再読込してバグを特定する
怪しい場所にconsole.logを挿入して見つける
開発コンソールのbreak pointで値を見たり、処理の流れを確認したりもする
この辺りは便利
型チェックがあれば、実行せずともつぶせたバグにも遭遇する
typescriptに移行した
当然そのままでは読み込めないので、使用前にbundleしなくてはならない
bundleしたUserScriptを使っていたので、あまり問題にはならなかった
ツール
文法チェック:deno
esbuildでもできる
型チェック:deno
なぜDeno
URLでコードを検査・実行できる
web browserと同じmodule解決方法を用いているので、Scrapboxと親和性が高い
ScrapboxはコードブロックごとにURLが発行される
bundle
読み込みに30秒かかるようになった
早く読み込みたかった
deno
単体テスト
domテストできない
書く場所
git管理してlocalで書く
つくったもの
よく使うものをまとめた
紹介するのが面倒?
UserScriptを作っている人・ヒントにした人とか紹介
(TODO:アイコンにする)
shokai
開発コンソールを開かずにUserScriptの状態を知らせられる点で便利
特にスマホなどで
yutaro
progfay
いつもお世話になっていて頭が上がらないtakker.icon*3
改良したいところだが放置中
masui
放置中だけど……takker.icon
nishio
最近ぜんぜん作ってないけど……
foldrr
最近メンテしてない……
madobe
scrapboxで遊ぼうの作者
主にUserCSSを使わせていただいている
UserScript面では、bookmarkletの作成方法とweb pageのscraping方法を参考にした
yuta0801
マウスクリックのjackで助言を受けた
daiiz
すごすぎるtakker.icon*3
最初はこれをTypeScriptで書き換えようとしたが断念
文字入力の実装はここからとってきた
後に少し簡略化した
widthとheightをつけるvercelのserverless functionのコードをまねした
nodeで書かれた部分をDenoで書き直すのに苦労した気がする
ここで紹介したコードを使っていただいている人がいて嬉しい
そのような場を作ってくれたことに感謝takker.icon*2
ci7lus
TamperMonkeyを使う方法があることをここから知った
mizdra
学んだ点がたくさんある
これを知ってから、大規模なUserScriptはpreactで書いている
UserScriptをgit管理する
TypeScriptで書く
単体テストを書く
当時はDenoでまともにDOMテストできなかったので、役立てなかった
最近のDenoなら、npm対応したのでいけるかもしれない
そのうち試そうと思う
自分のUserScript開発歴のターニングポイントだったかも
popup menuのJSXはいろんなUserScriptで使い回させていただいている
pretterをScrapboxで動かすUserScriptも参考にした
pokutuna
同じくpreactを使ったUserScriptの実装例として参考にした htmを使うとtranspileなしでpreactを実行できることを知った typescriptに移行する前までは、htmでpreactを使ったUserScriptを書いていた
yosider
一時期ここでUserScriptを書いていた
villagepumpのusers
「これができたらいい」という願望を勝手に拾って実装していった
その他、様々な人たちの発想や実装のおかげで、UserScirptを開発できています
まとめ
UserScriptは(ほぼ)なんでもできる
不満点があったらUserScriptで解決できる
(やろうと思えば)scrapbox上でwebアプリを作成できる
欠点もあるので注意
不具合が増える
scrapboxの更新に対応しつづけなければならない
仕様変更で突然動かなくなることもある
UserScriptを増やしまくると大変な目にあう
scrapboxのバグをバグだと気づかずにUserCSS/UserScriptで直してしまう
これは実害ない
UserScriptを書く人がもっと増えてくれたら嬉しいです
基本自分は他人のUserScriptのアイデアをパクって改造したものしか作ってない……takker.icon
アイデア面で貧弱なので、もっと書く人が増えていろんな発想でUserScriptを作ってくれる人が増えてくれると嬉しい
/icons/hr.icon
takker.iconのUserScript歴を書いていこうと思ったが、一つ一つの分量がおおくなってしまった
説明へたくそで伝わりにくそうなのもある
今まで作ってきたUserScriptや、UserScriptを作る上での技術を淡々と挙げていくほうがいいかな?
takker.iconからどんな話を一番聞きたいか教えてほしいですtakker.icon
自分が話したいことにすると、話題が広がりすぎて収集がつかない
今までに作ったUserScript
UserScriptの作り方
UserScript以外の話題
やりたいこと
画像を載せる
画像がないとわかりにくい
各UserScriptのページから引っ張ってくるつもり
日本語として読みやすい文章にする
分量を減らす
5分に収まらない部分や枝葉の部分は別ページに切り出す