Closed53

[{開発日記}]19歳エンジニア、ReactでUIコンポーネントライブラリをつくる

tomixytomixy

5月31日

動機はなんだったかな。

高校時代は個人開発ばかりやっていたから、バックエンドとフロントエンドのどっちの道に進みたいかなんか決まっていなくてね…
圧倒的にバックエンドの方が得意な気はしていたけれど、どっちもそこそこ好きだったし、フルスタックエンジニアになりたいととりあえず言っておいていた時期がある。

ただ、いろいろあってフロントエンドのプロになりたいと強く思うようになった。
まあきっかけとしては、前職でHTML/CSSの社内勉強会講師を担当したことだろうな。
なんというか、人間に寄り添う仕事がしたいのだ。ハードウェアの都合よりも、人間の都合をひたすら考えたい。適当に済ませたらすぐにユーザにバレて不満に直結する緊張感が追求欲を掻き立てる。ここに情熱を置きたいと思った。

tomixytomixy

2022年6月13日〜26日

諸事情あってPCが使えなかったこの期間は、ひたすら本を読み漁った。

・HTML仕様書
・インクルーシブデザイン
・Atomic Design
・TypeScript
・CSS設計
・OOUIデザイン
・アクセシビリティ(WAI-ARIA)
・UI/UX

あたりを勉強。

tomixytomixy

2022年6月27日

ようやくPC解禁。
リポジトリを作って、開発環境(というかnpmパッケージの雛形?)をセットアップ。

tomixytomixy

2022年6月28日〜29日

SimpleMindでコンセプトマップを作った。
このライブラリのコンセプト(実装観点)や網羅したいカテゴリを書き出して、それぞれに属するコンポーネントを挙げていって、propsで実現可能にしたいバリエーションを整理。

かなり膨大…

tomixytomixy

2022年6月29日〜30日

SImpleMindでAtomic Design風コンポーネント実装パターンのチートシートを作成。
これとTSの本を見ながらならかなりサクサクいい感じのコンポーネントを量産できるだろう。

せっかくなので公開したいけど、形式がねえ…

tomixytomixy

2022年6月30日

引き続きSimpleMindで、UIコンポーネントのテスト手法を整理。
フロントエンドのテストって大変なのね…

これもそのうち公開したい。

tomixytomixy

ようやく具体的な実装を考えていく。

パッケージアーキテクチャ

  • yarn1のワークスペースでmonorepo構成とする
  • ディレクトリ構成はお作法がわからないのでmuiを参考にする

props

  • chakra uiに似たシンタックスでスタイルを制御できるようにする(csstypesで型づけ)

スタイリング

  • styled-component

コンポーネントリスト

  • storybook
tomixytomixy

npx lerna bootstrapってpackage増えるたびにやるのかしら。

tomixytomixy

2022年7月1日

ついにコンポーネント実装に入る。
ディレクトリ構成の時点でひたすら悩む…どういうパッケージ分割にすれば使いやすいのか…「あれはどこに入っているんだっけ?」と迷いを持たせたら負けかな。

とりあえず慣れよう。
共通で使うコンポーネントはcoreパッケージにまとめることにする。

tomixytomixy

Lerna覚書

sampleパッケージを内部に作成

npx lerna create sample

特定のパッケージにインストール

cd パッケージ
yarn add -D @types/react-dom

共通パッケージをインストール

yarn add -W --dev @types/react-dom

参照できるように?

npx lerna bootstrap

特定のパッケージのみ実行

lerna run --scope @react-polyhexui/styling-patch build

tomixytomixy

monorepoについて、いろいろと勘違いをしていた。

polyhexUIパッケージ(ルート)を公開するのではなく、その中に作ったパッケージの一つをメインパッケージとして公開するのね…
private: trueを書き忘れていたせいでlernaもyarnも使えない状況に陥っていた汗

tomixytomixy

ちなみにTypeScriptを書いた経験は一切ない。
写経すらせずにいきなりライブラリを書き始める怠け者である。というか、目標がある程度高くないと勉強のモチベーションが上がらないタチなのだ。

tomixytomixy

2022年7月2日

Chakra UI風スタイリング用Propsの定義にあたって、いくつかの問題点が浮上している。

  1. 全CSSプロパティに対応したい
  2. が、csstypeはテキスト用プロパティなどの分類まではしてくれない
  3. CSS modulesというMDNの規格に沿って自動で分類したい
  4. ということで、mdn-dataに含まれるJSONデータをgroupsの値ごとに分類しよう

この実現のために躍起になっていた昨日。
関数型プログラミングの本を読み漁ったり、TypeScript PlayGroundで型パズルを試したりと、スマートな記法を模索していたが、結局疲れ果てていつの間にか寝ていた。

朝起きたら頭痛が酷かったので、もうJSON解析ライブラリを探す方向に切り替える。

そうしたら見つけた…JSONをSQL likeに解析するライブラリを!!!!!
https://keizokuma.com/alasql-usage/

tomixytomixy

ちなみに途中までいじったTypeScript PlayGroundを一応Save目的で貼っておく。

https://www.typescriptlang.org/ja/play?noEmitOnError=true#code/MYewdgzgLgBAhlASgVwDYFMIBFkFsAOMAvDAN4BQMMARAALAAWcAThOlNQFxmVU0QBPMFDgAPLjXpNW7GAB1qAHkYs2UAHwKA3NQA0vKtQDmzEMnwQJAbQN9qAYQDKjmPelrLtgLr6+-EVDIltzU0HBgACYsEXq21LgRYAD6yMyoEtQMUFAWnAD0eRHoAG7oqCD46MwAdLggAF4AlqiocNUgzEaFIMAQeQDq6ABGeU6OeVKq7NS8AL6+kqDIwlUAtNACGBIUfqFCIuIh9GYrzOtQm+gwyidQaxsYq2BwuOjqZHJgVFYwgtDouG4N2WdzOfzuuHUWhgXhgAB84Z9vr8BLghiBUBAgUtTutUejMVCYfDEV8YD84BEIo0oI1Sni0RisddKdTafTBIzCdDYQikeSYGB0EYEHT0EChSL2egGQSibzScj8Mx0AAzRqiIHKtUa+Uk-k-CDIVXqzXXI0m3U8-Vkn7McJGcXXe1gR16vm2mD4Sla1mNV3uxUCiCVOAAa1WcGZihD6HDkYggYNMFVcBaQzgwDD2Nu9wuj2er3ln1msV2-tBqeAmGstkMY3sueYjnz6BQW28C0MJjMFlrfnrzlcTZgLcunj8PjiRQgwGYjXwUA6wR4A5ofulsqZ2zrdkEwjEGR+igrwqq7wAZBfzfiMe8vABiMtrmivalwDJp9JdgfUf00xo0wyMA8jgGAAAoVQAR2QRoVQiABKZ812oSpmGrA9HRXagwBAZDf1AAhkDuGIQijRxKmARp1XQGIf12DoimYDJGKqAB5VUAEF8FDF1q3w3YwkCbCwkiaIZjXeZd2oVN00zMMdxffh9kPEJgVxB4ZULN4BLsN9AM-FpdMMf9aSAkIiio3AgPouw0IwkQsOAvDbMMQj8GI2jPwgCj0Comi6N3QxWOYkJlkaGD0DY5gmOM-wECCDJRKiGKJIHKSUMlUVSkUl89gPQ4aBjW9UHeYquXUAB+OL4logyyKM1yaFMwD0hCBRVgUGAGAEfAGHQMBVlwf1Eqa1CqgcuAnJCXCavczzSPXHzKOoxovLGkKWJi9iuJ4uM+PQGqhMSkJkvE3cMt-b1Fp2PL9wODITxWR1mEva9yrlGr9I-BrvyC5qwAA8yaAABnkagFBq+yBscmsZpc-7qHmkjvN8-y1sCpTqE2sLAci6LYrG46RJEMTUou2zUJVU1cpQ+7VKKzlPrG77DL+rGWuBiHuahiaYamuGaFmsbka8sjlr81b1sRnGaHC-HtuYomAhO+KyZiCnpJdR1ad-enCuoH5j1PF73jhGB-XVQG7hhUgACZZhhB94XgYiEax1nfpqzm2vXN3efQ-npqF928tFxbqHIlaApq2XscVjjuN48J+OVhKSfCFKNckynY3jKNdcElSDbgN2XaGNAMCgCAXbAPAhiqGvzYAd0YpvfkqFpVjMWBzfU0FzkuJ4Xh0lm6p+9dGsRn3P39sbocwwWcNDlDw9R6OMdjxWMnl5Aou3tPhKS0ms7SvxLsE40ae4W66eLx6mbvL7x7Z73rdajIFGqcGA8m4Pl7miAIiKNxZoylpjPKcdd770JojYmx9M7nRztJR+mJC57nvmpVB6gADUz93yvzGjPGaoEILQVgvBJC88+aL2wsLRGa9QEb2lljOOIVE57RYCnQ6h9VahBPkg9KucBD-FwOgww+sMjAAEMAVAjRgC1zwFUeRLs0x9TgA3WkCjzaoJUebDcYoXY-FNLRa4JtzyVWJObH46BRB3EiDXfueYh7aXvPg+qk92Z5WIcpLk8jf5ByXvQrGjClpgJjhtbeuMIp7wJlUI6KsM7qzPnwWYtgL7xSPqdARqUfzxESCkNIGQsg5CxAUIopRyhoVqA0ZorR2idG6L0AYwxRjOAmDiAemk0qXToBEHoSjhC63yg9I4-TgCDNgMeVIpUXYzNWNqU04EYxQHnAGBCLt+nWX9Ms6AayjDqA2ebb6qxxmTN2as-0ByjkwBVI6UQ+ALn7MOU7D4ZJFA9nMKsZgaAZTogiAITQYBSx5NPMwKsgsbAoTGAAcVMOYK57YeHSQbOANkjRwBIpSVOXYny+zcChb+MYw5IgAXAGmGASKJx8BxXuRJzlIA5IgYYBIyQZnFOyLkcpJQygVCqDUpoLQ2gdC6OMvogwRhjAmGc14Qy5gLDoKqcAUBVgQuGZIo4SrhCqszFcUgyYtUqtTMNVAAggTGuaAIYeRZrQemRBAdCQIHXACTJ6cKoAijfIdE6RQ7qQCeu1m8W1QZjHKtWMUFggFhDmrDRG+c4QNDBoNWG1UcZAgqnWOwWkrpoyGtVWm1IMo1DZqMImJNno81xsArScAmbshXNzbGyNopa3Foba65Eea9nsEYDG7V3aoCMA7QKPNzd0CNCMFkPtKqx0TqyMO0N-bWzTsHhgBdvxGj1BlJSAAVkEKATrN3boiHu6A66owOW7qUZg84ihAgvTDK9VRb1BqsSGmAM5L0gGvS+oEn7H3fufY0Io665FClWCKfAT6b3AZ9WBmUkHoMvuLMC-Cf5TgQpXIS3YYwABiyrcO6qxZ2OIeKsMoqHPh4Q1KqC0sMJ++ci5lzoMjrOADP7YPiOUgVZyzBrKoBdooBesN1DuInpHKeHN35c1wnxmyiNhMC2wlHSWESGFAI8iAsJzDmW-jYQnXaycwCpzgfSkIti0KNFlSIdImsUL-u1YBmDRQuMjIZsvOTAm+6KbdGJwh09pO+w8-xgJtD16qc3iLDTC1wvoxYZAqJNB2GGf2twhJ6cMgWeUdZoCdnfx5upCGVoAhXMapoBSMu5shjlCzC7CAzc4CEHNrJVAGZavmwqDW54AmvB+ZCMURoRp5NYx80Elev4fGRznup4BYttMRfiyhKBeNYkH1Mxl8zDzsv81s8glCeaLWmtK5goqh2rWuL654t+QMgsgTApBdAME4K0SoQpmhsM6Hjd2KE1j4TIsy0S9QaBcSlbrayWrU+eXdgHYLRmttObjs8fhp5wTqaEoZscuGtMe91BPjHgQr2RDAu8ZC9QwOYX4aANmxHFTcXdMMUB8lpOqXjPIqxvA7JiDyZ7fy02+NXW60lpXLfPWJ3gsUuseaS5AZrh1zROeJ2l2JNeJQpN2TpO3vk4+85Knmm5u-Z01vWKIQmecIOul8H-CufZyEdJLtqye0MER6M07YaB2MEjEMCAGJPLqFIAARl0A7JXX5rtmVux0DXI33tKZ11F6nsXwFG-idEhWsD2dmYh4I8+lN7eXGd+59XEuLY2b0TAEA1WYlXEUA6NdpAQZB5BYjT2V2ic3ZJ8NvKo3Pu65i0whb9O7DLaryDi3fCzrc9t-tsNs7J0cBvnAsXihR3jrn5773qBfcB8byHyT3jifI6j13mP-9glh2i1pg3A-k+hTlitmB8TeFJMhzz6HfOo3z9XHdMXRevOCnAFcJLjcLgHUINHIkYFjpvpgGbH3IVnOOwBis8MwFauAZAXvImCSNcAwINkuPOMAGmKsKgRGlARgQiNcKACsHYsgAQWmCqsQegTAb8PmDgfIssmjumjKPQVpCPC8mQdgdAB0PIgQUqnxk3Doq2GoGwbDpwdjtwUWA+EcubCoPaMAAPFWgmlIejjIVAdam8AoZgfVlGAwJoRwWgXIW8IoeXswNpNRiYYWmYbobwfomALhAEIgXYRmlwY4ZYYaPxqgKsPgRYKol3BAH4QEY1u3JULSHcOEUEfol3FETSDKIEe3O6lGIATANEWBhASkW+rLkongaqhOvYVwaQX3HLsosAOsN6FRK6GYWUfka8IUaqCoQLqUYwYxP6BLjoq0BAP1BEKsFuqYJgYoHGNAAmFGljvGtqu0SMWMSqlGJMc3MBlAAwPUYwT8kMAIDCLvirhNgfiHJ5qFtrpTvHnrjThLHTjfltExBwkZiZhnhtlnpPjntJPBhBo1khpxgvuzj-pHsXkJifqPM3i-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-MMhBOcs9tcdddeecFehX9cZB89BeC+hfhe0Ri-MCzfzf0-hNLewNI8jsK+o-suMdEffdiIa8bPub7flH1wK7eZAl696QG9GPUAk9E9k93fm8vjU-ob4fW+Nf-yfex+-ej3MAA9lDA9u+g9Ljg9gCQ9Shigw9Anw8wNp5y-wMR-Z5KfSSCH8xdZ7eyMqP2jqOGmaOBFycE+2mXdG-Xek+3cU84d0NPdW92d0cQsZB29eOOBHocTfUnALNGCn-iCN98mQMJdcOy+g79-P6D-pcoRBBrCx+4tV-NYag0uZ+L959c+hvfPuvys6W8uYn-cvkvAP4gAj+W6E-mfyuSX8QeN-B5oi0S6h8++jLZ-i8SH7kMXeOPdUOp3QYVMk+ODZVGoy+BKEnMJXQAcb1M6ekzelPIvuAKCw783u9HFhrxzYZclb+0vEPr30f7YDKykfPAb+DV5O54+fdTTgQJ16wc9OUjc1gZwAHACgB2fNfhZ2YF-1WB29ANqrw64VdMuEgsAbvwpZL8Veg0YwaTyMEGDHu5+PjgRwE76CcuTuawRR2MF2DHmfpUsgGWxIiCX+SvSmJ-ykHcZSBsjRQId1T7XBTutA1QfQJu5MCN+tHLftCyl7QkgS2ESvjt1wDV9-uvzevigLp4t82+0PZgEL0Xjd8gcy3LAatxwG-1UkCTcPnUJ5aE10m-LTJqG19pztqaYrIKtrx6TkA0k5ACWtWFcCEMsACAOAN9QEAmVEgX+fWNwHxaugFgZGbgJxBvR7VdmAYBYMTEWHp0Fg1dPYfsgWBWBswTBfZF4COEI0OsQwHdH5F7gesNhAgLYQckGHkByABQC4JUDGHYBJh0w2YWAAABS3uL4O8NVDLBVCiBL0PIjDCKAPGugGAFVRgC2J7EEQGuElRACqgYAHjdQOBBKa5BsRCIpKtwCqobJboKodNF8HxEQBThUVLwG8PIBfCrguGZoKCDhEIjky7wEgLdCsBIj-QMADEViI8aXDsRvI2ECiIGhoiYAyZGAJYiRHcAhQ16QYbSIECYjsR9Ixkb1CuAth9kzVCAHCK5EwAWRqANkfCPOF7NNR3wnUVcnWH2gBAeog0cQCNGsiqg7Ix4XaJeHqB1Aloq4PYHGF-CZhiQB0TiKdHGjTRCIv0b8JED-DEg3on0aOHTq2i9qNcEgMvRhGKBIxEw6MYGLAAIjrRroJMfaKir6jMxAYgEV6PeEUFoA8AJAFbUjHvUki9oTEE6LYi3D7h1QJKhAHAgIAqUOAAgAhErHgBqxPYusYQydEjih29YxsWmAgC1BGs4EcCElQRGNANkRAd4OBFui9BmQS43gHim4ATjMAfY-AFYEaBeBqgeKQYQhAHHkAqxGIdANUHKBGBuxtYycYQwbGggZx14goFWNgAV4d0MzKMJxBfGYApxH41AM1XHHASIAoEqoDOOqDwRkA1YBcX+KJFRVVxebD4XkCoB-iOxUVJ0SDEwlUAKRqQL4H+MwnzAyAswAcQUEwm3iMAD4kAE+L-EASIAQEqlDBKbHNUvxeQJkdKLADohRAjokgB42RF2JJRNcUgOSD5FfBBR6o7gJbCooyjHYliGUQqJ5TMBMJ8Yg8RAGTKAxwATopkWqO0kcSZxVgSIcwA1EiEIIP48kElVhBqjWxdw1QtUBhg2Uux2ko8VeK-wFAq4-+ZuPuKgm6SoRJAJKrwG-FDi7xDEp8YZKxG4Rm4A4qgOFMgCRTHxeImEWlKzDPjexeAfAAiOMlvjpxmIBCAiIADkZGEqVeK0CDCgAA

tomixytomixy

2022年7月3日

CSSsyntaxの分類も終わり、いよいよpropsを作成してcomponentから呼び出せるようにするフェーズへ突入。
その辺りはコンポーネントを実装しながら作っていった方が良さげ。とりあえずStorybookとReactの環境を作らないとね。

https://github.com/tetracalibers/React-polyhexUI/commit/9b3ae4205d35a519dc3943e4a35be7bd8874b5df

StyletronのこのAPIを使ってどうにかカスタムPropsのスタイルを定義できないかな。と画策中。

instance.renderStyle({
  color: "red",
  fontSize: "12px",
});
// → "css-abc"

https://styletron.org/api-reference

セレクタの変換にはこのパッケージを使おうと思ってる。
https://www.npmjs.com/package/case

tomixytomixy

本当にPropsでのスタイリングでいいのか?と考えてみたり。
どんどんタグが汚れていくのはinline styleやutility firstと変わらない。

かといってstyled-componentも対応関係を追うのが面倒くさい。
CSS modulesはなんか怖い。(非推奨予告も出てるし)
JSXに投入するの(JSS?)もなんか嫌だ…

っていうか、私はそもそもにしてVueよりReact派ではあるが、スタイリングに関しては圧倒的にVueに魅入っている。
JSとHTMLを融合させるメリットは大いに感じるし、テンプレートエンジンなんかよりもJSXの方が圧倒的に良きソリューションだとは思うのだが、そこにCSSまでぶちこもうとするのは個人的に地雷(受け付けない代物)なんだ。

せめてCSSは分けたい。でもどうにかタグ構造との対応をわかりやすくしたい。

そうやって云々と考えていたら、降りてきた。
こんな感じでReactのスタイリングができたらなあ〜という新記法を思いついた。

自分の力でまず取り組んでみたいアイデアなので、写真は非公開

右がJSX、左がXML拡張の独自記法
これでJS+HTML(JSX)とCSSの分離が実現される & デザイン的にも対応がわかりやすい
( ※JSで変化させるCSSはこれまで通りJSXに書くことになるが…)

技術的に実現できないかちょっと真剣に考えている
最低限欲しい要件としては

  • 右のHTMLコードから左のコードの雛形をコマンドで作成できるようにする
  • watchモードのCLIツールを作成して、右のHTML構造を変更したら左にも反映されるようにする
  • StylePatchタグ内全体にハッシュ値のクラス名を付与して、タグ内に定義したCSSがタグ外の要素に影響しないようにする(この辺りはStyletronとかの力も借りつつ。)
  • StylePatchタグ内ではカスケードされる(1つのpにmarginを設定したらすべてのpにmarginが継承される、といった具合。ただしclass付の場合はその要素のみ、明示的にその要素のみに適用させたい場合は!localをつける)

もちろんParserとCLIツールは自分で書かないといかん。
この案の実現に向けてちょっと全力投球してみたい。(どんどん薄くなるUIコンポーネントの影…)

tomixytomixy

カリー化やクロージャは感覚でなんとなく使っているけど、これから重い処理を書くわけだしもっと綺麗なコードが書けるようになりたいにゃ

というわけで一旦開発は中断して、関数型プログラミングの基礎を読むことにするにゃ

構文解析木やドロドロ正規表現解析の綺麗な書き方についてもしっかり勉強するにゃ…

https://akimichi.github.io/functionaljs/
https://github.com/akimichi/functionaljs

tomixytomixy

2022年7月4日

TypeScript移植をするには知識が足りなさすぎる…
本当につくづく思う、TypeScriptの世界ではまだまだスライムしか倒せないひよっこなのだ。

というわけでちゃんと勉強しよう。
プログラミングTypeScriptを読むにゃ。

tomixytomixy

写経を楽しめるのは(プログラマ歴的に)若いうちの特権だよなあ。
出てくる出てくる。うわその記法使うんか…とか。
おかげで改造なり移植なり工夫を凝らさないとろくに楽しめなくなってしまった。

初級者卒業の一つの基準は、自分の頭で考えるようになることかもね。
大人にならないと批判心は生まれないのと同じ。

というか、新しい言語やパラダイムに触れる時も、カタログ感覚でチラ見しつつ何か作ればいいじゃんと思ってしまう。
どうせ経験済みのツールとの差分を捉えるだけだから、入門書なんて退屈で冗長で読んでられないのよね。

もうその域まで来たんだな。そろそろ中級者を名乗ってもいいだろうか。

tomixytomixy

てなわけで独自開発に戻るにゃ
って言ってもまだまだ下調べも必要だけどね

tomixytomixy

doc - Markdownでコンポーネントの使用法とプロパティを文書化する

actions - インタラクティブ要素でアクションが実行されたときにUIフィードバックを取得

viewport - ビューポートのサイズと向きを調整して、レスポンシブコンポーネントを構築

controls - UIでコンポーネント入力を動的に操作する

backgrounds - 背景を切り替えて、さまざまな設定でコンポーネントを表示

toolbars - レンダリングを制御する独自のツールバーアイテムを作成

storysourse - storrybookの画面からstoryを確認

storybook-addon-html - コンポーネントのHTMLタグを確認

StoryShots - Jestを使用して、すべてのストーリーのコードスナップショットを自動的に作成

Accessibility - コンポーネントがWebアクセシビリティ標準に準拠しているかどうかをテスト

Console - ログ、エラー、警告などのコンソール出力を表示

tomixytomixy

戦いが終わった。
結局ビルド構成はrollup + esbuild-pluginに落ち着きました。

tomixytomixy

いよいよコンポーネントを作りつつ、独自のスタイリングシステムを実装していく。
独自Providerで駆動するシステムということもあり、いろいろとアプリでの挙動が気になるので、create-react-appによる実地検証環境も構築した。

tomixytomixy

2022年7月6日

昨日はなんか自動化スクリプトを書いて終わったw

そろそろプログラミング再開してから一週間が経つかな?
一週間の疲労と不摂生が祟って、18時には眠っちゃいました。

そして今日は手始めにmerge自動化スクリプトを書いた…んだけど、これVScode拡張入れればGUI操作でストレスなくできたかも…?
まあいいや、私はとりあえずzxを使いたかったのだ笑

tomixytomixy

関数型もいいけど、大規模になるとやっぱりオブジェクト指向で書きたくなるよな
構成はOOP、局所的なデザインは関数型…くらいがちょうどいいかも

tomixytomixy

2022年7月7日

独自スタイリング記法をCSSに変換するプリプロセッサを書いているが、ネスト対応がうまくいかないの図
output

再帰でネスト対応するよりも最初からネスト部分を分離して別途処理した方がいい気が

デカいプログラムを書く時は各モジュールの枠組みをオブジェクト指向で書きたくなるが、その内部の各メソッドを洗練させるには関数型プログラミングが不可欠…という役割分担を意識すればだいぶ綺麗なコードが書けるんじゃないかという独自理論

とりあえずいい加減に関数型(LodashとRamda)を勉強しつつUtilityツールを作っては実装イメージを固めていきたい。

tomixytomixy

プログラミング言語の中のHaskell、物理学でいう量子力学的なポジションなのでは(難易度的に)

tomixytomixy

2022年7月9日

ようやく使えるようになってきたparsar-ts…!!
意外と簡単にパーサが書けて驚いている…

tomixytomixy

2022年7月11日

更新が滞っているのには事情がある。
ちょっと疲れてしまったのだ。私は関数型の沼に引き摺り込まれている。

まあいいや。冷静になって描き直そうと思うから、落書きの痕跡はここに貼っておくよ。

import * as S from 'parser-ts/string'
import * as P from 'parser-ts/Parser'
import * as C from 'parser-ts/char'
import { run } from 'parser-ts/code-frame'
import { apply, pipe } from 'fp-ts/function'

import { map, foldMap } from 'fp-ts/Array'
import { match } from 'ts-pattern'
import _ from 'lodash'
import shell from 'shelljs'
const { ShellString } = shell
import jsonFormat from 'json-format'
const config_jsonFormat = {
  type: 'space',
  size: 2,
}

const styp = `<StylePatch>
  <span>
    {{
      backgroundColor: '#1a1a1a',
      borderRadius: '2px',
      color: 'white',
      display: 'inline-block',
      fontSize: '0.8rem',
      padding: '0.4rem 0.5rem',
      position: 'relative',
      __after: {
        borderColor: '#1a1a1a transparent transparent transparent',
        borderStyle: 'solid',
        borderWidth: '3px 3px 0 3px',
        bottom: '0',
        content: '',
        display: 'block',
        height: '0',
        left: '50%',
        position: 'absolute',
        transform: 'translate(-50%, 100%)',
        width: '0',
      },
    }}
  </span>
</StylePatch>
`.replaceAll(/[\r\n\t]/g, '')

interface AstNode extends Record<string, string> {
  readonly kind:
    | 'OPEN_componentTag'
    | 'OPEN_htmlTag'
    | 'CLOSE_tag'
    | 'CSS_property'
    | 'CSS_value'
    | 'START_css'
    | 'END_css'
    | 'BEGIN_nesting'
    | 'END_nesting'
    | 'BEGIN_stypFile'
    | 'END_stypFile'
    | 'empty'
  readonly value: string
}

type Ast = Array<AstNode>

const spaceTrim = P.surroundedBy(S.spaces)

const startFile: P.Parser<string, AstNode> = pipe(
  S.string('<StylePatch>'),
  P.map(value => ({
    kind: 'BEGIN_stypFile',
    value: value.slice(1, -1),
  }))
)

const endFile: P.Parser<string, AstNode> = pipe(
  S.string('</StylePatch>'),
  P.map(value => ({
    kind: 'END_stypFile',
    value: value.slice(2, -1),
  }))
)

const openingComponentTag: P.Parser<string, AstNode> = pipe(
  P.manyTill(
    pipe(
      C.char('<'),
      P.alt(() => C.letter)
    ),
    C.char('>')
  ),
  P.map(value => {
    return {
      kind: 'OPEN_componentTag',
      value: value.slice(1).join(''),
    }
  })
)

const openingHtmlTag: P.Parser<string, AstNode> = pipe(
  P.manyTill(
    pipe(
      C.char('<'),
      P.alt(() => C.lower)
    ),
    C.char('>')
  ),
  P.map(value => {
    return {
      kind: 'OPEN_htmlTag',
      value: value.slice(1).join(''),
    }
  })
)

const closingTag: P.Parser<string, AstNode> = pipe(
  P.manyTill(
    pipe(
      S.string('</'),
      P.alt(() => C.letter)
    ),
    C.char('>')
  ),
  P.map(value => ({
    kind: 'CLOSE_tag',
    value: value.slice(1).join(''),
  }))
)

const cssProperty: P.Parser<string, AstNode> = pipe(
  P.manyTill(
    pipe(
      C.letter,
      P.alt(() => C.oneOf(["'", '_'].join('')))
    ),
    C.char(':')
  ),
  P.map(value => ({
    kind: 'CSS_property',
    value: value.join(''),
  }))
)

const cssPropertyValue: P.Parser<string, AstNode> = pipe(
  P.manyTill(
    pipe(
      C.alphanum,
      P.alt(() =>
        C.oneOf(
          ["'", '"', ':', '#', '.', '-', '%', ' ', '(', ')', ','].join('')
        )
      )
    ),
    S.string("',")
  ),
  P.map(value => ({
    kind: 'CSS_value',
    value: value.join('').replaceAll(/\'/g, ''),
  }))
)

const startStylePatch: P.Parser<string, AstNode> = pipe(
  S.string('{{'),
  P.map(value => ({
    kind: 'START_css',
    value: value,
  }))
)

const endStylePatch: P.Parser<string, AstNode> = pipe(
  S.string('}}'),
  P.map(value => ({
    kind: 'END_css',
    value: value,
  }))
)

const startNestCss: P.Parser<string, AstNode> = pipe(
  S.string('{'),
  P.map(value => ({
    kind: 'BEGIN_nesting',
    value: value,
  }))
)

const endNestCss: P.Parser<string, AstNode> = pipe(
  S.string('},'),
  P.alt(() => C.char('}')),
  P.map(value => ({
    kind: 'END_nesting',
    value: value.replaceAll(',', ''),
  }))
)

const parser: P.Parser<string, AstNode> = pipe(
  startFile,
  P.alt(() => spaceTrim(endFile)),
  P.alt(() => spaceTrim(openingHtmlTag)),
  P.alt(() => spaceTrim(openingComponentTag)),
  P.alt(() => spaceTrim(closingTag)),
  P.alt(() => spaceTrim(cssProperty)),
  P.alt(() => spaceTrim(cssPropertyValue)),
  P.alt(() => spaceTrim(startStylePatch)),
  P.alt(() => spaceTrim(endStylePatch)),
  P.alt(() => spaceTrim(startNestCss)),
  P.alt(() => spaceTrim(endNestCss)),
  P.map(value => {
    return value
  })
)

const result = run(P.many(parser), styp)

const ast = result._tag === 'Right' ? result.right : []

import * as Ar from 'fp-ts/Array'

import { last } from 'fp-ts/Semigroup'
import { Foldable, zip } from 'fp-ts/Array'
import { identity } from 'fp-ts/function'
import { fromFoldableMap } from 'fp-ts/Record'

import * as Rec from 'fp-ts/Record'
import { Magma } from 'fp-ts/Magma'

const m2: Magma<string> = { concat: (x: string) => x }

type AstNodeAddID = {
  id: string
  forest?: Array<TREE.Tree<unknown>>
} & AstNode

const syntaxRecords: Array<Record<string, string>> = pipe(
  ast,
  Ar.mapWithIndex((i: number, record: Record<string, string>) =>
    Rec.union(m2)(record)({ id: `${i}` })
  )
)

const parseAreaSyntaxRecord = pipe(
  syntaxRecords,
  Ar.dropRight(1),
  Ar.dropLeft(1)
) as Array<AstNodeAddID>

import * as TREE from 'fp-ts/Tree'
import * as ARRAY from 'fp-ts/Array'
import * as E from 'fp-ts/Either'
import * as STATE from 'fp-ts/State'

const zipObject =
  <K extends string, A>(keys: Array<K>) =>
  (values: Array<A>): Record<K, A> =>
    fromFoldableMap(last<A>(), Foldable)(zip(keys, values), identity)

type BuildStatus = E.Either<
  TREE.Forest<AstNodeAddID> | never,
  TREE.Forest<AstNodeAddID> | never
>

type AstTree = STATE.State<
  BuildStatus,
  TREE.Tree<{ readonly value: AstNodeAddID } | AstNodeAddID>
>

const castTree = pipe(
  parseAreaSyntaxRecord,
  ARRAY.map(r => zipObject(['value', 'forest'])([r, []]))
) as TREE.Forest<AstNodeAddID>

const isolateTarget = (records: TREE.Forest<AstNodeAddID>) => {
  const [[{ value }], forest] = pipe(records, ARRAY.splitAt(1))
  const test = TREE.fold(
    (root: AstNodeAddID, forest: TREE.Forest<AstNodeAddID>) =>
      TREE.make(root, forest)
  )
  const res = test({
    value: value,
    forest: forest,
  })
  return res
}

import * as NUMBER from 'fp-ts/number'

const matchAnyOne = (s: string) => (ary: Array<string>) => {
  const matchCount = pipe(
    ary,
    ARRAY.filter((item: string) => s === item),
    ARRAY.size
  )
  return !NUMBER.Eq.equals(matchCount, 0)
}

const nestStartKeywords = ['OPEN_htmlTag', 'START_css', 'BEGIN_nesting']

// prettier-ignore
const matchController = 
  (forest: TREE.Forest<AstNodeAddID>): AstTree => {
    const { kind, id } = forest[0].value
    return pipe(
      kind,
      E.fromPredicate(
        kind => matchAnyOne(kind)(nestStartKeywords),
        () => {}
      ),
      E.match(
        () => ((_s: BuildStatus) => [TREE.bindTo('value')(forest[0]), E.left(forest.slice(1))]),
        () => {
          const nextTarget = pipe(
            forest,
            isolateTarget,
          )
          //console.log(currTree)
          return (_s: BuildStatus) => [TREE.bindTo('value')(nextTarget), E.right(nextTarget.forest)]
        }
      )
    )
  }

const astBuildController = (state: AstTree): AstTree => {
  const [currTree, status] = state(E.right(castTree))
  return pipe(
    status,
    E.match(
      () => state,
      prevForest => {
        return pipe(prevForest, matchController, astBuildController)
      }
    )
  )
}

//const [t1, t2] = STATE.traverseArray(matchController)([E.right(castTree)])

const [output, finalStatus] = astBuildController(() => [
  castTree[0],
  E.right(castTree.slice(1)),
])(E.right(castTree))

const test = parseAreaSyntaxRecord

console.log(test)
//
console.log(jsonFormat(test, config_jsonFormat))

new ShellString(jsonFormat(test, config_jsonFormat)).to('tmp/buildinfTree.json')

// prettier-ignore
const css = pipe(
  ast as Ast,
  map(({ kind, value }: AstNode) => {
    return match(kind)
      .with('BEGIN_stypFile', () => '')
      .with('END_stypFile', () => '')
      .with('OPEN_componentTag', () => value + '{')
      .with('OPEN_htmlTag', () => value + '{')
      .with('CLOSE_tag', () => '}')
      .with('CSS_property', () => value + ':')
      .with('CSS_value', () => `"${value}"` + ';')
      .with('START_css', () => '')
      .with('END_css', () => '')
      .with('BEGIN_nesting', () => value)
      .with('END_nesting', () => value)
      .exhaustive
    }
  ),
  foldMap({ concat: (a: string, b: string) => a + b, empty: '' })((s: string) => s)
)

const normalizedCss = css
  .replaceAll('_at_', '@')
  .replaceAll('__', '&::')
  .replaceAll('_', '&:')

new ShellString(normalizedCss).to('tmp/pseudoCSS.scss')
tomixytomixy

2022年7月12日

一旦不慣れな関数型Syntaxを封印して本気出したら、独自記法からの変換処理(パーサーとレキサー)も4時間で書き終えちゃった件。

オリジナルのReactスタイリングシステム、残りの工程はCLIコマンド作成だ。

  • .prettierignoreとかを作成して環境整備するコマンドstyp setup
  • jsxからスタイリングファイルの雛形を生成するコマンドstyp init
  • スタイリングファイルに書いたスタイルをJSXに反映させるコマンドstyp patch
  • jsxの変更をスタイリングファイルと同期させるコマンドstyp watch --from=jsx
  • stypファイルの変更をjsxファイルに反映させるコマンドstyp watch --from=styp

その前にちょっとリファクタしてマシにするかー。モジュールとして分離もしないとだし

tomixytomixy

モックアップを描くのって大事だ。
これからはとりあえず実現させてからコードのデザインをどうにかするスタンスでいこう。
最初から綺麗なコードを描けたらいいんだけど、残念なことにどうにも私はまだその域には達していないらしい。

tomixytomixy

2022年7月14日

不要になったごみにゃ

const parse =
  (stopKeyword: string) =>
  (buffer: Buffer): ParseResult => {
    let prevChar: Array<C.Char> = Array(stopKeyword.length)
    return pipe(
      buffer,
      STREAM.stream,
      P.takeUntil((c: C.Char) => {
        prevChar.push(c)
        //console.log(prevChar)
        return prevChar.join('') === stopKeyword
      }),
      EITHER.match(
        () => ({
          rest: buffer,
          found: '',
        }),
        obj => {
          const [stop, ...rest] = ARRAY.dropLeft(obj.next.cursor)(buffer)
          const findString = obj.value.join('') + stop
          return {
            rest: ARRAY.dropLeftWhile((c: C.Char) => c === ' ')(rest),
            found: findString,
          }
        }
      )
    )
  }

const finder = {
  beginFile: parse('<StylePatch>'),
  endFile: parse('</StylePatch>'),
  tagStart: parse('<'),
  tagEnd: parse('>'),
  beginCss: parse('{'),
  endCss: parse('}'),
  endProperty: parse(':'),
  endValue: parse(','),
}

let tokensArray: Array<string> = []

const nextParse = (rest: Buffer) => {
  return (
    match(_.first(rest))
      .with('<', () => parse('>')(rest))
      //.with('{', () => parse('{')(rest))
      //.with("'", () => finder.endValue(rest))
      .otherwise(() => parse('}}')(rest))
  )
}

const saveContinued = (buffer: Buffer): Buffer => {
  const { rest, found } = nextParse(buffer)
  //console.log(_.first(rest), found)
  tokensArray.push(found)
  return _.isEmpty(rest) ? [] : saveContinued(rest)
}

pipe([...rawStyp], finder.beginFile, obj => obj.rest, saveContinued)

console.log(tokensArray, '\n')
tomixytomixy

2022年7月15日

import * as STORE from 'fp-ts/Store'
import * as STATE from 'fp-ts/State'
import * as TRACED from 'fp-ts/Traced'

interface Token {
  type: string
  value: string
}

interface Parser {
  // 現在の位置のトークンを取得
  readonly peek: (pos: number) => Token
  // 現在どこまで読んだか
  readonly pos: number
}

const tokenSequenceParser: Parser = {
  peek: (pos: number) => tokenSequence[pos],
  pos: 0,
}

const ast = []

const astNodeBuilder = {
  tag: () => {
    /**
      map: <A, B>(f: (a: A) => B) => <E>(fa: State<E, A>) => State<E, B>
     */
    const pushTokenStack = (tokens: string[]) =>
      tokens.push(tokenSequenceParser.peek(tokenSequenceParser.pos).value)
    const judgeFinish = (isFinished: boolean) => {
      if (isFinished) {
        return [pushTokenStack, isFinished]
      } else {
        return [pushTokenStack, isFinished]
      }
    }
    const stack = STATE.map(pushTokenStack)
    console.log(stack)
  },
}

const pushTokenStack = (tokens: string[]) => {
  tokens.push(tokenSequenceParser.peek(tokenSequenceParser.pos).value)
  return tokens
}
const judgeFinish = (isFinished: boolean) => {
  if (!isFinished) {
    STORE.seeks((pos: number) => pos + 1)(tokenSequenceParser)
    STATE.map(pushTokenStack)(STATE.get())
    console.log(STATE.map(pushTokenStack)(STATE.get())([]))
  }
  return [[], isFinished] as [string[], boolean]
}

const stack = STATE.map(pushTokenStack)
console.log(stack(judgeFinish)(false))

const tokenMatcher = (token: Token, _cursor: number) => {
  return match(token).with({ value: '<', type: 'JSXPunctuator' }, () =>
    astNodeBuilder.tag()
  )
}

const res = tokenSequence.map((token, _cursor) => tokenMatcher(token, _cursor))

//console.log(res)
このスクラップは2023/01/07にクローズされました