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。
松尾研の強化学習講座
東京大学松尾研の強化学習スプリングセミナーの受講生を募集します!https://t.co/brvEHDWA0I
— 今井翔太 / Shota Imai@えるエル (@ImAI_Eruel) January 29, 2021
深層強化学習の基礎から,ゲームAI,ロボット等の応用,最先端の研究まで一気に学べる講義です
私は,初回にRL基礎〜有名アルゴリズムの発展,最終回にゲームAIについて講義します
是非受講をご検討ください! pic.twitter.com/qzoMvawgKy
Twitterで流れてきてエントリーしたら受講できた。学生向けの講座だったけど放送大学に籍があるからか、育休中だからかわからないけど受講できた。マジでありがたい。
講義は強化学習の基本から最新までの動向の流れや、各手法の数式的な説明、実際のコードを用いた演習など、非常に充実したものだった。
途中数式が苦手すぎて、おのれのアタマの良さに絶望しながらも、最終課題をやりながら理解が進んでいき、なんとなく強化学習の入口には立てた気がする。
はじめは「強化学習?知ってる知ってる、DQNでしょドヤァ」という感じだったが、今は「DQNっすか・・・(真相強化学習の始まりの手法だけど今はもっと進化した手法がいくらでも・・・)。いやぁ、自分何もわかってないっす。」と謙遜できるようになった。
これで自分もAI人材()だ。
ISUCON11過去問演習
やろうと思ったができなかった。
毎日LeetCode
LeetCode - The World's Leading Online Programming Learning Platform
毎日とかできるわけないよね。5問くらい解いた。
WasmEdgeコントリビュート
初OSSコントリビュート。なにか1つくらいコントリビュートしてみたいと思ってましたので嬉しい。
内容はgoogle testを用いて本当に簡単なテストを追加しただけ。
だけどコントリビュートする手順など全く知らなかったためコミットまではかなり時間がかかった。
WASMに興味があって、Wasmのプロジェクトを選んだのだけど、有名所でWasmの標準実装であるWasmtime(Rust)、Wasmer(Rust)、最速を謳うWasmEdge(C++)とどれが今後一番ライジングするだろう。各特徴を見ると、WasmEdgeが一番ポータビリティ製と速度に注力している感じで良さそうだったが、言語がC++なのでコントリビューターはWasmtimeやWasmerより少なかった。
NESエミュレータ作成動画を見る
プラスウイング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がこれから来るのではないかと思ったので、使用感を確かめてみた。
bufbuild/connect-crosstest: Connect's gRPC and gRPC-Web interoperability test suite. https://t.co/GXcTOprLll これ素晴らしいな ... 。真面目に buf / connect 調べてみよう。connect-es にも Cloudflare Workers で動かしたければ意見くれって書いてあるのが良い。
— V (@voluntas) February 26, 2023
(上記ツイートは、WebSocket, Long polling, server sent event, gRPC-connectの比較の際に出てきた発言なので、これらgRPC-connect以外のものについても今後理解を深めていきたい。
c.f https://qiita.com/Takagi_/items/a9f2ac0b2bfae309f735)
それとたまたま本記事をかいてる3/1にnodeの対応がv1.0になったようだ。
Connect is now full-stack TypeScript! We're excited to launch the beta of Connect for Node.js today, a library for serving Connect, gRPC, and gRPC-Web APIs using Node.js: https://t.co/CIwWuHApJg
— Buf (@bufbuild) February 28, 2023
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.tsをnode上に作成し以下を記載。
// 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用サーバを立てても問題ない
tRPCやMagicOnionといった、サーバ・クライアントが同じ言語であることを活かした便利な通信ライブラリを使わない
最後になるが、gRPCがあるのになぜわざわざgRPC-conncetが作られたのか、詳しい解説をしてくださるサイト様。これを読むと使える環境であればgRPC-connect使っておけばいいのでは?と思わされる。
https://symthy.hatenablog.com/entry/2022/09/24/160309
Git rebaseを使ってmainの変更を取り込む
参考サイト: https://style.biglobe.co.jp/entry/2022/03/22/090000
なぜrebaseを使うか。上のサイトにかいてあるが、コミットログをきれいにするため。
main→branchをマージで行うと、自分のbranchのコミット間に他の人のコミットが紛れ込む。
具体的に何が起こったときにログがきれいだといいのかわからないけど、どうせmainにマージする前には一度main->branchのマージを行うので、mergeではなくrebaseを使ってきれいにする方針を取ろうと思う。
ちなみにGitLabでもrebaseでのマージを推奨しているようだ。 https://docs.gitlab.com/ee/topics/git/git_rebase.html
手順
作業ブランチ: featとする
- ブランチのバックアップを作成する(現在のブランチ: feat)
- git checkout -b feat-back
- mainでfetchする(現在のブランチ: main)
- git fetch origin main
featの分岐点をmainの先頭に移動(現在のブランチ: feat)
- git rebase origin/main
このとき競合を解決できずわけわからなくなったらとっておいたバックアップに戻す
git reset --hard feat-backこれでrebaseをまったくなかったことにできる
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配下で依存が生じるのが少し気になったので、これが問題かを考えてみた。
- 機能追加
- 機能追加自体はfeturesフォルダ配下に機能単位でフォルダ作成して追加すればいいのでほか機能に影響を与えない
- 機能追加時に他の機能を使用する場合 -> ほか機能をimportすれば良いだけなので特に問題はなさそう
- 機能修正・削除 1.ファイルのimportを見て依存するファイルを探す必要がある 2.相互依存になっている場合、修正が大変になる
2-1、2-2はやや問題がありそうなのでここを考えてみる。 2-1について、依存するファイルをディレクトリ構造で明確化するには2通りが考えられる。
ディレクトリをネストして、依存されるものを親フォルダに配置する
1つ以上の機能から参照される機能は、共通機能フォルダにを作りそこに配置する
ディレクトリのネストについては、機能が複数の機能から参照される場合、いちいちディレクトリ構造を変化させる必要が出てきそうなので、あまり現実的ではなさそう。
共通フォルダ化について、必ず機能->共通機能の依存とする。しかし結局共通機能フォルダ内の依存が3-1と同じように発生する可能性がある。加えて本質的にファイル間の依存が減るわけでもないためimport文を見てどこに依存があるのかを調べる必要があるのは変わらない。
2-2の相互依存の発生については、どうしてもそれが解消できない場合は一つの機能としてフォルダを分けてしまうことが考えられる。 これはfeaturesフォルダの理念に反しないので許容と思われる。
そもそも、機能の依存が発生するときは、例えばtodo-list機能 -> auth機能といった依存が考えられ、この依存が相互になるケース自体が稀だという気もする。
結論としては、featuresフォルダ内の依存は仕方のないところだと思って諦める。しかしそこまで大きな問題にもならなそうな気がする。
機能ごと以外のディレクトリ分けを比較
- ファイルタイプによるディレクトリ分け
- componentをcomponentフォルダ
- hooksをhookdフォルダ
- 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
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でオフにすることができます。オフにした場合はconverterやreflect、typeといったオプションが無視されてしまいます。
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に、falseでpublicにすることができます。internalにした場合、外からの変更を受け付けなくなります。
type
Reactive propertyの型を指定することができます。
Reactive Propertyが変更されたとき何が起こるか
Reactive propertyが変更されると、Litのライフサイクルの更新のトリガーが発火し、コンポーネントのテンプレートを再描画します。
このときに何が内部で行われいるのかを説明します。
- プロパティのsetterが呼び出される
- setterがコンポーネントの
requestUpdateを呼び出す - そのプロパティの前と今の値を比較する
- プロパティが変更されていた場合、非同期でアップデートがスケジュールされます。もしすでにスケジュールされている場合は、スケジュールされるのは1度だけです
- コンポーネントの
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は上級者向けのテクニックとして覚えていてください。
Lit公式ドキュメントまとめ レンダリング
Components
Components
- 前記事 Litの定義
レンダリング
以下のように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拡張の恩恵を預かれるようにする。

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