My works

About

今までに作成したプログラムの一部をまとめています。 多くが Web 系か CLI アプリケーションか Lua スクリプトです。

Love

Icons from https://deno.com, https://www.rust-lang.org, https://rye.astral.sh/, https://biomejs.dev/, https://lit.dev/

興味・使用経験がある

勉強したい

Astro + Svelte で色々作るとかワクワクしますね

このページ

Lit, esbuild, HTML, Sass, TypeScript

技術系のプロダクトをまとめるためのポートフォリオページです。 このページはプロダクトをまとめているだけでなく、作成物のショーケースでもあります。 このページ自体は HTML, Sass (SCSS), TypeScript で記述されており、ライブラリとして Lit を使用しています。 ビルドには esbuild を使用し、ランタイムには Deno を使用しています。

esbuild は主に Node.js 環境向けに作られているため、Deno で使用できる機能は一部制限されています。 特に node_modules 配下のライブラリを自動でバンドルしてくれない (そもそも node_modules が存在しない) などの問題があるため、これを解決するために自作のプラグインを使用しています。 (独立したプロジェクトにはしていませんが、ライブリロードするためのプラグインもこのために作成しています。) まだまだ実際のプロダクトで Deno 環境の esbuild を使用するには至りませんが、このような実践的なページも十分作ることができます。 また、各セクションにある GitHub のレポジトリのカードは独立したライブラリとして公開しているものです。

ページを作る中でそこそこの量の文章を書くにあたり、各セクションを Markdown で書けるようにしています。 HTML ファイルはテンプレートとして扱われ、リンクされたファイル名の Markdown ファイルを marked でコンパイルした内容が挿入される仕組みになっています。 これは HTML ファイルの読み込みをフックするプラグインで実現されています。

きちんとプロダクトの説明をまとめたものを作りたいと思ったため作成しました。 ついでに今まで GitHub Pages を使っていたので Cloudflare Pages も使ってみたいと思い使ってみました。 「このページを作るのに自作ソフトウェアがたくさん動いているんだぜ!」と言いたかったのでVite や Astro などのメジャーなフレームワークを使わずに作りました。

Aseprite Scripts

Lua

ドット絵エディタ Aseprite の自分で開発したスクリプトをまとめたレポジトリです。 スプライトの PSD (Photoshop File Format) 出力や Windows 向けアイコン・カーソル出力、作品に効果を適用するフィルタなどがあります。 (GitHub 使い始めのよくわからないうちに作ったため MonoRepo になっています...)

PSD 出力スクリプトは Aseprite に PSD 入出力が欲しいというニーズは Aseprite ユーザーの間で一定数あり、知り合いや私自身も PSD 出力を使いたかったため作成しました。 ありがたいことに公式レポジトリやコミュニティなどで紹介してもらったりもしています。 Lua でのバイナリファイルの操作自体に難があったり、PSD ファイルの仕様の複雑さや情報が少ないことから開発には時間を要しました。 現在はアニメーション出力のために段階的に改修を進めており、PSD ファイルの解析がある程度済んだため実際に animated PSD を出力する機能を追加する段階です。 それ以前に当時のコードがかなり汚いため 2024 年 6 月現在リファクタリング中で、自作 Lua バンドラを用いたリファクタリングなどを進めています。

Windows のアイコン・カーソルを出力するスクリプトでは ICO, CUR, ANI 形式でアイコン・カーソル・アニメーションカーソルの出力ができます。 作成したマウスカーソルを頒布するときに別のソフトを通すのが面倒だったため作成しました。 Aseprite は CLI で動作することもできるため CLI からスクリプトを呼び出すと自動的に Aseprite ファイルからカーソルなどを出力できます。 ICO や CUR は BMP とほとんど同じファイル構造をしているため実装は容易で、ANI も RIFF のなかに ICO/CUR が乗っているだけなのでサクッと実装できました。

Aseprite Type Definition

Lua

Aseprite の Lua API の lua-language-server 用の型定義です。

前述の Aseprite でアイコン・カーソルを出力するスクリプトを作るときに型支援が欲しかったので作りました。 公式 docs を手動で書き写して型定義と説明のデータを作りました。 作っている途中でドキュメントの間違いに気づいて直してもらったりしています。

型情報自体はいいですが、説明に関してはドキュメントから自動生成にしたいです。 ドキュメントのアップデートにも対応できていないので直さないと...

esbuild-plugin-cache-deno

TypeScript, esbuild, Deno

Deno 環境で実行される esbuild で HTTP/HTTPS/NPM インポートをバンドルできるようにするためのプラグインです。 このページでもこのプラグインを用いて Lit をバンドルしています。 仕組みとしては、Deno の lock ファイル (npm でいう package.lock) を用いて URL が具体的にインポートしているモジュールを突き止め、Deno 自体のグローバルキャッシュに対応付けています。 特に Node.js に関してはモジュール解決の仕組みが複雑で、一部の機能は完全に実装できていません (それでも React や lodash など主要ライブラリはバンドルできています)。 リファクタリングとともに lock ファイルのバージョン 3 に対応し、このバージョンではリダイレクト先や具体的に依存している npm モジュールのバージョンなどの特定が容易になったため実装がかなり簡潔になりました。

開発当時には HTTP/HTTPS インポートをバンドルできるようにするプラグイン自体は存在しており、fetch API と特定ディレクトリへのキャッシュを用いて実装されていました。 しかし同じ URL のキャッシュがプロジェクト間で共有されないのが気に入らない (私が Node.js ではなく Deno を使う理由です) という点やあまりメンテナンスがされていないという問題点があったため自分で作ってしまったのが始まりです。 その後 Deno が npm モジュールのインポートに対応したこともあり npm モジュールにも対応しました。

かなり実装は大変でしたが実用的なものに仕上がって満足しています。 実際に私が作る小規模な Web のプロジェクトでは大体これを使ってバンドルしています。

独自のセクションは設けませんが、Sass をビルドする拡張機能を機能追加するフォークやファイルをコピーする拡張機能、ビルド終了時にビルド時間とともにエラー数を報告するプラグインなども作っています。 最近規模が少し大きめのプロジェクトを作るにつれ、ビルドが esbuild 内で完結できない (公式に esbuild は「リンカ」と表現されています) という問題点が顕在化してきて、使いやすいタスクランナー (イメージとしては gulp) を探しています。 一部のプロジェクトはファイルの変更監視→再ビルド→Server Sent Events でライブリロードの仕組みを実装しているものもあるのでそれを切り出してもいいなと考えています。

GitHub Cards

Lit, esbuild, TypeScript, babel

GitHub のカードをウェブサイトに埋め込むための custom component です。 Lit を使用して Web Components を使って作られています。 そのため <script> タグで読み込み <gh-repo-card> のような要素を置くだけで使用できます。 esbuild でビルドしているのですが、現在のバージョンではデコレータを変換してくれないため babel を使用しています。 そのため TypeScript の experimental decorator が使えず、JavaScript のデコレータを使っています (勝手に esbuild が TS→JS のデコレータ変換をしてくれると思っていました; JS デコレータがブラウザに広く実装されたら esbuild に実装されるらしいです)。

GitHub のカードを提供するプロダクトは既に複数存在していましたが、ニーズに合わなかったため作りました。 画像として返却する API は解像度が低いという問題があり、DOM を書き換える API を使うものでは Shadow DOM で使えなず、さらに GitHub の API 上限にすぐに当たるという問題がありました。 そのため DOM を書き換える API のものを参考にし、Web Components を使って再実装しました。 もとの機能に加えカスタマイズ性を向上させたりユーザーのアイコンを表示するなど機能が拡張されています。 API 上限に関しては Cache API と Lock API を駆使して重複したリクエストの送信を排除することで対応しています。 この機能の実装には fetch をラップする方法や Service Worker を使う方法がありますが、後者は全リクエストを snooping してしまいオーバースペックかつ利用範囲が限られるため前者の方法を利用しています。

数日で作ったにしては悪くないと思っていますが、詰めきれていないエッジケースも多いので直したいと思っています。 レポジトリにも書いてあるとおりユーザーのカードも追加する予定です。 npmjs に公開したら自動で気に esm.sh (CDN) で使えるようになり、簡単に再利用できて便利だなあと思いました。

neblua

Lua

Lua で記述された Lua のバンドラです。 zero-dependency (他のライブラリに依存しない) になっており、持ち運び性が非常に高いことが特徴です。 そのため neblua を使って neblua 自身をバンドルすることもできます (動作確認に使っています)。 構文解析などを行わない設計になっているため非常に高速に動作します。

Lua は require 関数を用いてモジュールを読み込みますが、require の実際の振る舞いはカスタマイズ性が高く、hack 的な方法を使わなくてもバンドラを作成することができます。 Lua にはチャンクという概念があり、これは読み込んだモジュールを返す関数です (Common JS や UMD と似た概念だと思います)。 neblua では require の動作をカスタマイズし、優先的にバンドルによってハードコードされたチャンクを参照するように書き換えます。 グローバルの扱いに気をつければ、このようにして文字列レベルの処理でバンドルを行うことができます。 またエラー時の出力をわかりやすくするための工夫もされており、xpcall 関数を使うことでエラーの発生した行数を書き換えています。

neblua は前述の Aseprite の PSD 出力スクリプトの作成の際、ファイルが巨大化しすぎてメンテナンスが困難になってきたため作られました。 Aseprite スクリプトは Lua ファイルを直接頒布するという形式のため、複数ファイルでの実装と相性が悪いためです。 バンドラ自体は既に存在していましたが Luarocks を使うのが面倒だったのとバンドラを作りたい気分だったので作りました。

個人的には速度や依存関係の少なさや技術的なおもしろさから、結構いいものになったと感じています。 現在はインポート対象のファイルを自動判別する機能を実装しており、少しインターフェースを整備すればバージョンを上げられそうです。 内部的に使っている Jest-like なテストライブラリも切り出して単体のライブラリにしたいなと考えています。

日本語グライド入力システム

(大学で作成したプロジェクトのため公開できません)

TypeScript, HTML, Sass, Webpack, Python, FastAPI, pipenv, docker

キーボード上をなぞって (指をスライドさせて) 入力することができるキーボードの実装です。 実際に仮想キーボードとして実装するというよりは機能の実証という形で作成しています。 そのためフロントには Web, バックエンドには Python を使用しています。 Python を利用した理由はグループワークで他の人が使えるという条件で他に選択肢がなかったためです。 Python の環境作成には pipenv を使用していますが、環境共有を簡単にするため docker で環境を固めています。 アルゴリズムは指でなぞった軌跡からの特徴抽出、特徴点からのラティス生成、ラティス上での探索という順で実装されています。 機械学習を用いる方法も考えられましたが、時間的な制約で行うことができませんでした。

英語では Gboard などがありますが日本語で作成している例はほとんどなかったため、グループワークの題材として採用しました。

古典的なアルゴリズムの組み合わせで作成されているため技術的には難しい部分はあまりありませんでした。 ただグループのメンバーとの技術的なギャップが大きく、グループワークとして進めるという点でかなり苦労しました。 実際フロントや API に関してはほとんど自分で実装してしまい、ビジネスロジックの実装をしてもらう (アルゴリズムは全員で考える) という形で行っていました。 開発のための環境構築が一番の問題点だと考えていたため、docker を使って極力シンプルに環境を共有できるように工夫するなどもしました。 実際に動作するものができてそこそこの精度も出たので作成物にはある程度満足しています。 最近はこれを機械学習ベースで再実装して VR 上の入力システムを構築したいなあと考えています。

AtCoder CLI

Rust, clap, serde, scraper, ureq

AtCoder の問題提出・ワークスペース初期化のための CLI アプリケーションです。 ヘッドレスブラウザを使用せず ureq パッケージを使った HTTP リクエストで動作しているため非常に高速で、キャッシュ操作によりログイン情報を扱っています。 余談ですが CLI を作成するのに満足して AtCoder 自体をしていないため機能改善の余地はたくさんあると思います。

既存の CLI アプリケーションもありますがそれ自体が別のアプリケーションに依存していること、そのアプリケーションがヘッドレスブラウザを使っているために動作が遅いことが気に入らなかったため作成しました。 Rust で作った理由は Rust でアプリを書く練習をしたかったためです。

Rust でちゃんとしたアプリケーションを書くのはこれが初めてでしたが、得られた知見がいくつかありました。 Rust でプログラムを書く場合は自然ときれいな設計になる (きれいな設計にしないと書くのがつらい) のですが、アプリの構造の設計にだいぶ頭を悩ませる事になりました。 特に CLI とビジネスロジックを分けるかどうかも大きな問題で、公式チュートリアルの grep は分けていましたが今回は最終的に分けるのをやめることになりました。 これは CLI での操作自体がビジネスロジックであるため、不可分であるという判断からです。 また、最初からパフォーマンスを考えたら負けだということを痛感しました。 Rust の所有権・ライフタイムシステムが複雑というよりかはソフトウェアの設計がそもそも複雑で、本来はこのレベルのことを人間が手動で管理しないといけないと考えるとパフォーマンスを犠牲にして簡潔性得るのが妥当に思えます。

Turing Complete Unofficial

TypeScript, HTML, Sass, Webpack

Turing Complete というゲームの日本語解説です。 Turing Complete は NAND 素子などから出発してデコーダやマルチプレクサ、状態回路などを経てカウンタやレジスタ、 ALU などを実装し最終的にはチューリング完全な計算機を作るというゲームです。 完全に自分で計算機を組んでオレオレ ISA も作れる他、高速性や回路の小ささのために論理回路に回帰して高性能なシステムを作ることができるなど非常に楽しいゲームとなっています。 この解説ページは Webpack 駆動の SSG となっています (Webpack がスタンダードだった時代に作りました)。 コントリビュータが Markdown で記事を書いて main ブランチに取り込まれると CI でビルドされて GitHub Pages にデプロイされるという仕組みになっています。 メタファイルから Markdown を読み込んで HTML を出力するため、独自の Webpack プラグインを実装しています。 Markdown パーサには markdown-wasm、後処理などに katex や DOMPurify などを 使っています。

最初は CSR していましたがパフォーマンスの問題から SSG に切り替わったという経緯があります。 今作るなら Astro か Lume を使うと思います。

Simple HTML Element

TypeScript

HTML 文字列を生成するための DOM-like API です。 SSG でのコンテンツの生成など HTML 文字列をレンダリングしたいけれど大きい文字列リテラルを作りたくないし JSX のような大げさなものを使いたくないというニーズがあり作成しました。

npm と deno.land へのモジュールの同時公開を行ったことがないため手探り状態で作成しており、ファイル構成が少々汚いです。 npm への公開を CI で行っているため、checkout 後にビルドコマンドを走らせるほうが適切だなあと思っています。

vpmpkg

TypeScript, Hono, Vite, Lit, HTML, CSS

GitHub のレポジトリに対応する VPM (VRChat Package Manager) レポジトリのマニフェストを生成する API です。自作ライブラリの VPM パッケージを用意するのが面倒だったのと他人の VPM レポジトリに使用しないパッケージが入っているのが嫌だったため作成しました。 サーバーは Hono で記述されており、ディスク/インメモリキャッシュなどを使ってリクエストをさばいています。 静的サイトは Vite + Lit で作成されています。

Lit を使用するのはこのプロジェクトがほぼ初めてで、使用してみたら Web Components の属性変更などのインターフェースが抽象化されていて使用感が良かったです。 既に React などが使われている環境だと使い所が難しいですが、新規プロジェクトや Adobe の Spectrum Web Components のような使い方をするのにいいなあと感じました。 かつて https://vpmpkg.ts7m.net にデプロイされており、VPS 上の Deno で動いていたため Let's Encrypt の設定を手動で行ったりしたため勉強になることも多くありました。 現在は運用を終了しており、理由は静的にレポジトリのマニフェストをホストできることに気づいたことと、利用者が自分しかいなかったためです。

Glitch Shader

HLSL, C#, Unity

Unity で post processing を使わずにマテリアルに設定するだけでグリッチ・走査線・色収差エフェクトを得られるシェーダーです。 HLSL で記述されており、マルチパスレンダリングや Stencil などを駆使して疑似半透明表示や色収差の実装を行っています。 基本的な光源計算には OpenLit を使っており、効果の実装に集中しています。 最初のバージョンでは RGB チャンネルごとに別のパスで描画していたのを 2 パスだけを使うように改修できたことで表示品質の向上と描画負荷の減少を同時に達成しました。

現状グリッチエフェクトは vertex ステージで頂点座標をずらすことで実装しているためメッシュが荒い場合に表示の崩れが気になってしまう問題があります。 そのため geometry ステージでの処理に改修したいという気持ちがあります。

Delichon

TypeScript

npm, Deno のプロジェクトで使用している依存関係の更新をチェックする CLI ツールで、手動で実行できる Dependabot や Renovate のようなものです。 オプションを付けることでそのままバージョンを書き換えることができたりします。 このために JSON の空白を保ったまま編集するためのライブラリを作りました。 内部構造は環境レベル (Deno or npm など)、設定ファイルレベル (import map or deps.ts など)、指定子レベル (バージョン名 or URL など) で設定できるようになっており、拡張性が高いため定義さえすれば python や rust など他の環境に対しても使えます。

プロジェクトや特にライブラリの数が増えてきて、依存しているパッケージをアップデートする作業が大変になってきたため作成しました。

ぱっと更新を確認できて便利ですが結局 CI ツールのほうがいいなとなっています。