generated at
LT:UserScriptを支える技術
Scrapbox Drinkup #12 RETURNSで発表するかもしれない資料
LT枠はとらなかったが、飛び入りLTがあるので、発表するチャンスに備えて作っておく

アドバイスがほしいので井戸端に置きましたtakker
まだ体裁を整えられていない
重複もあるし日本語がおかしい部分もある
書き込んではいけない場所も書き換えてほしくない場所もないので、自由にコメントしていただけたらと思います

takkerのUserScript歴を書いていこうと思ったが、一つ一つの分量がおおくなってしまった
説明へたくそで伝わりにくそうなのもある
今まで作ってきたUserScriptや、UserScriptを作る上での技術を淡々と挙げていくほうがいいかな?

takkerからどんな話を一番聞きたいか教えてほしいですtakker
自分が話したいことにすると、話題が広がりすぎて収集がつかない
今までに作ったUserScript
UserScriptの作り方
UserScript以外の話題



やりたいこと
画像を載せる
画像がないとわかりにくい
各UserScriptのページから引っ張ってくるつもり
日本語として読みやすい文章にする
分量を減らす
5分に収まらない部分や枝葉の部分は別ページに切り出す
文脈が繋がってるなら、無理に切り出す必要も無さそう?blu3mo
+1nishio
「分量を減らす」というより「5分でどの程度話せるのか把握して時間配分を考える」のが重要だと思った
現状全然考えてなさそうに見えるので。
なにも考えていない()takker
とりあえずLT:作ったUserScriptの紹介で試してみます
作業室 2023-01-29で話せるか把握する

感想
UserScriptで色々な事を実現しようとしている執念(?)と、それを可能にしている技術力/ハック技がとても面白いblu3mo
他のツールを使わずに、わざわざScrapboxで実現することにこだわっているのは執念かもtakker
単純に他のツールを使うハードルが高いということでもある
はるひさんのように気軽にいろんなツールに触ると楽になる?(何が)
執念でワロタMijinko_SD
自分はtakkerさんが作ったもの(ScrapBubble, external-completion, etc)の存在を知っているけど、未知の人にとっては「そういうものが存在する」という事もおもろい気がするblu3mo基素
UserScriptの紹介だけでも新鮮かも?takker
知らない人が自分の作ったものをみてどういう感想を抱くのか想像できない
初手にScrapBubbleなどのGIFを載せるだけでも面白そうblu3mo
「普段こんなの作っています」でGIF載せるのはいいですねtakker
下に入れておこう
いっそ発表動画を作ってしまえば時系列に依存しないLTになるinajob
まぁ資料公開だけでも十分説もある
すでに参加しない人がLT資料書いてたりしますしtakker
動画作るのはハードルが高く感じますtakker
Aviutlのセットアップ
ゆっくり音声の作成
レイアウトの設定
...動画=ゆっくり解説動画という変換がかかってしまったtakker
動画を公開するかどうかはさておき実際に発表してみて録画して見返してみてはどう?nishio
これを何分で話すつもりなのか謎だなと思った
発表時間は5分なのか…20分くらいかかるのではと思ったw
なので分量を減らすLT:UserScriptを支える技術#63cf2bf21280f000006dbbeaつもりですtakker
5分で話せる範囲ってどれくらいだろ……谷川岳から帰ってきたら発表練習してみるか……
5分はマジで短いです。1トピックかな…基素
マジで短い+1nishio
確かに1トピックではなくてもいいか。研究100連発みたいな感じのスタイルもあり基素
自分の視点では作ったもの紹介が一番気を引けるのではないかなと思った
めもinajob
web socket → WebSocket
指摘サンクスですtakker

hr

Scrapbox Drinkup #12 RETURNSの発表しないかもしれないLT資料です

自己紹介
takkertakker
2020-07くらいからscrapboxを使い始めた
「時々井戸端にいる」時々とは!?はるひblu3mo
UserScriptをたくさん作っているらしい
普段はこういうのを作っています
実質ScrapScriptsのfork版
基素さんが撮影してたGIFのほうがインパクトあるかも?
どれか覚えてないですがこれでもScrapboxユーザーには十分伝わるような気がします基素
一瞬何が起きているのかわからないのでリンクを表示しているよと口頭補足があると良さそう
直近だと、ScrapUniverseみたいなのをページ下部に出すやつなんかを作りました
(ページ本体も含んでいるGIFを撮影したい)



LT枠はもう埋まってしまいましたが、飛び入りLT枠があるとのことなので、あわよくば発表してみたいという思いから資料を作ってみました

最近作ったもの

scrapboxのUserScriptについて
よく使う方法
開発環境
今まで作ってきたもの
など

原点
emoji selecterから始まった
yutaroさんが作成した、絵文字の入力補完script
自分で読んで理解したい!
Firefoxでは動かなかったので、Firefoxでも動くように改造する必要があったという事情もある
今メインで使っている/takkerも、emoji selecterを改造する場所として作られた

UserScriptで使う技術

基本的に、JavaScriptで実装できるものならなんでもできる
Reactを読み込んで、scrapbox上で自前のエディタを動かすことも技術的には可能
code mirrorvim.wasmを読み込んで動かすなど
ここでは、身近な手法から紹介していく

PageMenuとPopupMenu
PageMenu
動的にtitleを変える機能がなくなってしまった
復活してほしい
PopupMenu
動的にボタンを切り替える

最近、mobile版でもPopupMenuが出るようになった
便利takkertakker
型定義をscrapbox-jp/typesに作ってある
意外と知らない関数が生えていたりする
PageMenuのaddItemのonClickでMouseEventを取得できるのが好きtakker
まあバグだと思うけど……

dom操作
このあたりから、複雑な機能も作れるようになってくる
よく使うDOMへのショートカット
文字の位置検知
.c- から取得できる
リンクや数式の取得
.cursor-line の状態でDOM構造が変わるため、すべてのケースに対応できるよう組むさいは工夫が必要

文字入力

rest api
開発コンソールの通信内容や、assets/index.jsの中身を見て、片っ端から調べ上げた
調査結果はscrapbox研究会に載せてある
wrapperを作った
最初はscrapbox-api-helperを使っていたが、その後scrapbox-userscript-stdに移動した
wrapperを作った理由
URLの組立やレスポンスの処理が特殊なAPIがあるため
そのまま使うのは面倒
TypeScriptで型付けしたかった
もちろん、あくまで内部APIなので、いつ仕様が変わってもおかしくない
実際、export APIはmethodが変わったし、api/pages/:projectname/:pagetitleはちょくちょく生えているpropertiesが変わる

preact
複雑なDOM操作が増えてきたら、これを使う
reactはファイルサイズが無駄に大きいので、preactを使っている
選択範囲に似ているリンクを入力補完するUserScript程度なら、minifyしたコードを一発でコードブロックに張り付けられるほど小さい
TODO:ここ要確認
scrapbox本体もReactではなくPreactにすればいいのになーと日頃から思っているtakker
まあpackageの依存関係上無理なんだろうけど
takkerElement.shadowRootにReact appをmountさせて動かしている
CSSをカプセル化するのと id のダブりを防ぐのが一応目的
意味のあることかは不明
逆にUserCSSでスタイルを変えられないなどのデメリットがある
まあただの好みである

websocket
ここから上級者向け
なのだが、すでにscrapbox-userscript-stdライブラリ化してあるので簡単に使えるはず
任意のprojectの任意のページのCRUDが可能になる
scrapbox.ioのwebsocketの通信内容を調べて、模倣した
途中で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は作っていない。
作る予定も特にない
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を作った
いろいろ設定
一部のURL(画像URLやprivate project内のソースコード)をbundleからはずす
sourcemapをつける
import mapを使う
scrapbox json dataで出力する
どんなに長いコードでもscrapboxに一発で取り込めて便利
今はscrapbox-bundlerを使っている
web browser上で動くbundler
リンクを踏むだけで実行できるのが最大の特徴
スマホでも動く!
Deno scriptはスマホだと動かせない
これが不満だったtakker
スマホから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を作ってそちらに置くのがおすすめ
Usercssで存在を隠すという選択肢もある

コードの張り付けテクニック
何度も張り直す場合は、undoして前回のコードを消すと楽
うっかりページ遷移してundoできなくなってしまったら、がんばって手作業で消すしかない

外部APIを使う
上述したようにCSPが厳しい
何の方法もなく使えるのはGyazoのupload APIしかない
CSPを回避できるweb browser拡張機能TamperMonkey(gerasemonkey)を導入する
初出はしーなるすさん
tweet2imageなどのserverless functionを叩くのに使っていた
これを知ったことで、UserScriptでできることがかなり広がった
作ったもの
これら2つは、後に公式で同様のものが実装された
ただし、外部リンク記法のほうは公式実装にバグがあるので、tampermonkey実装を使った方がいい

scrapboxに埋め込む
iframe-srcが厳しいため、埋め込めるものはほとんどない
そもそもscriptで埋め込んでも、UserScriptを入れている人にしか効果がない
回避策:画像で埋め込む
いろいろserverless functionを作った
アスキーアートを画像にするserverless function
画像にすればだいたいなんでもできる
出来ないこと
SVG内で外部リソースを読み込む
CSSやfontファイル、画像ファイルなどは読み込めない
たとえ同ドメインであっても不可
あらかじめdata URLなどで埋め込むしかない
スクロールなどの動的操作
リアルタイム更新
最速でもタブの再読込のとき
実装
vercelとdenoで作っている
deno deployも使ってみる?
突然サービスを終了しないかが心配
それなりに長く使えるところでdeployしたい
まあそんなこといったらvercelだっていつまで無料で使えるかわからないが……

不可能なこと
だとtakkerが考えていること

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で書く


つくったもの
未完成: ScrapVim

よく使うものをまとめた

scrapbox.io/customizeを使わなくなった
紹介するのが面倒?
scrapbox.io/customizeに書いたほうが認知されやすそう

UserScriptを作っている人・ヒントにした人とか紹介
(TODO:アイコンにする)
shokai
mobile版scrapboxの判定を参考にしたtakker
UserScript Eventsのサンプルコードをヒントに、status-bar (scrapbox)に情報を表示するUserScriptを作った
scrapbox-userscript-stdに移動してある
開発コンソールを開かずにUserScriptの状態を知らせられる点で便利
特にスマホなどで
yutaro
emoji selecterの作者。すごいtakker
他にもGlslCanvasを使ったUserScriptなども書いている
progfay
いつもお世話になっていて頭が上がらないtakkertakkertakker
この構文解析がなければ、ScrapBubbleは実装できなかったでしょう
replace text UserScriptなども作っている
改良したいところだが放置中
増井俊之
ExpandHelpを改造していろいろやったりした
放置中だけど……takker
nishio
keichoをscrapboxで動かしたくて、scrapbox-keichoを作った
最近ぜんぜん作ってないけど……
foldrr
Mousetrapでキーボードショートカットを作っている
これを元にScrapBindingsを作ってみたtakker
最近メンテしてない……
madobe
scrapboxで遊ぼうの作者
主にUserCSSを使わせていただいている
UserScript面では、bookmarkletの作成方法とweb pageのscraping方法を参考にした
yuta0801
マウスクリックのjackで助言を受けた
daiiz
ScrapScriptsの作者
すごすぎるtakkertakkertakker
最初はこれをTypeScriptで書き換えようとしたが断念
その後、UserScriptで実装できる可能性に気づき、scrapbox-card-bubblescrapbox-text-bubbleを作り始めた
文字入力の実装はここからとってきた
Firefoxでも完璧に動くのはScrapScriptsの実装方法だけだった
後に少し簡略化した
svg-hostingの元ネタ
widthとheightをつけるvercelのserverless functionのコードをまねした
nodeで書かれた部分をDenoで書き直すのに苦労した気がする
scrapbox-icon-buttonなんかもまねして作ってみたが、1年以上使ってないかも……
ここで紹介したコードを使っていただいている人がいて嬉しい
そのような場を作ってくれたことに感謝takkertakker
ci7lus
TamperMonkeyを使う方法があることをここから知った
それ以降、GM_xmlhttpRequestでCSPを回避して外部サービスを使うscriptを作っていった
pdf.jsをScrapboxで使うヒントも得た
mizdra
学んだ点がたくさんある
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を書いていた
kuuote
黒魔術探索で助力していただいた
yosider
一時期ここでUserScriptを書いていた
villagepumpのusers
「これができたらいい」という願望を勝手に拾って実装していった
その他、様々な人たちの発想や実装のおかげで、UserScirptを開発できています

まとめ
UserScriptは(ほぼ)なんでもできる
不満点があったらUserScriptで解決できる
(やろうと思えば)scrapbox上でwebアプリを作成できる
欠点もあるので注意
不具合が増える
scrapboxの更新に対応しつづけなければならない
仕様変更で突然動かなくなることもある
UserScriptを増やしまくると大変な目にあう
scrapboxのバグをバグだと気づかずにUserCSS/UserScriptで直してしまう
これは実害ない
UserScriptを書く人がもっと増えてくれたら嬉しいです
基本自分は他人のUserScriptのアイデアをパクって改造したものしか作ってない……takker
アイデア面で貧弱なので、もっと書く人が増えていろんな発想でUserScriptを作ってくれる人が増えてくれると嬉しい