LT:UserScriptを支える技術
タイトルは最終的な内容によって変える
モリモリ資料にするなら、/nota-techconf/さようならElasticsearch、よろしくElastic Cloudが参考になりそう
とおもったが、情報の弁当箱は好きじゃないtakker.icon
やるならページを分けるだろうなあ
書き進まないなーtakker.icon
井戸端に載せて、いろいろ聞くか
06:49:03 といいつつ、1時間使ってたくさん書いてしまった……
自己紹介
takker.icontakker
takkerの連絡先
2020-07くらいからscrapboxを使い始めた
UserScriptをたくさん作っているらしい
LT枠はもう埋まってしまいましたが、飛び入りLT枠があるとのことなので、あわよくば発表してみたいという思いから資料を作ってみました
最近作ったもの
scrapboxのUserScriptについて
よく使う方法
開発環境
今まで作ってきたもの
など
原点
emoji selecterから始まった
/yutaro/yutaro.iconさんが作成した、絵文字の入力補完script
自分で読んで理解したい!
Firefoxでは動かなかったので、Firefoxでも動くように改造する必要があったという事情もある
今メインで使っている/takkerも、emoji selecterを改造する場所として作られた
UserScriptで使う技術
基本的に、JavaScriptで実装できるものならなんでもできる
Reactを読み込んで、scrapbox上で自前のエディタを動かすことも技術的には可能
code mirrorやvim.wasmを読み込んで動かすなど
ここでは、身近な手法から紹介していく
PageMenuとPopupMenu
PageMenu
動的にtitleを変える機能がなくなってしまった
復活してほしい
PopupMenu
動的にボタンを切り替える
最近、mobile版でもPopupMenuが出るようになった
便利takker.icon*2
型定義をscrapbox-jp/typesに作ってある
意外と知らない関数が生えていたりする
PageMenuのaddItemのonClickでMouseEventを取得できるのが好きtakker.icon
まあバグだと思うけど……
dom操作
このあたりから、複雑な機能も作れるようになってくる
よく使うDOMへのショートカット
文字の位置検知
.c-から取得できる
リンクや数式の取得
.cursor-lineの状態でDOM構造が変わるため、すべてのケースに対応できるよう組むさいは工夫が必要
文字入力
rest api
開発コンソールの通信内容や、assets/index.jsの中身を見て、片っ端から調べ上げた
調査結果はscrapbox研究会に載せてある
/help-jp
wrapperを作った
最初はscrapbox-api-helperを使っていたが、その後scrapbox-userscript-stdに移動した
wrapperを作った理由
URLの組立やレスポンスの処理が特殊なAPIがあるため
export APIやapi/pages/:projectname/search/titlesなど
そのまま使うのは面倒
TypeScriptで型付けしたかった
もちろん、あくまで内部APIなので、いつ仕様が変わってもおかしくない
実際、export APIはmethodが変わったし、api/pages/:projectname/:pagetitleはちょくちょく生えているpropertiesが変わる
preact
複雑なDOM操作が増えてきたら、これを使う
reactはファイルサイズが無駄に大きいので、preactを使っている
選択範囲に似ているリンクを入力補完するUserScript程度なら、minifyしたコードを一発でコードブロックに張り付けられるほど小さい
TODO:ここ要確認
scrapbox本体もReactではなくPreactにすればいいのになーと日頃から思っているtakker.icon
まあpackageの依存関係上無理なんだろうけど
takker.iconはElement.shadowRootにReact appをmountさせて動かしている
CSSをカプセル化するのとidのダブりを防ぐのが一応目的
意味のあることかは不明
逆にUserCSSでスタイルを変えられないなどのデメリットがある
まあただの好みである
web socket
ここから上級者向け
なのだが、すでにscrapbox-userscript-stdライブラリ化してあるので簡単に使えるはず
任意のprojectの任意のページのCRUDが可能になる
scrapbox.ioのweb socketの通信内容を調べて、模倣した
途中でsocket.ioを使っていることに気づいてからは、CDNで導入したsocket.ioを使うようにした
黒魔術
Reactで管理しているDOMには、__ReactFiberなどReactが生やした内部データがある
とれるもの
カーソル操作
選択範囲操作
これを自前で取得するのは地獄
React Componentの内部変数からScrapboxのカーソル位置と選択範囲を取得
カーソル位置や選択範囲の変更時に発火するlistenerを登録できる
これが一番便利
選択範囲内の文字列は、PCからなら#text-inputから取得できるが、mobileだと取得できない
自分で実装しようとすると非常に大変
当たり判定の計算
太字記法などで文字の大きさが変わると、位置計算が
カーソル移動の検知方法
カーソルにfocusがあるかどうかでDOMの状態が変わるので、一筋縄では行かない
かなり不安定なハック
いつscrapbox側で仕様が変わるかわからない
おそらくReact Hooksで書き換えられたら死ぬ
いつ消えるかわからないhackに依存するのはよくないのだが……メインのUserScriptで深く依存してしまっている
仕様変わって使えなくなったら発狂する自信がある
以上の手法を一つのrepoにしてgit管理している
scrapbox-userscript-std
Denoから使える
node向けpackageは作っていない。
作る予定も特にない
dntを使えば作れるはず(未確認)
外部ライブラリを使う
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する
deno bundleでbundleする
esbuildはnode.js型のmodule解決しかできないので、そのままでは使えない
minifierにはesbuildを使う
$ deno bundle <URL> | esbuild --minify
その後、いろいろ設定してbundleしたくなったので、esbuildにpluginを入れたDeno scriptを作った
UserScriptをbundleするDeno script
UserCSSをbundleするDeno script
いろいろ設定
一部のURL(画像URLやprivate project内のソースコード)をbundleからはずす
sourcemapをつける
import mapを使う
scrapbox json dataで出力する
どんなに長いコードでもscrapboxに一発で取り込めて便利
今はscrapbox-bundlerを使っている
web browser上で動くbundler
リンクを踏むだけで実行できるのが最大の特徴
スマホでも動く!
Deno scriptはスマホだと動かせない
これが不満だったtakker.icon
スマホからUserScriptを更新できるようになった
UIが雑だったりバグが残っていたりするが、普段使っている範囲で問題は起きていないので、放置している
これも不便に慣れてしまうということなのだろうなあ
コードブロックへの張り付け
小さいコードはそのまま張り付ければいい
大規模なUserScriptを作っていると、minifyしても一発で張り付けられないようになる
text is too longと言われる
回避策
分割して張り付ける
例:ScrapBubbleの最新版(TODO: リンク貼る)は2回に分けて貼り付ける
より巨大になると張り付けが大変
何回も張り付けなければならない
scrapboxが重くなる
最悪途中でクラッシュする
scrapbox json dataにしてimportする
ブラウザでのrenderingを介さず、直接scrapbox.ioのDBに格納する
構文解析やrenderのコストを省けるので、1MBのコードでもいけるはず
うっかりコードを張り付けたページを開いてしまうと(お察し下さい)
閲覧者のブラウザを壊さないよう、使う際はprivate projectやbundle専用projectを作ってそちらに置くのがおすすめ
cf. クソデカminifyコードを/takkerに貼り付けるのはいい加減やめようか
コードの張り付けテクニック
何度も張り直す場合は、undoして前回のコードを消すと楽
うっかりページ遷移してundoできなくなってしまったら、がんばって手作業で消すしかない
外部APIを使う
上述したようにCSPが厳しい
何の方法もなく使えるのはGyazoのupload APIしかない
CSPを回避できるweb browser拡張機能TamperMonkey(gerasemonkey)を導入する
初出はしーなるすさん
tweet2imageなどのserverless functionを叩くのに使っていた
これを知ったことで、UserScriptでできることがかなり広がった
作ったもの
外部リンク記法に変換するscript
scrapbox-embed-tweet
これら2つは、後に公式で同様のものが実装された
ただし、外部リンク記法のほうは公式実装にバグがあるので、tampermonkey実装を使った方がいい
scrapboxに埋め込む
iframe-srcが厳しいため、埋め込めるものはほとんどない
そもそもscriptで埋め込んでも、UserScriptを入れている人にしか効果がない
回避策:画像で埋め込む
いろいろserverless functionを作った
svg-hosting
svg形式のテキストを画像にするserverless function
plantuml-proxy
PlantUMLファイルから画像URLを生成するserverless function
aa2svg
アスキーアートを画像にするserverless function
最新の実況天気図を取得するserverless function
画像にすればだいたいなんでもできる
出来ないこと
SVG内で外部リソースを読み込む
CSSやfontファイル、画像ファイルなどは読み込めない
たとえ同ドメインであっても不可
あらかじめdata URLなので埋め込むしかない
スクロールなどの動的操作
リアルタイム更新
最速でもタブの再読込のとき
実装
vercelとdenoで作っている
deno deployも使ってみる?
突然サービスを終了しないかが心配
それなりに長く使えるところでdeployしたい
まあそんなこといったらvercelだっていつまで無料で使えるかわからないが……
不可能なこと
だとtakker.iconが考えていること
UserScriptの開発方法
最初期
直接コードブロックに書き込んでいた
自分のページのscript.jsからimportする
書き込みとリロードをひたすら繰り返して試す
Hot reload?そんなものはない
Scrapbox-UserScript-Switcher
開発している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で書く
つくったもの
emoji selecter
external-completion
ScrapBindings
未完成: ScrapVim
ScrapBubble
scrapbox-userscript-std
よく使うものをまとめた
scrapbox.io/customizeを使わなくなった
紹介するのが面倒?
scrapbox.io/customizeに書いたほうが認知されやすそう
UserScriptを作っている人・ヒントにした人とか紹介
(TODO:アイコンにする)
shokai
mobile版scrapboxの判定を参考にしたtakker.icon
scrapboxで音声入力
UserScript Eventsのサンプルコードをヒントに、status-bar (scrapbox)に情報を表示するUserScriptを作った
scrapbox-userscript-stdに移動してある
開発コンソールを開かずにUserScriptの状態を知らせられる点で便利
特にスマホなどで
yutaro
emoji selecterの作者。すごいtakker.icon
他にもGlslCanvasを使ったUserScriptなども書いている
progfay
scrapbox-parserの作者
いつもお世話になっていて頭が上がらないtakker.icon*3
この構文解析がなければ、ScrapBubbleは実装できなかったでしょう
replace text UserScriptなども作っている
改良したいところだが放置中
masui
ExpandHelpを改造していろいろやったりした
放置中だけど……takker.icon
nishio
keichoをscrapboxで動かしたくて、scrapbox-keichoを作った
最近ぜんぜん作ってないけど……
foldrr
Mousetrapでキーボードショートカットを作っている
これを元にScrapBindingsを作ってみたtakker.icon
最近メンテしてない……
madobe
scrapboxで遊ぼうの作者
主にUserCSSを使わせていただいている
UserScript面では、bookmarkletの作成方法とweb pageのscraping方法を参考にした
yuta0801
マウスクリックのjackで助言を受けた
daiiz
ScrapScriptsの作者
すごすぎるtakker.icon*3
最初はこれをTypeScriptで書き換えようとしたが断念
その後、UserScriptで実装できる可能性に気づき、scrapbox-card-bubbleやscrapbox-text-bubbleを作り始めた
文字入力の実装はここからとってきた
Firefoxでも完璧に動くのはScrapScriptsの実装方法だけだった
後に少し簡略化した
svg-hostingの元ネタ
widthとheightをつけるvercelのserverless functionのコードをまねした
nodeで書かれた部分をDenoで書き直すのに苦労した気がする
scrapbox-icon-buttonなんかもまねして作ってみたが、1年以上使ってないかも……
scrapbox.io/customizeの作者
ここで紹介したコードを使っていただいている人がいて嬉しい
そのような場を作ってくれたことに感謝takker.icon*2
ci7lus
TamperMonkeyを使う方法があることをここから知った
それ以降、GM_xmlhttpRequestでCSPを回避して外部サービスを使うscriptを作っていった
pdf.jsをScrapboxで使うヒントも得た
mizdra
scrapbox-userscript-icon-suggestionの作者
学んだ点がたくさんある
preactを使ったUserScript
これを知ってから、大規模なUserScriptはpreactで書いている
UserScriptをgit管理する
TypeScriptで書く
単体テストを書く
当時はDenoでまともにDOMテストできなかったので、役立てなかった
最近のDenoなら、npm対応したのでいけるかもしれない
そのうち試そうと思う
自分のUserScript開発歴のターニングポイントだったかも
popup menuのJSXはいろんなUserScriptで使い回させていただいている
pretterをScrapboxで動かすUserScriptも参考にした
scrapbox-formatterという名で作り直そうとしたが……分割したコードブロックのformatが困難なことと、自分の中で需要がなくなったことで放置してしまった
pokutuna
同じくpreactを使ったUserScriptの実装例として参考にした
htmを使うとtranspileなしでpreactを実行できることを知った
typescriptに移行する前までは、htmでpreactを使ったUserScriptを書いていた
yosider
選択範囲に似ているリンクを入力補完するUserScriptの元ネタ
programming-notesの作者
一時期ここで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分に収まらない部分や枝葉の部分は別ページに切り出す
#2023-01-24 05:31:20
#2023-01-21 11:48:13