にわかプラス

にわかが玄人になることを夢見るサイトです。社会や国際のトレンド、プログラミングや電子工作のことについて勉強していきたいです。

GitのCGALを実行できるようにするまで

GitのCGALを実行できるようにするまで

CGALとは

https://www.cgal.org/
計算幾何学のC++オープンソースライブラリ
一番有名っぽい
ライセンスはGPL/LGPL/Commercial lisence

なぜCGALに手を出したか

  • 幾何学的なことを勉強する必要が出てきた
  • CGALのつかいかたやマニュアルから一般的な幾何学(Computer geometry)の知識取得を目論んだ
    • Demoやマニュアルが充実している
  • Header onlyライブラリであったり、wasm化事例があったり使い勝手が良さそう

調べてもあまり使用事例が出てこない。GDALより汎用的なジャンルな気もするがライセンスが問題なのかあまりヒットしなかった。日本では6社がCommercial lisenceを結んでいるらしい。

環境

Ubuntu22.04(Pop!OS 22.04)

Install

環境構築が終わった後に記憶で書いているため、ぬけもれがたくさんある。 構築しながら書けばよかったと反省。

Git からCGAKをCloneしてexampleを実行してみる。 コマンドは公式を参照した https://doc.cgal.org/latest/Manual/usage.html

cd $HOME/CGAL-6.0.1/examples/Triangulation_2 # go to an example directory
cmake -DCMAKE_BUILD_TYPE=Release . # configure the examples
make # build the examples

Qtがないというエラーが出るので、Qtをインストールする。

HPからオンラインインストーラを取得。深いところにあって分かりづらかった。

# これらも必要だった(経緯は失念)
sudo apt-get install build-essential libgl1-mesa-dev

# Qt6オンラインインストーラで警告が出たのでいかを実行

apt install libxcb-cursor0 libxcb-cursor-dev

Qtのインストール3hくらいかかりそうな勢い。

多分exampleはviewerを必要とするからQtが必要。CUI実行だけならいらないはず。

オンラインインストーラだからQt creatorとかも入ってきてる気がする。

一旦キャンセルしてaptで取得してみた

install qt6-base-dev libqt6opengl6-dev

だけどもビルドに依然としてqt6がないと言われる。しょうがないのでオンラインインストーラから再度インストール。Qt creatorは除いた。

Qt6のインストール完了したが、パスが通ってないのか

➜  Triangulation_2 git:(master) cmake -DCMAKE_BUILD_TYPE=Release . 
CMake Error at /usr/lib/x86_64-linux-gnu/cmake/CGAL/CGALConfig.cmake:117 (message):
  The requested CGAL component Qt6 does not exist!
Call Stack (most recent call first):
  CMakeLists.txt:7 (find_package)

(CGAL/.github/にinstall.shを言うのがあり、その中にQt6があるのでこれをとりあえず実行してみる。たぶん不必要)

   Triangulation_2 cmake -DCGAL_DIR=/home/mr-akami/workspace/tmp/CGAL-6.0.1 -DCMAKE_PREFIX_PATH="/home/<your user>/Qt/6.8.0/gcc_64" -DQt6_DIR="/home/<your user>/Qt/6.8.0/gcc_64/lib/cmake/Qt6" -DCMAKE_BUILD_TYPE=Release .

これでいけた。 Githubからとってきたソースを使う場合は-DCGAL-DIRが必要だったみたいだ。

-DCMAKE_PREFIX_PATHはQtの場所。

-DQt6_DIRはQtのcmakeの大本のQt6Config.cmakeある場所。指定しなくてもいいかも。

cmakeでmakeファイルが作られたあと、makeをうてばビルドが走る

Puppeteerに必要なライブラリ

Puppeteer

フロントエンドのテストをヘッドレスで行うために使ってみた。
今まではchromeをインストールした環境でPuppeteer無しで行っていたが、Puppeteerがchrome(chromium)のバイナリを入れてくれるようなので試してみた。

インストールはyarn add -D puppeteer

これでapt get installを使わずにchromeのバイナリを入れることができた。 だが、実行すると必要なライブラリがないと言われるので、これらをやっぱりapt get installで入れないといけない。

sudo apt get install libglib2.0-dev libnss3-dev libatk1.0-0 libasound2 libatk-bridge2.0-0 libpangocairo-1.0-0 libxkbcommon-x11-0 libcups2 libxcomposite-dev libgbm-dev libxrandr2

puppeteerのバージョンとchromiumのバージョンの対応はここ Chromium Support | Puppeteer

これでapt getでchromeを入れるよりもポータブルな環境ができた。
と思ったがpuppeteerが入れるのはx86_64のバイナリなので、Arm64版が必要なM1 Macでは動かないね。

ソフトウェアエンジニアの育休振り返り

育休を終えて

悲しいことに3ヶ月の育休が終わる。
仕事という生活に組み込まれたルーティンがなくなったという体験がものすごく新鮮だった。 控えめに言って最高だった。
同僚が育休を取るときは全力でサポートしようと決めた。

子育てはもちろんだけど、ソフトウェアエンジニアとしても色々勉強できるタイミングだったので空いた時間てちょこちょこと勉強もできた。 以下は勉強したことについて所感をまとめていく。

育休中の積み上げ

WEB+DB PRESS Vol.128

WEB+DB PRESS Vol.120

WEB+DB PRESS Vol.131

興味のありそうなWe+DB Pressの本を適当にメルカリで買ってきた。
特にブラウザの仕組みはとても良かった。一番興味のあるところだったし、著者のhikaliumさんの解説が初心者の自分にもわかりやすいレベルまで深ぼってくれているので感動を覚えた。

Webフロントエンドハイパフォーマンスチューニング

最近Web開発を始めた身として、必読本と思う。ブラウザのレンダリングの仕組みを元に、パフォーマンスの出る書き方を学べた。
ブラウザのレンダリングといえば、このブログ様でも詳しく記載されていた。感謝。 Webブラウザのレンダリングの仕組みを理解する

機械学習スタートアップシリーズ Pythonで学ぶ強化学習 入門から実践まで

Kaggleの強化学習のコンペに参加してみたかったので、強化学習の入門としてこの本を選んだ。
このブログ様で紹介されていたので。
kaggleで強化学習をやってみた - 機械学習 Memo φ(・ω・ )

初心者にはいい内容だったと思う。流石に1週しただけではそこまで大脳に定着しないが、
辞書的に繰り返し使っていこうと思う。
使っているFWはKeras。

松尾研の強化学習講座

Twitterで流れてきてエントリーしたら受講できた。学生向けの講座だったけど放送大学に籍があるからか、育休中だからかわからないけど受講できた。マジでありがたい。
講義は強化学習の基本から最新までの動向の流れや、各手法の数式的な説明、実際のコードを用いた演習など、非常に充実したものだった。
途中数式が苦手すぎて、おのれのアタマの良さに絶望しながらも、最終課題をやりながら理解が進んでいき、なんとなく強化学習の入口には立てた気がする。
はじめは「強化学習?知ってる知ってる、DQNでしょドヤァ」という感じだったが、今は「DQNっすか・・・(真相強化学習の始まりの手法だけど今はもっと進化した手法がいくらでも・・・)。いやぁ、自分何もわかってないっす。」と謙遜できるようになった。 これで自分もAI人材()だ。

ISUCON11過去問演習

ISUCON11 まとめ : ISUCON公式Blog

やろうと思ったができなかった。

毎日LeetCode

LeetCode - The World's Leading Online Programming Learning Platform

毎日とかできるわけないよね。5問くらい解いた。

WasmEdgeコントリビュート

WasmEdge

初OSSコントリビュート。なにか1つくらいコントリビュートしてみたいと思ってましたので嬉しい。
内容はgoogle testを用いて本当に簡単なテストを追加しただけ。
だけどコントリビュートする手順など全く知らなかったためコミットまではかなり時間がかかった。

WASMに興味があって、Wasmのプロジェクトを選んだのだけど、有名所でWasmの標準実装であるWasmtime(Rust)、Wasmer(Rust)、最速を謳うWasmEdge(C++)とどれが今後一番ライジングするだろう。各特徴を見ると、WasmEdgeが一番ポータビリティ製と速度に注力している感じで良さそうだったが、言語がC++なのでコントリビューターはWasmtimeやWasmerより少なかった。

NESエミュレータ作成動画を見る

www.youtube.com

プラスウイングTVさんのRustでNESエミュレータを作る動画を毎日見てた。
すっごく丁寧作る過程を見せてくれるので、めっちゃおもしろい。
途中、あれ?動かないぞ、とかなってデバッグしているところもリアルタイムに見せてくれる。あんまり人がデバッグしてるとこって見れないよね、一番勉強になる部分だけど。
いまはフンフンと見てるだけだけど、そのうち自分も写経しようかなと思ってる。

ゲームで学ぶ探索アルゴリズム実践入門

探索のアルゴリズムを学ぶには最高の本だった。自作のゲームと、それをとく各手法の解説なので、ゲームを作ってそれを解くという探索アルゴリズムの楽しさを味わうことができた。
自分で編み出したThunderサーチとか計算を高速化するためのビット演算の解説があり、世界1位は恐ろしいなと思った。

ちょうぜつソフトウェア設計入門

設計の考えがしっかりと網羅的に書いてあってとても良い本だった。
初心者〜中級者向けということもあり、最初の設計の本として読んでおくのがいいと思う。
コードサンプルも多いけど、日本語での解説も多い。挿絵が可愛い。

良いコード/悪いコードで学ぶ設計入門

実際の業務のように、悪いコード例を良いコードにリファクタする本。
全体的な設計というよりは局所的な改善のTipsがある感じ。

nix 入門

https://nixos.org/guides/nix-language.html

開発環境の整備の方法として、Dockerではなくnixが軽量でおしゃれで良いんじゃないかと思い入門。
結果難しすぎて挫折。公式のドキュメントはちゃんとしてるけど、自分がクソ雑魚初心者すぎて入り込めなかった。
結局Rustの開発環境のテンプレートを動かしてみて終了。
HERPはNixを本番環境にも適用してるみたいだけど、変態すぎだろ(尊敬)。

HERP における Nix 活用 - blog.ryota-ka.me

HTML解体新書

Webをやるようになってから、HTML、CSSわからなさすぎワロタ、ってなってたので超ありがたい本だった。
公式のHTML Standardを見てもなんだかよくわからないし、英語おおいし、うーんって思っていた。そんなところにそのHTML Standardの日本語解説がてくれたのでめっちゃありがたかった。
HTML仕様ってこんな感じなのね、ということが理解できた。これのCSS版もほしいんだよなぁ。

毎日英語

できるわけないよね。ネイティブキャンプ3回やっただけでした。

Rustに入門

LeetCodeのEasyならギリかけるかもくらいになった。
Borrow Checkerうざすぎてあああああってなる、よく流行ったなこんな言語。
CLIはRustで書こうと思っているのでもう少し習得したい。
ただしCargoやtomlでのエコシステムはマジでいいはマジで良い。

その他

  • 近所のカフェめぐり
  • 近所の児童館訪問
  • 近所の小児科訪問
  • ガンダムオリジン読破
  • ドラクエ4クリア
  • ハルとナツ 届かなかった手紙(ドラマ)
  • ハサン中田のイスラム教の本

平日に時間があるのでできるだけお散歩した。
児童館とか神施設すぎるわほんと。なんで子供の時行かなかったんだろう。ハイキュー全巻あって震えた。
ガンダムオリジン面白すぎて震えた。というか児童館もガンダムオリジンもDQ4もハルとナツもハサンも全部最高で震えすぎて超伝導。

おわりに

3ヶ月もあったので、計画を立てればもっとたくさんできたかもしれないけど、心の赴くままにやりたいことやったので満足。
特に強化学習やRustは0->1にできたので普段のスキマ時間だと大変だったろうなと思う。

振り返るとプログラミング系ばっかりに時間かけていて、別の趣味っぽい趣味あんまり持ってないことに気がついた。なにか新しい趣味を身に着けたい な。

とりあえず

英語やろう。

gRPC-connectのGetting startをNode(TypeScript)でやってみた

動機

前から少し気になっていたのと、時雨堂の方のTwitterを見て、gRPC-connectがこれから来るのではないかと思ったので、使用感を確かめてみた。

(上記ツイートは、WebSocket, Long polling, server sent event, gRPC-connectの比較の際に出てきた発言なので、これらgRPC-connect以外のものについても今後理解を深めていきたい。
c.f https://qiita.com/Takagi_/items/a9f2ac0b2bfae309f735

それとたまたま本記事をかいてる3/1にnodeの対応がv1.0になったようだ。

Getting start

connect.build をなぞる。

フォルダの作成とライブラリのインストール

mkdir connect-example
cd connect-example
npm init -y
npm install typescript tsx
npx tsc --init
npm install @bufbuild/buf @bufbuild/protoc-gen-es @bufbuild/protobuf @bufbuild/protoc-gen-connect-es @bufbuild/connect

Protoの作成

mkdir -p proto && touch proto/eliza.proto
syntax = "proto3";

package buf.connect.demo.eliza.v1; // packageで指定した文字列がURLのアクセスポイントになる

// message型の変数を定義
message SayRequest {
    string sentence = 1;  //他のメンバ変数とかぶらない識別子をつける必要がある
    // string以外にもint32など取れる
    // 詳しくはgRPCのProtocol Bufferを見ると良い
}

message SayResponse {
    string sentence = 1;
}

// API(RPC)の定義
service ElizaService {
    rpc Say(SayRequest) returns (SayResponse) {}
}

Proto定義ファイルからTypeScriptのコードを生成

buf.gen.yamlを作成し、以下を記載

version: v1
plugins:
  - name: es
    opt: target=ts
    out: gen
  - name: connect-es
    opt: target=ts
    out: gen

コマンドを実行してファイルを生成
npx buf generate proto

genフォルダが自動で作られ、その配下に.tsファイルが生成される

gRPC-connectのサービスを実装

connect.tsを作成

// touch connect.ts

import { ConnectRouter } from "@bufbuild/connect";
import { ElizaService } from "./gen/eliza_connect";

export default (router: ConnectRouter) =>
  // registers buf.connect.demo.eliza.v1.ElizaService
  router.service(ElizaService, {
    // protoで定義したsay関数の実装
    // protoではSayと大文字だったが、こちらは小文字になっていてもいいようだ。
    async say(req) {
      return {
        sentence: `You said: ${req.sentence}`
      }
    },
  });

サーバを立ち上げる

gRPC-connectの公式ではnode, express, fastifyのプラグインを提供している。
ここ本ドキュメントではfastifyで実施している。

fasifyのプラグインをインストール
npm install fastify @bufbuild/connect-fastify

// server.ts
import { fastify } from "fastify";
import { fastifyConnectPlugin } from "@bufbuild/connect-fastify";
import routes from "./connect";

const server = fastify();

await server.register(fastifyConnectPlugin, {
  routes,
});

server.get("/", (_, reply) => {
  reply.type("text/plain");
  reply.send("Hello World!");
});

await server.listen({ host: "localhost", port: 8080 });
console.log("server is listening at", server.addresses());

サーバ起動
npx tsx server.ts

自分の環境だとここでエラーが出た。

エラー対処: Top-level await is currently not supported with the "cjs" output format

tsのエラーではトップレベルの 'await' 式は、'module' オプションが 'es2022'、'esnext'、'system'、'node16' または 'nodenext' に設定されていて、'target' オプションが 'es2017' 以上に設定されている場合にのみ使用できます。 と出てくる。

自分の環境のデフォルトのtsconfig.jsonでは、"target": "es2016""module":"commonjs"となっている。
エラーの通り、トップレベルのawaitはcommonjsでは許可されていないので、エラーの通りにtsconfig.jsonを書き換える。

cf. CommonJSとES Modulesについてまとめる https://zenn.dev/yodaka/articles/596f441acf1cf3

しかし実行時に依然としてエラーが出たので、tsconfig.jsonをデフォルトに戻し、top level awaitにならないよう即時実行関数として以下のように書き直した。

// await server.listen({ host: "localhost", port: 8080 });
(async () => { await server.listen({ host: "localhost", port: 8080 }); })();

これでgRPC-connectを待ち受けるサーバができた。

続いてクライアントからこのgRPC-connectを叩く。

クライアント1: cURL

一番手っ取り早くcURLから叩く。

curl \
  --header 'Content-Type: application/json' \
  --data '{"sentence": "I feel happy."}' \
   http://localhost:8080/buf.connect.demo.eliza.v1.ElizaService/Say

出力
{"sentence":"You said: I feel happy."}

クライアント2: サーバサイドから叩く

client.tsnode上に作成し以下を記載。

// client.ts

import { createPromiseClient } from "@bufbuild/connect";
import { ElizaService } from "./gen/eliza_connect";
import { createConnectTransport } from "@bufbuild/connect-node";

const transport = createConnectTransport({
  baseUrl: "http://localhost:8080",
  httpVersion: "1.1"
});

async function main() {
  const client = createPromiseClient(ElizaService, transport);
  const res = await client.say({ sentence: "I feel happy." });
  console.log(res);
}
void main();

実行
npx tsx client.ts

クライアント3: webブラウザから叩く

クライアント2でかいたのとほぼ同じ。インポートするライブラリをconnect-webに変える程度で良い。 本家のgRPCと違って、httpでやり取りできるので、webブラウザから叩くためのサーバを立ち上げる必要がなく非常に使いやすい印象。

import { createPromiseClient } from "@bufbuild/connect";
import { ElizaService } from "./gen/eliza_connect";
import { createConnectTransport } from "@bufbuild/connect-web";

const transport = createConnectTransport({
  baseUrl: "http://localhost:8080",
  // Not needed. Web browsers use HTTP/2 automatically.
  // httpVersion: "1.1"
});

async function main() {
  const client = createPromiseClient(ElizaService, transport);
  const res = await client.say({ sentence: "I feel happy." });
  console.log(res);
}
void main();

TypeScriptの型定義ファイル

型定義ファイルがどこにもないなと思ったが、それもそのはず、gRPC(-connect)自体はprotoから型定義を生成はしない。
別のライブラリを使う必要がある。

ts-proto-gen
https://github.com/improbable-eng/ts-protoc-gen GitHub - improbable-eng/ts-protoc-gen: Protocol Buffers Compiler (protoc) plugin for TypeScript and gRPC-Web.

使い方はこちらのサイト様を参照すれば良い。
Protocol Buffers から TypeScript の型定義を作る

型定義とは関係ないが、上記サイトを調べる途中で見つけたおもしろいサイトをここにメモする。
自前でgRPCサーバを作成している。Protoco Bufferの仕様から、自分でシリアライザ/でシリアライザを作っていてすごい。
なんだかんだガチプロトタイピングにはTypeScript楽だしgRPCを手作りする - lilyum ensemble

おわりに

gRPC-connectは予想以上にかんたんに使うことができて、普通にRestAPIを作成するのと同程度と感じた。
サーバ間同士はこれでいいんじゃないかなと思う。
クライアントに提供するAPIとしては、RestAPIとどう住み分けるといいのか知見をためたい。
今の所感としては以下のようであればgRPC-connectを採用してもいいのかもしれない。

  • サーバー側のFWがgRPC-connect対応している
    • もしくは、新たにgRPC-connect用サーバを立てても問題ない
  • tRPCMagicOnionといった、サーバ・クライアントが同じ言語であることを活かした便利な通信ライブラリを使わない

cf.) REST、gRPC、および GraphQL を使い分ける (パート 1) | エクセルソフト ブログ

最後になるが、gRPCがあるのになぜわざわざgRPC-conncetが作られたのか、詳しい解説をしてくださるサイト様。これを読むと使える環境であればgRPC-connect使っておけばいいのでは?と思わされる。
https://symthy.hatenablog.com/entry/2022/09/24/160309

【gRPC】Connect が作られた背景概要/これまでの gRPC-Web/Connect でできること - SYM's Tech Knowledge Index & Creation Records

Git rebaseを使ってmainの変更を取り込む

参考サイト: https://style.biglobe.co.jp/entry/2022/03/22/090000

style.biglobe.co.jp

なぜrebaseを使うか。上のサイトにかいてあるが、コミットログをきれいにするため。

main→branchをマージで行うと、自分のbranchのコミット間に他の人のコミットが紛れ込む。

具体的に何が起こったときにログがきれいだといいのかわからないけど、どうせmainにマージする前には一度main->branchのマージを行うので、mergeではなくrebaseを使ってきれいにする方針を取ろうと思う。

ちなみにGitLabでもrebaseでのマージを推奨しているようだ。 https://docs.gitlab.com/ee/topics/git/git_rebase.html

手順

作業ブランチ: featとする

  1. ブランチのバックアップを作成する(現在のブランチ: feat)
    • git checkout -b feat-back
  2. mainでfetchする(現在のブランチ: main)
    • git fetch origin main
  3. featの分岐点をmainの先頭に移動(現在のブランチ: feat)

    • git rebase origin/main

    このとき競合を解決できずわけわからなくなったらとっておいたバックアップに戻す
    git reset --hard feat-back

    これでrebaseをまったくなかったことにできる

  4. mainブランチをfeatブランチにマージ(現在のブランチ: feat)

    • git push -f origin feat

フロントエンドのディレクトリのベストプラクティス

フロントエンドのディレクトリ構成のベストプラクティス

この記事を参考にこのブログを書いた。 https://profy.dev/article/react-folder-structure

Webフロントエンドのプロジェクトに途中参画したが、ディレクトリ構造が少しカオスめだったので、どのようなディレクトリ構造がベストか模索していた。
例えばフロントエンドは画面に関わるファイルが多くなるため、クリーンアーキテクチャみたいなフォルダ構成はイマイチ合わない感じがすると思っていた。
そんなときに上記のサイトは回答に近い構造を詳しい解説付きで提示してくれていたので大変感謝し、メモとしてここに残すことにした。

参考記事はReactを使うことが前提になっているため、contextやhooksといったReact独自のフォルダ・ファイルが登場する。 自分はあまりReactには詳しくなかったが記事内容は十分理解できた。
参考サイトも別にReactに詳しいわけではなく、逆に「contextってなんだ?よくわからないけどReactででてくるからとりあえず作っておいた」みたいなコメントが残っており、フレームワークによらない結論となっている。

結論

機能ごとのディレクトリ構成にすること。

└── src/
    ├── features/ # 機能ごとに分割し、これ自体を共通コンポーネントとしても扱う
    │   ├── todos/
    │   ├── projects/
    │   ├── ui/
    │   └── users/
    └── pages/ # features配下のファイルをインポートしてページコンテンツを作成
        ├── create-todo.js
        ├── index.js
        ├── login.js
        └── terms.js

ディレクトリ構造によって、プロジェクト全体の見通しを良くすることと、フォルダ間の依存関係を定義できることが大事だと個人的に思っている。

プロジェクト全体の見通しを良くすることで、同じような機能の再実装を防げたり、修正の際の影響範囲を簡単に見積もることができるようになる。
フォルダ間の依存関係を定義することで、複雑な依存を発生させづらくできる。また、これも修正の際の影響範囲の見積もりを簡単にできる。
ただしフォルダ自体の粒度が適切ではないとあまり意味をなさないルールになるのでその塩梅が難しい。

参考ブログの結論をよく見ると、pagesを見ることで、このアプリケーションがどのようなページをもっているのかがひと目でわかる。
また、機能をfeatureフォルダに集め、ここにビジネスロジックやそのロジックを使ったコンポーネントをまとめている。機能単位でフォルダを分けることで、機能を実現するために必要なファイルがどこにあるかをすぐに把握できる。

└── src/
    ├── features/ # 機能ごとに分割し、これ自体を共通コンポーネントとしても扱う
    │   ├── todos/
    │   │   └── todo-list/
    │   │       ├── index.js
    │   │       ├── todo-item.component.js # todoのアイテムを表示するコンポーネント
    │   │       ├── todo-list.component.js # todoのリストを表示するコンポーネント
    │   │       ├── todo-list.context.js   # todoのコンテクスト(React用に存在するかもしれない)
    │   │       ├── todo-list.test.js      # テストコード
    │   │       └── use-todo-list.js       # todo-listのためのビジネスロジック

依存関係はブログには言及があまりなかったがおそらくこのようになっているはず。

  • pages配下の個別ページ -> features配下の各機能ファイル
  • fetrues配下の各機能ファイル -> features配下の各機能ファイル
    • 機能は他の機能を使う場合があると考えられる
  • features -> pagesという依存は存在しない。

(-> : 使用する)

常にpagesからfeaturesに依存があることがわかるため、pagesの変更は他のページと機能に影響を与えないことがすぐに把握できる。

features配下で依存が生じるのが少し気になったので、これが問題かを考えてみた。

  1. 機能追加
    1. 機能追加自体はfeturesフォルダ配下に機能単位でフォルダ作成して追加すればいいのでほか機能に影響を与えない
    2. 機能追加時に他の機能を使用する場合 -> ほか機能をimportすれば良いだけなので特に問題はなさそう
  2. 機能修正・削除 1.ファイルのimportを見て依存するファイルを探す必要がある 2.相互依存になっている場合、修正が大変になる

2-1、2-2はやや問題がありそうなのでここを考えてみる。 2-1について、依存するファイルをディレクトリ構造で明確化するには2通りが考えられる。

  • ディレクトリをネストして、依存されるものを親フォルダに配置する

  • 1つ以上の機能から参照される機能は、共通機能フォルダにを作りそこに配置する

ディレクトリのネストについては、機能が複数の機能から参照される場合、いちいちディレクトリ構造を変化させる必要が出てきそうなので、あまり現実的ではなさそう。

共通フォルダ化について、必ず機能->共通機能の依存とする。しかし結局共通機能フォルダ内の依存が3-1と同じように発生する可能性がある。加えて本質的にファイル間の依存が減るわけでもないためimport文を見てどこに依存があるのかを調べる必要があるのは変わらない。

2-2の相互依存の発生については、どうしてもそれが解消できない場合は一つの機能としてフォルダを分けてしまうことが考えられる。 これはfeaturesフォルダの理念に反しないので許容と思われる。

そもそも、機能の依存が発生するときは、例えばtodo-list機能 -> auth機能といった依存が考えられ、この依存が相互になるケース自体が稀だという気もする。

結論としては、featuresフォルダ内の依存は仕方のないところだと思って諦める。しかしそこまで大きな問題にもならなそうな気がする。

機能ごと以外のディレクトリ分けを比較

  1. ファイルタイプによるディレクトリ分け
  2. componentをcomponentフォルダ
  3. hooksをhookdフォルダ
  4. contextをcontextフォルダに分割
└── src/
    ├── components/
    │   ├── edit-todo-modal/
    │   ├── todo-list/ # uiと違い、ビジネスロジックをもっている
    │   └── ui/ # button,check boxといった単純なコンポーネント
    ├── contexts/
    └── hooks/

問題点: 例えばページを追加していく場合、component配下にフォルダとファイルが増えていく。
component配下内にはページや純粋なUIコンポーネント、共有されるコンポーネント、共有されないコンポーネントなど様々な種類のファイルがあるため、お互いの参照関係が複雑になる。

-> pagesフォルダを作り、ページ関係するコンポーネントを切り出す

ページとグローバルなファイルによるディレクトリ分け

  • ページに関わるものをpagesフォルダにまとめる
    • ex) pages/home/home-page.js, todo-list/todo-item.component.js
  • 複数のページから参照されるcompnentはcomponetフォルダにおく
  • hooks, contextsはそのまま
└── src/
    ├── components/
    │   ├── todo-form/ # このコンポーネントは複数のページから参照されるとする
    │   └── ui/
    ├── contexts/ # contextはこのフォルダにまとめたままにする
    ├── hooks/ # hooksはこのフォルダにまとめたままにする
    └── pages/
        ├── create-todo/
        ├── home/ # homeページでのみ使用されるコンポーネントをまとめる
        │   ├── home-page.js
        │   ├── edit-todo-modal/
        │   └── todo-list/
        │       ├── todo-item.component.js
        │       ├── todo-list.component.js
        │       └── todo-list.test.js
        ├── login/
        └── terms/

問題点: component, hooks, context, pagesを使って一つの機能を作っているため、フォルダの見通しが悪い。

-> hooksとcontextも関連するpagesフォルダに配置する

hooksとcontextsをpageフォルダ配下に分割する

  • pagesフォルダ配下のページと同じ階層に、そこで使われるhooksやcontextsを移動
    • ex) pages/home/home-page.js, use-todo-list.js
└── src/
    ├── components/
    │   ├── todo-form/
    │   └── ui/
    ├── hooks/
    │   └── use-auth.js # 複数のページから参照されるの共有hooksなのでここにおいておいく
    └── pages/
        ├── create-todo/
        ├── home/
        │   ├── home-page.js
        │   ├── edit-todo-modal/
        │   └── todo-list/
        │       ├── todo-item.component.js
        │       ├── todo-list.component.js
        │       ├── todo-list.context.js # 2. で別フォルダにおいていたcontextをここに移動
        │       ├── todo-list.test.js
        │       └── use-todo-list.js # 2.で別フォルダにおいていたhooksをここに移動
        ├── login/
        └── terms/

問題点:

  • Todoに関する機能がcomponentsとpages配下に分散しており、機能の見通しが悪い
  • ディレクトリ構造からだけでは、todo関連機能がpages/home配下にあることが簡単にわからない
  • Todo機能を他のページが使用することになった場合、そのファイルはcomponentに移動されるが、もしtodoに関連する他ファイルがhome配下にある場合はhome配下に残されたままになり、より見通しが悪くなる。

ファイル名はケバブケースを激推し

ケバブケースというのはmy-file-name.jsのように-で単語を区切っていくスタイル。
Mac、Windowsではファイル名の大文字小文字を区別しないが、Linuxは区別する。これのせいで開発環境と、CI環境・実行環境で思ったとおりに動かない場合がある。
そのため、この著者様はケバブケースを推奨していた。

まとめ

  • ディレクトリはページと機能単位で分ける。
    • ページ: 1ページ1jsファイル
    • 機能: コンポーネントや単純なロジックなど。すべての他機能フォルダから参照可能。
  • ファイル名はケバブケース

Lit公式ドキュメントまとめ Reactive property

Lit 公式ドキュメントまとめ 3

www.niwaka-plus.com

www.niwaka-plus.com

Reactive properties

Litはリアクティブなクラス変数もしくはプロパティをもちます。 リアクティブというのは、この変数もしくはプロパティに変更があったとき、コンポーネントをアップデートするトリガーとなる特殊な変数になります。

class MyElement extends LitElement {
  static properties = {
    name: {},
  };
}

このようにstatic propertiesで宣言したあとのnameがReactive propertyになります。

主なReactive propertyの役割は以下の4つです

  • Reactive updates: LitはReactive propertyごとに内部的にgetter/setterのペアを作ります。このReactive propertyが変更されたときコンポーネントをアップデートします。

  • Attribute handling: Litはデフォルトで、プロパティに対応する属性をもちます。そしてその属性が変更されたとき、対応しているプロパティも変更されます。オプションでプロパティ値の変更→属性値の変更ということも可能です。

  • Superclass properties: Litは自動的にスーパクラスで宣言された処理が適用されます。ですので自分の作ったLitのオブジェクト内で処理を上書きしたい場合をのぞいて、Litのアップデートに関わる処理を記載する必要はありません。

  • Element upgrate: DOMの要素の更新を自動で行ってくれます。

Public propeties and internal state

PublicなReactive propertyは外からその変数を変更することができ、InternalなReactive propertyは自身のコンポーネントからのみ変更することができます。
どちらも変更されるとコンポーネントアップデートのトリガーとなります。

PublicとInternalでReactive propertyを宣言する方法

class MyElement extends LitElement {
  static properties = {
    name: {},// Public
    _age:{state: true},// Internal
  };
}
customElements.define('my-element', MyElement);
<my-element .name="TARO"></my-element> //OK
<my-element ._age=20></my-element>     //NG

privateなReactive propertyにするには{state: true}とします。こうすることで外側からこのプロパティを変更できなくなります。

クラスフィールド(メンバ変数)を使ってはいけない

JavaScriptではLitのReactive propertyを使う場合はメンバ変数を使ってはいけない。

これは、JavaScriptの仕様上、メンバ変数がLitのReactive propertyより優先度が高くなってしまうため、うまく動かかなくなってしまう可能性があります。

その代わりにコンストラクタで変数を宣言することは可能です。

constructor() {
  super();
  this.data = {};
}

ただしTypeScriptでは以下のルールを守ればメンバ変数を宣言することが可能です。

  • tsconfigにuseDefineForClassFields をfalseで設定すること
  • declareをフィールドに追加して、フィールド初期化子で初期値を与えること

Babelを使う場合は設定が公式に書いてあるので参照してみてください。

Property options

attribute

LitのReactive propertyはattibuteと連携してます。連携はfalseでオフにすることができます。オフにした場合はconverterreflecttypeといったオプションが無視されてしまいます。

converter

Reactivr propertyとattributeのカスタムコンバータを定義することができます。

hasChanged

Reactive propertyに値がセットされるたびに自動でこの関数が呼び出されて、前の値から更新されているチェックします。値が更新されている場合はLitのアップデートのトリガーを発火します。

NoAccessor

trueにすることでデフォルトのアクセッサーを生成しません。普通はdefaultのfalseで運用されます。

reflect

Reactive propertyの値の変更をAttributeにも伝えたい場合、trueにします。デフォルトはfalseです。通常はAttributeの変更はReactive propertyに伝播しますが、逆は行われません。

State

trueでReactive propertyをinternalに、falsepublicにすることができます。internalにした場合、外からの変更を受け付けなくなります。

type

Reactive propertyの型を指定することができます。

Reactive Propertyが変更されたとき何が起こるか

Reactive propertyが変更されると、Litのライフサイクルの更新のトリガーが発火し、コンポーネントのテンプレートを再描画します。
このときに何が内部で行われいるのかを説明します。

  1. プロパティのsetterが呼び出される
  2. setterがコンポーネントのrequestUpdateを呼び出す
  3. そのプロパティの前と今の値を比較する
  4. プロパティが変更されていた場合、非同期でアップデートがスケジュールされます。もしすでにスケジュールされている場合は、スケジュールされるのは1度だけです
  5. コンポーネントのupdateが呼び出されます。そしてプロパティの変更がattributeに反映され、コンポーネントのテンプレートが再レンダリングされます。

注意: もしReactive propertyのarrayの要素を変更した場合、updateのトリガーにはなりません。
なぜならオブジェクト自体が変更されたわけではないので変更とみなされないからです。

Objectやarrayのプロパティの変更について

上記で述べたように、Objectやarrayの変更を検知することができません。
しかしながら、これらを扱うために2つの方法があります。

Immutable data pattern

arrayの要素を更新するつときに、まるごと上書きしてしまう方法です
this.myArray = this.myArray.filter((_, i) => i !== indexToRove)

Manually triggering an update

トリガーであるrequestUpdate()を直接呼び出す方法です

this.myArray.splice(indexToRemove, 1);  
this.requestUpdate();

このように任意のタイミングでアップデートのトリガーを発火することができます。
ただし、requestUpdate()は自身のコンポーネントのみに有効です。

一般的な用途としてはImmutable data patternが適しています。
Manually triggering an updateは上級者向けのテクニックとして覚えていてください。

www.niwaka-plus.com

www.niwaka-plus.com

Lit公式ドキュメントまとめ レンダリング

Components

Components

  • 前記事 Litの定義

www.niwaka-plus.com

レンダリング

以下のようにrender()メソッドの中にhtmlタグを用いてhtmlを描画することができます。

import {LitElement, html} from 'lit';

class MyElement extends LitElement {
  render() {
    return html`<p>Hello from my template.</p>`;
  }
}
customElements.define('my-element', MyElement);

Hello from my template.

render()の中にはhtmlタグだけではなくJavaScript/TypeScriptを入れることができます。

レンダリングできる値

先程のrender()はhtmlタグを返しますが、Litがレンダリングできるhtml要素のものであれば何でも返り値にすることができます。

  • string, Booleanのようなプリミティブな値
  • DOMノード
  • noChangeやnothingといったセンチネルな値(後に公式ドキュメントで解説)
  • サポートされた配列やイテレータ

具体的な用法は公式ドキュメントのこちらを参照ください。 https://lit.dev/docs/templates/expressions/#child-expressions

また、html要素ではないがレンダリングできるものとしてはsvgがあります。

render() の良い書き方

render()内は以下のように書くことを推奨しています:

  • コンポーネントのステートを変化させないこと
  • 副作用を生じさせないこと
  • コンポーネントのプロパティはインプットとしてのみ用いること
  • 受け取ったプロパティの値と同じ値を返すこと

これらのガイドラインを守ることで、コードをわかりやすく保つことができます。
render()外でDOMを作ることも避けるべきです。
その代わり、コンポーネントのテンプレートを状態の関数として表現し、その状態をプロパティに取り込むのが良いでしょう。

Litコンポーネントテンプレートの作成

Litテンプレートは他のテンプレートを呼び出すことができます。
以下はヘッダー、フッター、メインコンテンツをもつ<my-page>というテンプレート作成する例です。

import {LitElement, html} from 'lit';

class MyPage extends LitElement {
  static properties = {
    article: {attribute: false},
  };

  constructor() {
    super();
    this.article = {
      title: 'My Nifty Article',
      text: 'Some witty text.',
    };
  }

  headerTemplate() {
    return html`<header>${this.article.title}</header>`;
  }

  articleTemplate() {
    return html`<article>${this.article.text}</article>`;
  }

  footerTemplate() {
    return html`<footer>Your footer here.</footer>`;
  }

  render() {
    return html`
      ${this.headerTemplate()}
      ${this.articleTemplate()}
      ${this.footerTemplate()}
    `;
  }
}
customElements.define('my-page', MyPage);

上記をコンポーネント分割してみます。

// my-page.js
import {LitElement, html} from 'lit';

import './my-header.js';
import './my-article.js';
import './my-footer.js';

class MyPage extends LitElement {
  render() {
    return html`
      <my-header></my-header>
      <my-article></my-article>
      <my-footer></my-footer>
    `;
  }
}
customElements.define('my-page', MyPage);
// my-header.js
import {LitElement, html} from 'lit';

class MyHeader extends LitElement {
  render() {
    return html`
      <header>header</header>
    `;
  }
}
customElements.define('my-header', MyHeader);

my-article.jsとmy-footer.jsも同じ構造なので省略します。
このようにしてLitコンポーネントは別のLitコンポーネントを呼び出すことができます。

テンプレートはいつレンダリングされるか

まず、ページのDOMに追加されると最初にそのテンプレートをレンダリングします。
それ以降はReactiveなプロパティが変更されるたびにアップレードサイクルが走り、再レンダリングされます。(Reactiveなプロパティはユーザが自由に設定することができます。後ほど解説があります。)

Litのアップデード処理は、パフォーマンスと効率を最大限にするよう設計されています。
例えば複数のreactiveな値が一度に変更された場合、アップデードサイクルのトリガーは1度だけ発行され、非同期にマイクロタスクのタイミングでアップデード処理が実行されます。

加えて、DOMはすべてが再レンダリングされるわけではなく、変更の合った部分のみをレンダリングします。
レンダリングについてさらに詳しい公式ドキュメントはこちら。
https://lit.dev/docs/components/properties/#when-properties-change

DOMのカプセル化

LitはShdow DOMを用いて、コンポーネントがレンダリングするDOMをカプセル化しています。
Shadow DOMはhtml要素自体を作成し、ドキュメントツリーとは完全に分離されていて、そのため相互運用やスタイルのカプセル化などが可能となっています。

Shdow DOM自体にいての解説はこちら。
https://web.dev/shadowdom-v1/

LitコンポーネントにおいてShadow DOMがどのように動いているかの解説はこちら。
https://lit.dev/docs/components/shadow-dom/

Lit公式ドキュメントまとめ Components

Lit公式ドキュメントまとめ 01

最近アサインされたプロジェクトがLitを使うプロジェクトだったので、Litを使い始めました。
最初はなんでReactじゃないんだろうと思って少々不満でしたが、1ヶ月も使うとだいぶ馴染んできて、いい感じになってきました。
コンポーネントの再レンダリングなんかはLitが自動でやってくれるので、Reactより良いなと思える部分が増えてきました!

Litとは

Googleが提供しているWeb Componentを簡単に作れるライブラリ。
Web ComponentはWeb標準技術なので、他のあらゆるWebライブラリ/フレームワークから呼び出すことができます。

導入

導入は公式のチュートリアルを実施すれば良い。 https://lit.dev/tutorials/

とても良くできたチュートリアルで、左側で問題を見ながら右側のプレイグラウンドでコードを実行できます。
わからなくなったら回答も即座に実行することができるため、非常に優秀。

Lit公式ドキュメントまとめ Components

Lit公式ドキュメント https://lit.dev/docs/components/properties/#public-properties-and-internal-state

Litには当然だが様々な機能が有り、1,2ヶ月触った程度では一部しか理解できていないことがわかりました。
網羅的に理解したいので、Lit公式ドキュメントを頭から読んでいき、その記録をつけたいと思います。

  • Lit公式ドキュメントまとめ 01
    • Litとは
    • 導入
    • Lit公式ドキュメントまとめ Components
    • Components
      • Componentsの定義
      • Lit ComponentsはHTML Elementと同等に扱うことができる
      • TypeScriptでLitの型定義を作る方法
続きを読む

ArduinoIDEをVS Codeにしたらコード補完や書き込みが楽で最高になった

はじめに

Arduinoの標準IDEはよくできているが、コード補完が効かなかったり、参照ライブラリ元へ飛べなかったりと少し残念である。 そこで、エディタをVS Codeに切り替えることで、コード補完、参照ライブラリへのジャンプ、その他VS Code拡張の恩恵を預かれるようにする。

f:id:hiddenvally333:20210404225359p:plain

  • はじめに
  • 準備
  • Visual Studio Code Extension for Arduinoのインストールと設定
    • Arduinoのパスを設定
    • 拡張の使い方
  • コード補完の有効化
    • setting.jsonにインクルードパスを記載
  • 文字化け対策
  • Arduinoへの書込み設定
  • おわりに
続きを読む