[JavaScriptから現実世界に干渉する7の方法: ブラウザでハードウェアをコントロールする技術]

JavaScriptから現実世界に干渉する7の方法: ブラウザでハードウェアをコントロールする技術という本を書いてみた。前々からこのテーマでまとめた一冊というのを作りたかったので勢いで書いた。サンプルコードもほぼ各章ごとに、そこそこすぐ試せる形で用意したりして工夫してみた。

JSから直接ハードウェアをコントロールするのが好きなのと (特に WebSerial/WebUSB)、もともと WebAudio が好きだったのもあるので、これらを包括的に「現実的に干渉する」というまとめにすることにしてみた。そうなると「ディスプレイ」(光)」も無視できないので、光→振動→音→(HID→MIDI)→Serial→USB とより低レベルになるような構成にしてみた。

直前にいろいろ一気に書いたけど、だんだんやりかたをアップデートした結果、これが一番図とサンプルコードに気合が入っている。

1月3日17時〜7日の17時までは無料に設定してみたので眺めてみてください。

  1. トップ
  2. tech
  3. 『JavaScriptから現実世界に干渉する7の方法: ブラウザでハードウェアをコントロールする技術』という本を書いた

2024年も2023年も、、、と辿っていったら最後に年末にまとめ書いたのが2016年だった。いつだよ

今年はコーディングエージェントが急激に実用的になって、実装にかける時間が少なくなり企画・要件定義・設計・レビューの重要さと伝えかたの重要度をしみじみ感じた。コーディング面だと書くよりも読むことの重要度がさらに高まりを感じる。ただそれでも現時点で人間を代替できるほどまったく賢くなくイライラさせられるので、さっさともっと性能あがってくれと願っている。

昔書いて飽きたコードを再発掘・再実装するモチベになったのがよかった。

あとはひたすら金の計算をしてたなぁ…… 夫婦間のお金の使い道の見える化をすすめた (いままでやってなかったのかよと言われそう)。コスト最適化。

ブラウザの安全性を高めるための XSLT の削除とかいうことで、つまりブラウザネイティブでの XSLT のサポートは消えるので、ちょっと哀しいですねという気持ち。

一時期(20年前……)は XML を手書きで書いて、XSLT で変換してHTMLにするという日記システムを作って使っていた (さすがにブラウザ直ではなくPHPでやらせてたけど)。そこから20年、クライアントサイド XSLT の本来の目的である、データをレンダリング可能な HTML に変換する機能は、より安全で、より人間工学に基づき、より適切にメンテナンスされた JavaScript API に取って代わられています。とか書いてあるのを見ると、まぁ事実なんだけどただかなしいなという。

XSLT (特に 1.0) は、副作用がなく、変数は不変(一度定義したら変更不可)、ループは再帰で実現するという、割と強めの関数型言語で、自分が初めて触った関数型だったと思う。XSLT のおかげで他の関数型のパラダイムも割とすんなり入った気もする。

Gemini に XSLT で関数型っぽさがわかるサンプルを書かせてみた

出力そのまま。SVG を出力するというサンプルを書いてくれた。しかもアニメーションする。これが素のブラウザで見れるのも今だけです。

XML はただの起点

<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" href="fractal.xsl"?>
<root></root>

XSLT 側では4つの四角を call-template で生成するところから開始して、再帰でフラクタル状に四角をつくっていく。

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns="http://www.w3.org/2000/svg">
  <xsl:output method="xml" indent="yes" media-type="image/svg+xml"/>

  <xsl:template match="/">
    <svg width="100%" height="100%" viewBox="-500 -500 1000 1000" style="background-color: #1a1a1a;">
      <title>XSLT Recursive Fractal</title>
      
      <xsl:call-template name="draw-square">
        <xsl:with-param name="x" select="0"/>
        <xsl:with-param name="y" select="0"/>
        <xsl:with-param name="size" select="300"/>
        <xsl:with-param name="depth" select="5"/> </xsl:call-template>
    </svg>
  </xsl:template>

  <xsl:template name="draw-square">
    <xsl:param name="x"/>
    <xsl:param name="y"/>
    <xsl:param name="size"/>
    <xsl:param name="depth"/>

    <xsl:if test="$depth > 0">
      
      <xsl:variable name="color">
        <xsl:text>hsl(</xsl:text>
        <xsl:value-of select="$depth * 40"/>
        <xsl:text>, 70%, 60%)</xsl:text>
      </xsl:variable>

      <rect 
        x="{$x - $size div 2}" 
        y="{$y - $size div 2}" 
        width="{$size}" 
        height="{$size}" 
        fill="{$color}" 
        opacity="0.9"
        stroke="#fff"
        stroke-width="1"
      >
        <animateTransform 
            attributeName="transform" 
            type="rotate" 
            from="0 {$x} {$y}" 
            to="360 {$x} {$y}" 
            dur="{10 + $depth * 5}s" 
            repeatCount="indefinite" />
      </rect>

      <xsl:variable name="nextSize" select="$size * 0.5"/>
      <xsl:variable name="offset" select="$size * 0.75"/> <xsl:variable name="nextDepth" select="$depth - 1"/>

      <xsl:call-template name="draw-square">
        <xsl:with-param name="x" select="$x"/>
        <xsl:with-param name="y" select="$y - $offset"/>
        <xsl:with-param name="size" select="$nextSize"/>
        <xsl:with-param name="depth" select="$nextDepth"/>
      </xsl:call-template>

      <xsl:call-template name="draw-square">
        <xsl:with-param name="x" select="$x"/>
        <xsl:with-param name="y" select="$y + $offset"/>
        <xsl:with-param name="size" select="$nextSize"/>
        <xsl:with-param name="depth" select="$nextDepth"/>
      </xsl:call-template>

      <xsl:call-template name="draw-square">
        <xsl:with-param name="x" select="$x - $offset"/>
        <xsl:with-param name="y" select="$y"/>
        <xsl:with-param name="size" select="$nextSize"/>
        <xsl:with-param name="depth" select="$nextDepth"/>
      </xsl:call-template>

      <xsl:call-template name="draw-square">
        <xsl:with-param name="x" select="$x + $offset"/>
        <xsl:with-param name="y" select="$y"/>
        <xsl:with-param name="size" select="$nextSize"/>
        <xsl:with-param name="depth" select="$nextDepth"/>
      </xsl:call-template>

    </xsl:if>
  </xsl:template>

</xsl:stylesheet>
  1. トップ
  2. tech
  3. XSLTという関数型言語

Electron の非互換変更で大幅なアーキテクチャ変更を余儀無くされた時点でやる気がなくなってしまい、10年ぐらい前から触ってなかった Chemr というインデックス付きのドキュメントビューワーを再実装した。

なお Chemr はもともと .chm (Compiled HTML Help) を macOS で読むためものだった。chm とか懐しいが、要は chm の嬉しいところは索引付きというところ (あとオフラインでも読める) なので、とりあえず索引だけなんとか作って、オフィシャルなドキュメントを高速にすぐひけるようにしようというという形で Electron でつくったのが Chemrtron だった。

再実装

コード自体はほぼコーディングエージェントにやらせた。

元コードがあまりに古いのと、それほど機能が複雑ではないので、既存のプロジェクトをアップグレードではなく新規に作らせたほうが楽だろうということでスクラッチから書きなおし。ただし旧実装はディレクトリをわけて置いておいて、参考にしろという形でコンテキストを与えながらやった。

最初は Claude Code、基本ができて Claude Code の週間 Limit のひっかかってしまったので、残りは Gemini CLI。

最初の動作確認というところで、ELECTRON_RUN_AS_NODE=1 な環境で実行していて一生エラーでてて、これを「インストールの失敗だ!」「お前の環境が悪い!」と言い張ってて Claude がまじでアホだった。

そしてバカでお茶目な Gemini ちゃんは「✦ 大変申し訳ありません。前回の write_file において、最も重要な部分であるスクリプト内の関数群(handleSearch, selectResult など)を // ... (中略) ... という文字列で置き換えてしまうという、致命的なミスを犯しておりました。 」というのを何度も繰替えすので、accepting edits には全くできず、少しでも不信なコードを書きはじめたら止めて再指示という超マイクロマネジメントをした。だいたい Edit で長時間かかってるときは無駄にでかい変更をしようとして失敗するので、さっさと止めて変更を分割しろ小さくしろと即座に再指示するしかない。

Gemini はデフォルトのシステムプロンプトが本当にクソでイライラするので書きかえて使っている。レビューしてる時に質がゴミなのに「次に進みますか?」って何度も訊いてくるのが本当に我慢ならない。

Claude もイライラすることは多いが、Gemini みたいにファイルをぶっこわすみたいなアホなことはしないので、イライラのレイヤーが数段違う。

そんなこんなで1時間ぐらいは無駄にしたけど、だいたい(3時間+5時間)2日で機能がそろって v2.0.0 として出せた。

インデックスを作る部分も古すぎるので非互換変更を入れ、既存のもののうち重要そうなのは、LLMに再実装させた。ついでに全部のインデクサがちゃんと動くかテストできるようにした。

  1. トップ
  2. tech
  3. Chemr (Chemrtron) を再実装