node modules なし blog を作っている話

今年の9月頃から,no dependencies で blog を作っています.(devDependencies には,jest と TypeScript を入れています) 正直,フレームワークを使えば blog くらいならすぐ作れるだろうと思っていたのと, フレームワークの使い方を覚えることが自分の成長に大きくつながるとは感じることができず, やっていて楽しい + 学習する余地がありそうな no dependencies で blog を作ることにしました.

リポジトリはこちらです maxmellon/kajitsu

機能要件を考える

ざっくり,自分がほしいなと思った要件を整理すると,

  • markdown で記事を書きたい
  • blog だけじゃなくて cookie や Cache-Controll header などを検証できる sandbox 環境も併設したい
  • Client Side で JavaScript は極力動かさない
  • jsx で,ある程度型が効くように
  • ただシンプルに html を返すだけで良い,それ以外は一旦後回し

みたいな感じになった.なので,上記の要件を満たすために以下のモジュールを自作することにした.

  • node web framework
  • jsx (client side の runtime コードなしでもいい)
  • CSS-in-JS (これは,必要かどうかはおいておいて作ってみたかった)
  • markdown parser

どこまでできたか

node web framework

express に近いようなインターフェースで作ってみた 現状,ヘルパー的な method がとにかく少なく,毎度 Content-Type を自分で書く必要があったりとまだまだ改善の余地がたくさんある. 使い勝手は以下のような感じ

// logger middleware
suica.use(async (_ctx, req, _res, next) => {
  console.log(req.method, req.url)
  await next()
})

suica.use("/", async (_ctx, _req, res) => { 
  res.setHeader("Content-Type", "text/html")
  res.write(`<!DOCTYPE html><html><head></head><body></body></html>`)
  res.end();
});

!(async () => {
  for await (const [req, res] of on(createServer().listen(3000), "request"))
    suica.run(req, res);
})();

post リクエストが来たときに,何もしないと body が Buffer 型で入っており,非常に扱いにくいという点. request header の content-type を見て,その content-type から Readble から chunk をまとめて適切な object に変換する必要があった. 何気なく普段使っている express の body-parser ってそういうものなのねという発見があった.

jsx

client side の レンダリングの実装は間に合わなかった(今後やる予定) jsx 自体を作るのは思っている以上に簡単だったが,どちらかというと html および その周辺の知識が乏しさに気がつくきっかけとなった

React のような設計指針はなく,シンプルに jsx でテンプレートを書きたい程度にしか思っていない.

whatwg html semantics の仕様をみながら,どの要素がなんの属性を持つことができるのかを手を動かしながら学べたのは非常に勉強になった.Global Attributes や,マイクロデータWAI-ARIA,各要素のセマンティクス,正しい html とはなにか,いかに普段我々の書く html が雑なものかを考えさせられるきっかけになった.

CSS-in-JS

これに至っては,なんとなく動くものは作れたが... コンポーネント軸で開発をすすめると セレクタ詳細度 がごちゃついて辛いのはわかるけれどそれを解決するのが CSS-in-JS なのかは割と今でも疑問.

↓ こんな感じで一応普通に使える

export const Header: FC = ({ children }) => (
  <Header>
    {headerContents}
  </Header>
);

const Header = styled('header')`
  position: sticky;
  z-index: 1;
  top: 0;
  background-color: var(--header-color);
  height: 40px;
  color: white;
`

markdown-parser

アドベントカレンダーリリースまでに間に合わなかった. 正規表現でやるような実装にはしたくなく,パーサーを名乗るのであればちゃんと字句解析,意味解析をしようとしている. いまは,簡単な字句解析までができている.

また,jsx で markdown を扱うとき次のように無駄な処理が入ることに課題を感じていた

markdown - [parse] - AST(?) -> [render] -> html -> [parse] -> VDOM - [render] -> html

markdown の AST 定めて その AST を VDOM に変換できれば,無駄にhtml を parseする必要がなくなる. jsx, VDOM を自作しているからこそできることだと思う

markdown - [parse] -> AST -> [process] -> VDOM -> [render] -> html

VDOM を使っているのに,VDOM ではなく,html を パースしてよしなに加工したり,critcal path をあれこれしたりする 無駄なことをしているツールが世の中に多いので,もっと VDOM に親和性が高い linter であったり,optimizer であったりを作る必要があるなと感じている.それには,html を json で表記するような標準の format が必要なのかもしれない?

これからやること

エンハンス開発

  • 自作 jsx によって作られたコンポーネントカタログ (ちょっとだけ動くくらいまで作っている)
  • テストランナーの自作
  • VDOM linter
  • 新しいjsx定義への変更
  • brotli するための middleware (for 自作 node web fromework)
    • zlib に入ったのは本当にありがたい
    • もちろん gzip, deflate への fallback も実装する
  • markdown内の syntax highlighter 実装

ログ基盤の構築

インフラ周りのツールを自作するかどうかは悩んでいるが,もうリリースしてしまったので取り急ぎ fluentd から Cloud Logging にログを流し込めるようにしたい これは,年内にも終わらせる

サーバ監視

mackerel が個人だと無料で使えて良さそう

CSP のレポート先エンドポイントの実装

個人的にどんな CSP違反 が起きているのか見てみたい

感想

html を node で返すというフレームワークを使えば秒でおわりそうなものを,仕様を読み込んだりしながら三ヶ月くらいかけて作ってきました. 何より一番よかったのはすごく楽しかったという点.なかなか,業務ではこういった開発はできないのでとても新鮮でよかったところです. この開発を通して,世の中に出ている ライブラリがどのように作られていたりとか,html の仕様とか学習できたのじゃないかと思います. そして,いかに標準APIを使いこなせていないか,知らないかが明らかになりました. また,ライブラリを採択するときにも,内部実装をみて筋が良いかどうかを判断するというのがこの開発を通して増えてきたと思う. 例えば,正規表現ベースのマークダウン系のライブラリだったり,オブザーバブルパターンを実装するためだけに Proxy を使っているものだったりと,「本当にそれ必要?」ってより具体的に問うことが以前に比べると成長できた点じゃないかなとおもいます.

この開発では,whatwg の仕様を読むことのきっかけにはなったが,肝心の JavaScript の仕様を読むきっかけにはならなかったので, 今後は,JavaScript の仕様にも目を通して手続き一つ一つにも疑問を問いかけながら開発を楽しんでいきたいなと思いました.

そして,仕様を読むきっかけになるというのは初めてスタート地点に立てたことであり,このレベルで満足せずもっと仕様を読み込んで 真の意味で「正しいものを正しく作る」エンジニアを目指していきたい .