Tech系サービスやガジェットの使い心地、自分の作業環境、資産運用について気が向いたときに記録を残しています。

記事内のAmazonアソシエイト適格販売及び、Google Adsenseでお小遣いを得ています。

リモートワーク&Zwiftデスクツアー2024

2024プライムデーを経てアップデートされたので、現在のデスク環境をまとめてみる。多分毎年やる。

勤務・ワークアウト量

  • 週1通勤、4リモートをベースに客先やイベント次第で前後
    • 現在は育休中
  • Zwift週2~3

見た目よりも実用性に振って構築。特に音声や入力IFは若干の遅延が気になってしまう方なので、有線接続を残している。 WEB会議がメインなので、安定性と低遅延のため有線LANのインターネット環境がマスト。

外観

有線接続を信じろ

34インチウルトラワイドモニタ+ノートPC画面のスタイルに落ち着いた。

プライベートのThinkPad T14sと仕事用のMacbook Pro(M1)で周辺機器をほぼ全て共用。地味に機器数が多く、有線LANも必要なのでHPのThunderbolt3ハブ経由ですべてのアクセサリを切り替える運用。

寝室が近く、明かりも声も気を遣う場合があるので、BenQのモニターライトが作業灯がわり。

夜中にPC作業するには十分な光量

Zwiftスタイル

エルゴトロンLXモニターアームをフルに使って、外部モニタをローラー前に移動。ローラー中の音声出力はもっぱらOpenRun Proを使って聞いている。ワークアウト中でも聞こえる音量だとリビングに響いてうるさいと指摘を受けたため…

部屋に設置されているダイキンのエアコンがサーキュレーター兼用タイプなので工業扇がなくとも十分な風量を確保できている。

大物

Flexispot EJ2

リモートワーク中期に購入した昇降デスク。天板はマルトクショップのオーダー品。当時は純正天板に望みのサイズ(140x70)が無かったが、今なら純正竹天板でも良いかなと思える。

天板の磨きやオイル加工については作成当時の記事に書き留めている。

gensobunya-tech.hatenablog.com

ただ、今となっては、E7E7Hのようなハイグレードを選んでいればよかったと思っている

EJ2は最低高がそこまで低くないが、椅子のシルフィーと合わせて、店頭で肘置きと天板がほぼツライチになることを確認していた。ただ、裸足で座った際は、靴を履いて座った時より数センチ低いほうがしっくりくるということに気づけなかった。

※EJ2の最低高が69cmに対して、E7シリーズは58cm。

Flexispot CH1 + Razar Core X + RTX 4070

プライベートPCに接続するeGPUキット。机の上はスッキリさせたかったので、Flexispot CH1を使ってデスク下に吊り下げ。

本来はタワーPCをセットするためのキットで、高さがあっていないがそれなりに安定して固定できるのでeGPUボックスでも続投させた。ケースを90度回転させることができるので、取り付け後にケーブルを抜き差しするのも楽。専用品なのに天板にねじ止めという点を除けばよくできている。

gensobunya-tech.hatenablog.com

TB3ハブ経由でデイジーチェーン接続することも可能だったが、電力消費や接続安定性・TB3帯域の消費も鑑みて、2ポートあるT14sのThunderbolt4端子に直接接続。、Zwiftや動画エンコードするときだけ繋いで利用している。

デイジーチェーン運用していた時期もあったが、認識したりしなかったりが煩わしかった。

オカモト シルフィー

定番なので書くことがない。ヘッドレストがあるとShokzの骨伝導イヤホンが使いづらいので無しのモデルを選んだくらいか。

入力IF系

キーボード・トラックボール

肩凝り解消に分割キーボードは効いた。トラックボールは定番のM575を中央に置いて満足しているが、最近はもっとポジションを動かさずに済む方法はないかと考えているところ。

ペンタブ

プレゼンでスライドに書き込む用。顔をカメラに向けたままにできるので、板タブ派。

整理整頓系

山崎実業 デスク下 天板ケーブルラック ロング

適当な配線のまま、正面から見たときにケーブルを隠せるナイスな構造

2024年の新顔。これまでは、ハブやディスプレイの巨大なACアダプタを飲み込みつつ、モニターアームと干渉しない直付けケーブルラックを探せずにいた。

PCに接続しないが電源は食うデバイス(Pixel Standなど)もあって、口数の多い電源タップが必須だったこともあり、拡張性と放熱性を考えてワイヤーラックに結束バンド留めをして、モニターアームクランプの穴に吊り下げていたのがこれまでのスタイル。柔軟性は高かったが見た目は捨てていた。

浮いていればいいんだよという哲学のもと構成したケーブルたち

高さと容量、ケーブル出口の柔軟性を持ち、前面板を取り外せるこのケーブルラックのおかげで、配線が見えない形に再整理。アームクランプの位置も制限が外れ非常に使いやすくなった。

内部の雑然とした状態はそのまま

珪藻土コースター付きカップホルダー

集成材の弱点である水分をデスク上から逃がす

2024年新顔。

溝を切ってあるコースターでも、張り付きからは逃れられなかったが、これで完全に解決。

机の上に水滴がついたり、こぼしたりする事故も防げてお得だった。

ケーブル整頓

ハブやeGPUにつないでいる着脱が頻繁なThunderboltケーブルはマグネット付きでノートPCトレイに貼り付け。

それ以外の配線を整理するだけのケーブルクリップと使い分けている。ゆくゆくはケーブルダクトなんかも使って、電源タップとLANケーブルもスッキリ隠したいところ。

ティッシュボックス

昇降デスクの脚にマグネットで装着するタイプのホルダー。

普段は目に入らないところなので、最低限のホールド機能だけをもった山崎実業製品を。

まとめ

生活に余裕が出てきたせいか、今年は見た目を改善するグッズが増えた。

モニタが5年ほど使っている古いものなので、そろそろハブと合わせて来年にも交換か…?

情報分電盤の無い家でルーター・ONUを隠しながら壁掛けTVの配線をした

ルーターの棚を無くしてリビングの見た目をスッキリさせたい、子供がケーブルをいじれないようにしたい、ルンバからケーブルを遠ざけたい…

様々な理由がありつつ、我が家ではこのすべてを実現するために、独身時代に妻が持っていたフルHD未満の化石TVリプレイスがついに決定した。

あまり広くないリビングを有効活用しつつ、キッチンからも見えるようにしたいという要望をかなえるため、壁掛け金具DIY設置を決意。

白羽の矢が立ったのは、スタープラチナのTVセッターフリースタイルだ。

  • ケーブルは配線モールで隠す
  • ルーターONUをTVの陰に隠す(自分のエゴ)
  • 無線APを高いところにセットして効率UP(自分のエゴ)
  • ツールドフランスを4K視聴する(夫婦のエゴ)

という要望をかなえるのがメインだが、金具そのものに小物入れが搭載されており、ここを情報分電盤のケーブルエリアに見立てて、ゴチャつく部分を隠してしまおうという目論見。

作業自体は下地を探して下穴をあけてネジ止めするだけなので、気楽だ

イクラックを壁に直付けした経験を活かし、金具そのものの設置は楽々終了。

下地探しやマスキングテープ、水準器に電動ドリルドライバー(インパクトまでは不要)があれば充分マニュアル通り作業できる。ドリルビットは何故か我が家に何種類もあるので困ることがない。

配線

今回重要になるのは配線設計。

可能な限り配線をテレビ裏で完結させるべく、小型の延長コードとUSB ACアダプタを組み合わせて壁掛け金具の小物入れ内から電源を取る。

ONU・無線AP・Switchbot Hub miniはUSB電源で稼働できるので、床近くのコンセントからはテレビ用・延長コード・ルーターの3本取り出しで済ませた。

電源に加えて、ONU用の光ケーブルと、1F無線AP・自室につながるLANケーブルをコンセント付近から引っ張る。さすがにここまでくると2系統必要だったので、垂直に上るところだけは配線隠しは2連。

LANコンセントは後付け時の制約上、アンテナ線から遠くになっている

あとは配線モールで地道にケーブル類を隠していく。

中華グッズの例にもれず、付属両面テープは全く役に立たないので、最初から3Mの剥がせる壁紙用両面テープを使うことを強くお勧めする。

大抵の壁掛けグッズには、アンカー付きの木ねじが付属しているが、家庭用ルーターの重量を支えるには大仰すぎるため、ピンフックを用いて固定。耐荷重1kgあれば充分。

あれやこれやして、高いところにブリッジモードのeeroを設置、ONUルーターは背面に隠れる構成を実現できた。LANケーブルはいい感じの長さの柔らかい物に買い替え。

外側からは配線モールと無線APしか見せない

エアコンの気流が直接当たるので、熱対策も完璧

注文住宅であれば、情報分電盤や物置の一部を使って回線をまとめるところだが、気の利いていない戸建てでもテレビ裏の空間を使って、最大限配線と機器を隠ぺいすることができた。

特に、熱対策については閉鎖空間ではなくエアコンの冷風も直接当たる今回の構成の方が明らかにいいだろう。一般的には情報分電盤やそれに類する部分のエリア排気はちゃんと設計されているんだろうか?

断熱やら配線にこだわっていくと、必然的に住宅系Youtube動画を見まくることになり上っ面の知識だけはついてくる。ローン控除終わる10年後くらいに理想の家を建てられたらいいなと思わなくもない

OpenAPI-typescriptで家計の割り勘スクリプトのコードを改善

我が家は共働きで、財布はそれぞれで管理しつつ生活費は収入に応じて大まかに按分するという運用をしています。

支出した生活費はすべて一元的に記録し、按分割合を掛けて月末に清算することで、個人支出の自由度と公平性を両立して、その気になれば後から調査・支出分析も可能というGoodな仕組みになっています。

割り勘の記録については、様々なサービスがありますが、Splitwiseを採用しています。

www.splitwise.com

明細ひとつひとつに傾斜割合をつけることができるので、今回の要件にはぴったりです。

ただ、「自動で50:50以外の傾斜割合を設定する」という機能はSplitwise Pro(月700円/人)が必要。夫婦二人で月1400円は決して安くない出費です、しかも利用目的が傾斜を自動設定するだけ。

現行ソリューション

SplitwiseにはAPIアクセスは無料でついてくるというクソデカメリットがあります。というか、これがSplitwise採用最大の理由です。

そこで、最低限動作する形で自宅グループの最新レコードを取得し、すべての明細に負担割合を設定しなおすLambda関数を作成することで、Proプランなしに家庭内の按分割合を自動で設定することができています。

github.com

1日で作ったので、とりあえず家庭内利用に問題ないレベルだけで実装されており、2時間ごとにGetExpensesで返ってくる直近20件の明細のみを対象に動作し、結果は自分のSlackワークスペースにログが残るという単純なシステムです。コアロジック(金額計算)以外のテストコードはありません。

2時間以内に20件以上の家庭出費が発生することは考えづらく、インプットデータはSplitwiseのアプリ内でバリデーションされるという前提に立っているので、はっきり言って雑な実装ですが1年以上うまく動作していました。

レガシーコード改善

非常にいい感じに働いていてくれたのですが、ある日「グループIDを指定して取得しているのにグループIDがNullのデータが混入する」というSplitwise側で謎のエラーが発生した結果、この関数も処理中にコケてしまいました。更に悪いことに、発覚が1週間以上遅れたため20件以上のレコードが積み上がり、過去分の再実行に一苦労しました。

ちょうどt-wadaさんの実録レガシーコード改善を読んで意識が高まっていたので、まごうことなきレガシーコードであるこの関数をTDDで改善していきます

speakerdeck.com

  • TDDによるモダナイズを体験する
  • openapi-typescriptを利用してタイプセーフなコードを書く
  • エラー時にSlack経由で自分にメンションを飛ばし、検知を早める

もともとSwaggerのAPI定義は公開されていたのですが、当時はTSの型定義にうまく変換できずにいたために、anyでお茶を濁していたのですが、そうも言っていられないので今回はキッチリやることが目標です。

序であり結:型を追加する

まずテストを増やしたいところですが、TypeScriptの恩恵で対処できる部分を先に潰しておきます。

結論から言うとこれだけでやりたいことがほぼ終わってしまいました…テストは異常系を追加してログの出力確認をしたのみです

使ったものはOpenAPI-typescript

github.com

OpenAPIの定義ファイルからd.tsファイルを生成してくれるので、typesディレクトリに突っ込んでターンエンド。

こんな感じでAPIのパスごとに型がついているので、呼び出してanyを全部潰していきます。

エラーが無くなるまで書き直して、イケてない関数名などを直して終了。

テスト書き始めたころに作っていた単体テストからは、環境変数依存を無くしてテストしやすくしてDone。

建売住宅断熱への道① コンセントからの冷気流入をDIYで塞ぐ

エアコンを止めるとあっという間に家が(暑くなる|寒くなる)…

日本の戸建て住宅は、2024年からようやく新築の省エネ基準が高くなりはじめ、認証がない新築は住宅ローン減税の対象外になりはじめている。

とはいえ、既存住宅の大半は薄い壁・スカスカの構造であり、UA値やC値なんぞ知ったことではないという建物が大半だろう。

我が家も多分に漏れず、長期優良やBELSの星など取っていない建売住宅だ。

気密が怪しく、換気はショートサーキットを起こしているっぽいし、エアコンを入れても何となく冷気が床に出てくる。そんな家だったが、冷気のもとをたどると、どうやらコンセント・スイッチボックス周りから冷たい空気が染み出しているっぽいことがわかった。

断熱気密を謳わない住宅ではコンセントカバーを外すと外気に近い温度の空気が流れてくる

カバーを外すと、冷気がブワっと出てくる。

外壁に面している箇所は特に顕著で、こんな場所が部屋に何カ所もあったらそりゃ断熱もクソもないなという状態。

ちゃんとした高断熱・高気密の家はこうした部分の隙間もきっちり加工されて室温が保たれるようになっているのだが、建売のローコスが正義とされる木造住宅ではそんな配慮は存在しない。

そこで、後付けで建売住宅の気密をちょっとマシにしている先人たちに倣って、家中の隙間を減らすことにした。用意するものは、パナソニックの防気カバーと、気密テープだ。

家のすべての箇所をカウントして、2連・3連もあわせて調達。

正しい施工プロセスは、電線を外して防気カバーに突き刺すという手順だが、これは電気工事士の資格が必要な行為。

自宅で自分のためにやる分には大した問題は発生しないと思うが、専門知識が無いと「これだけはやってはいけないライン」というものが分からない。

そんなわけで、無資格でもできる「カバーに切れ込みを入れていい感じに線を通し、隙間はテープで貼り合わせてなかったことにする」という力技をやってみた。

カバーに切れ込みを入れ配線を逃がし、取り付けをイメージする

作業は非常に単純だ。

  1. カバーを外し、ボックスへのねじ止めしてあるフレームを外す
  2. 配線の出ていく方向を見極める
  3. 無理なく配線できる部分から切れ込みを入れる
  4. 配線を逃がしながらカバーを装着する
  5. 気密テープで切れ込みや隙間をふさぐ
  6. ねじ止めしなおしてカバーをつけなおす

これだけ。3連になってくると切れ込みの形状が複雑になりやや大変だが、ほとんどは1連で済むのでトータル作業は2時間ほどで済んだ。

作業は電動ドライバーがあると楽。

配線逃がしの切れ込みを気密テープで塞ぐ

切れ込み効率的に入れて、気密テープで塞ぐ手順は立体パズルを解いているようで楽しくなってくる。

もちろん、正規の作業手順に比べれば、気密度は低い。とはいえ、そもそも家全体の躯体に対する加工をしていないので、作業の丁寧さを多少失ったところで、全体への影響は軽微だろう。

カバーを戻すと、空気の流入が露骨に減ったことが分かる

カバーを付けた状態で手をかざすと、まったく風の流入を感じない。

床にゴロ寝すると寒い気流を感じていた家だが、少なくとも足元と立った時の頭の高さで露骨に温度が変わる…ということはなくなった。

家全体で考えると、コンセントボックスの「穴」の大きさはなかなかのもの。

資材自体は数千円で揃うので、なかなか投資対効果の高い作業だった。

Nest Cam(Battery)にソーラーパネルを追加して電源不要の監視カメラを実現する

Nest Cam(バッテリー)

store.google.com

Nest CamはGoogleストアで販売しているスマート監視カメラ。 「壁とマウント」「マウントと本体」がマグネットで吸着するため、鉄製の壁なら加工なし、通常の住宅の壁でも強力な下地なしに鉄板さえ追加で張り付ければどこにでも、無段階で角度をつけて取り付けができます。

この無段階というのがミソで、一般的な屋外監視カメラでは、首振りの角度で設置場所の制限が発生するところ、Nest Camはほぼどこにでも設置できて好みの画角に設定できるというメリットがあります。

AnkerやRingのカメラは、上下チルトや左右の振りに制限があり、追加でマウントを買わないと自由度が確保できません。

Pixelシリーズを買った時に大量に付与されるクレジットの使い道として購入して使っていました。デフォルトでは過去3時間のイベントごと録画しか記録できないため、Nest Awareはほぼ必須でしょう。

800円/月に値上がりしたのは痛いですが、顔認識や動画保存はNest Camのメリットを享受するために必須(というよりこれらがなければAnkerやRingでよい)です。

高度な自動化

Google Homeアプリでの自動化にカメラトリガーは実装されていませんが、ベータ版のスクリプトエディタからカメラの人物認識をトリガーにアクションを実行できます。

HomeのスクリプトエディタはWeb版のみアクセス可能です。これで帰宅したら自室の明かりがONになるように設定しています。

home.google.com

スピーカーから任意の音声が流れるようにしても面白いですね。

充電、取り外し、エリア再設定

Nest Awareを契約していると、人物以外にもカメラの物体検知がトリガーされた際にスマートフォンアプリで通知を受けられるのですが、大抵の場合は自宅の敷地外もカメラの映像範囲内に入っていると思います。

そこはどのメーカーでもあると思うのですが、カメラの撮影範囲内の特定のエリアを指定して、通知の有無を設定できます。

これで、自宅敷地内に人が映った場合のみ通知するという、一般的な監視カメラに求められる機能が実現できます。

通知用の検知エリアを設定できるが、充電すると画角が変わるので再設定必須

ただ、Nest Camは本体とバッテリーが一体化しているので、充電の際は本体を取り外してバッテリーを室内で充電する必要があります。

常時電源用のケーブルはあるのですが、AC側が無駄に大きく、一般的な日本家屋の防水コンセントには装着できません。ちゃんとフィードバック受けてるのか?

store.google.com

しかも、充電のたびに取り外して、充電後に再度マグネット装着をすると画角が変わってしまいます。そうすると、前述のエリア設定も座標がずれてしまうので再設定が必要に…

大体2~3か月でバッテリーがゼロになるので、そのたびに再設定です。めんどい。

ソーラーパネルで充電から解放

store.google.com

さすがに半年ほど運用して面倒になったので、ソーラーパネルで充電頻度を下げる作戦に出ました。

代償として、1日中日陰の位置には設置できなくなりますが、幸い我が家では排水パイプに装着できそうでした。

パイプは樹脂なので、防犯カメラブラケットを装着してそこにソーラーパネルとNest Camを装着しています。

ブラケットが鉄製なので、Nest Camはぺたっと貼り付けるだけでOKなのがうれしい。

夏場はカメラに高熱アラートが出ていましたが、ソーラーパネルで隠すことで夏場の熱問題の解決も狙ってます。付属ケーブルが無駄に長いですが、日差しから隠れるように巻いてます。

ソーラーパネルによるバッテリー寿命無限大へ

ソーラーパネルを接続するとバッテリーレベルが見えなくなる謎仕様

設置場所は南西側で、南側には隣家があるため冬場の直射日光は午後の数時間しか当たらないという環境。

それでも、10月~2月の間でバッテリーが一度も0にならないという快挙を達成!

寒気が強い日は低温アラートが来ることもありましたが、録画は常に継続できています。夏場はこれからですが、熱による問題を除けば冬より発電量は多いはずなので、年間無充電を計画しています。

世界最速でHonoをAmplify Hostingで動かしてみた

前置き

筆者はAWSジャパンのソリューションアーキテクトです。本記事は個人的な興味関心による検証記事となります。

普段は常体で書いているこのブログですが、本記事は書きやすさから敬体で書いていきます。

AmplifyにおけるNext.js"以外"のSSRサポート

これまで、Next.jsのSSRのみをサポートしていましたが、Nuxt.jsのSSRサポートと同時に、node.jsで実行できる任意のフレームワークSSRアプリケーションを実行できるようになりました。

aws.amazon.com

ドキュメントには、Amplify Hosting用のディレクトリ構成が記載されており、ビルド成果物をこのディレクトリ構成に合わせて調整し、 deploy-manifest.json に設定を記載してやることで任意のエントリポイントからアプリケーションを起動できます。

※2023/11/21時点ではドキュメントは英語版のみ更新されています

docs.aws.amazon.com

Next.js および Nuxt.js 以外は公式アダプタが提供されておらず、この記事を書いている時点では自分でこれらの設定を記述する必要があるので、勉強がてら自分でアダプターを設定して作ってみました。

直近で一番利用頻度が高いフレームワークと言うことで、Astro SSRを試してみたかったところですが、エムスリーさんに先を越されてしまったため、別のフレームワークで試してみることにしました。

そこで、軽量かつ、エッジランタイムで爆速なWebフレームワーク、HonoをAmplify Hostingにデプロイしていきます。

github.com

なお、本記事で利用しているコードは以下のリポジトリで公開しています。

github.com

Hono Node.js Adapter

Honoといえば、専ら話題になるのはCloudflare Workers, Deno, Bunなどのエッジランタイムでの動作ですが、LambdaやLambda@Edgeをはじめとする一般的なnode環境でも使うことができます。

Amplify HostingのSSRはnode.jsで動いているように読めるので、今回はこのアダプターを使ってHonoプロジェクトを作成していきます。

ドキュメントのQuickStartに従ってプロジェクトを作成します。

npm create hono@latest
>nodejs

SSRであることがわかりやすくなるよう、ヘッダからUser-Agentを表示するロジックを追加して、localhostで動作確認します。

//src/index.ts
import { serve } from "@hono/node-server"
import { Hono } from "hono"

const app = new Hono()
app.get("/", c => {
  const userAgent = c.req.raw.headers.get("User-Agent")
  return c.text(`Hello Hono!\nUA: ${userAgent}`)
})

serve(app)

開発サーバーを起動した様子

うまくいきました。飾り気がないですが動作確認には十分なのでこのまま進めていきます。

Amplify Hosting用の設定ファイルを追加する

ドキュメントにExpressサーバー用のサンプルが記載されているので、これを参考にしながら設定していきます。

docs.aws.amazon.com

Amplify Hostingの設定については、ほぼエムスリーさんのブログの通りなので省略します。

www.m3tech.blog

たまたまローカルのnodeバージョンが18になっていたので、私は「ライブパッケージの更新」で Node.js version に18を設定しました。

tsxでentrypointを叩けるのか?

create hono で作成したサンプルはtsxを使って内部的にesbuildを使ったトランスパイルをしてTypescriptを実行しており、プロジェクトディレクトリに明示的にトランスパイル後のファイルが出てきません。

これは、感覚としてはTypeScriptをそのまま実行できており、開発者体験もよいので、「tsxでの実行をそのままAmplify Hostingに持ち込めたらいいなぁ~~!」くらいの気持ちで、ソースをそのまま.amplify-hostingディレクトリにコピーしてentrypointにindex.tsを指定してみました。

npm serveを自動的に認識していい感じに動いてくれればもうけものです。

//deploy-manifest.json
{
  "version": 1,
  "framework": { "name": "hono", "version": "3.10.2" },
  "imageSettings": {
    "sizes": [100, 200, 1920],
    "domains": [],
    "remotePatterns": [],
    "formats": [],
    "minimumCacheTTL": 60,
    "dangerouslyAllowSVG": false
  },
  "routes": [
    {
      "path": "/_amplify/image",
      "target": {
        "kind": "ImageOptimization",
        "cacheControl": "public, max-age=3600, immutable"
      }
    },
    {
      "path": "/*.*",
      "target": {
        "kind": "Static",
        "cacheControl": "public, max-age=2"
      },
      "fallback": {
        "kind": "Compute",
        "src": "default"
      }
    },
    {
      "path": "/*",
      "target": {
        "kind": "Compute",
        "src": "default"
      }
    }
  ],
  "computeResources": [
    {
      "name": "default",
      "runtime": "nodejs18.x",
      "entrypoint": "index.ts" ←
    }
  ]
}

ドキュメント通りにpostbuild.shも作りますが、ビルドせずにソースをそのままコピーしてみます。

# postbuild.sh
#!/bin/bash

rm -rf ./.amplify-hosting

mkdir -p ./.amplify-hosting/compute

# 改変
cp -r ./src ./.amplify-hosting/compute/default 
cp -r ./node_modules ./.amplify-hosting/compute/default/node_modules

cp -r public ./.amplify-hosting/static

cp deploy-manifest.json ./.amplify-hosting/deploy-manifest.json

ビルドは成功したのですが、動きませんでした。そこまで都合よくはいきません…

jsファイルにトランスパイルする処理を追加していきます。

Honoのtsファイルをトランスパイルする

//tsconfig.json
{
  "compilerOptions": {
    "target": "es6",
    "module": "commonjs",
    "outDir": "./dist",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true
  },
  "include": ["src/**/*.ts"],
  "exclude": ["node_modules"]
}
//package.json
{
  "type": "commonjs",
  "scripts": {
    "start": "tsx watch src/index.ts",
    "build": "tsc",
    "serve": "tsx watch src/index.ts",
    "postbuild": "chmod +x bin/postbuild.sh && ./bin/postbuild.sh"
  },
  "dependencies": {
    "@hono/node-server": "^1.2.3",
    "hono": "^3.10.2"
  },
  "devDependencies": {
    "@types/node": "^20.9.3",
    "tsx": "^3.12.2",
    "typescript": "^5.3.2"
  }
}

ビルドコマンドにtscを指定しつつ、ESmoduleとして認識されないように"type":"commonjs"を追加します。

tsconfigは、ドキュメントのものをそのままコピペして、postbuild.shdeploy-manifest.json もサンプル通りに修正しまして、無事デプロイが通りUser-Agentが表示されたことを確認出来ました。

オリジンの前にAmplifyがCloudfrontを挟んでアクセスさせているため、UAもCloudFrontと表示されています。

Astro MDX integrationで画像をレスポンシブ対応にする方法

自転車ブログをGatsbyからAstroに書き換えた。

blog.gensobunya.net

新興フレームワークだけあり、Gatsbyのように豊富なプラグインによる便利な設定というものはないので、殆どの機能を自分で実装する必要がある。

Head APIがなく、レイアウトコンポーネントから必要な要素を全てPropsで渡してheadタグに記述したり、クライアント処理をAstroでやるのはつらいのでReactを使ったりと、Gatsbyに慣れているといろいろ面倒なところが多く感じた。

その中でも、ナレッジが少なく(というかundocumented)でデフォルト処理してくれない、MDX内に相対パスで記述した画像をレスポンシブ化する部分だけ抜粋してメモ。

背景

AstroにはContent Collectionという機能があり、srcディレクトリ内に配置された画像を自動的にフォーマット変換・レスポンシブ対応で自動的に複数サイズの画像をsharpレンダリングする機能がある。

.astroコンポーネントの場合は、組み込みの<Image>, <Picture>タグを通じてこの最適化をコントロールできる。

docs.astro.build

一方で、このContent Collectionは.md, .mdxファイル内の画像リンクも最適化してくれるのだが、この最適化は最適化と名ばかりのオリジナルサイズのwebpを吐き出すだけである(しかもフォールバックもない)。

Content Collectionには(ドキュメントを読む限り)グローバル設定が存在しないうえ、gatsby-plugin-image のようなキャプション設定機能も存在していないため、自前でこれらを実装する必要がある。

Astroの書き味はNext.jsに似ていて、シンプルなコア機能やコンポーネントを提供する方針のようだ。

前提

  • Astro.3.x
  • Content Collection
  • @astro/mdx integration >1.1.0

実装

実は、@astro/mdx インテグレーションにはCustom Componentのマッピングimgを指定した場合、ContentCollectionによって処理された画像に限り AstroのImageMetadataタイプがPropsとして渡される仕様になっている。

github.com

これを知っていると非常にシンプルに記述できる。この情報にたどり着くまでは自分でrehype, remarkプラグインを書かねばならないのかと絶望していたところ。

---
//MdxPicture.astro
import type { ImageMetadata } from "astro"
import { Picture } from "astro:assets"
type Props = {
  src: string | ImageMetadata
  alt: string
}
const { src, alt } = Astro.props
---

{
  typeof src === "string" ? (
    <figure>
      <img class="mx-auto" src={src} alt={alt} />
      <figcaption class="not-prose text-center text-xs text-secondary md:text-sm">
        {alt}
      </figcaption>
    </figure>
  ) : (
    <figure>
      <Picture
        formats={["webp"]}
        fallbackFormat="jpg"
        widths={[360, 752]}
        class="mx-auto"
        {src}
        {alt}
      />
      <figcaption class="not-prose text-center text-xs text-secondary md:text-sm">
        {alt}
      </figcaption>
    </figure>
  )
}
//[...slug].astro
---
~
~
import { MdxPicture } from "~~~"
~
~
const { Content } = await post.render()
---

  <Content
    components={{
      ~
      ~
      img: MdxPicture
    }}
  />


componentsとして渡すときに特段Propsとして明示的に渡す必要がないところがポイント。

ドキュメントに書く価値のある情報のはずなのだが…