100日チャレンジ day49 (ミニブラウザ)

昨日
100日チャレンジに感化されたので、アレンジして自分でもやってみます。
やりたいこと
- 世の中のさまざまなドメインの簡易実装をつくり、バックエンドの実装に慣れる(dbスキーマ設計や、関数の分割、使いやすいインターフェイスの切り方に慣れる
- 設計力(これはシステムのオーバービューを先に自分で作ってaiに依頼できるようにする
- 生成aiをつかったバイブコーティングになれる
- 実際にやったことはzennのスクラップにまとめ、成果はzennのブログにまとめる(アプリ自体の公開は必須ではないかコードはgithubにおく)
できたもの

Webブラウザ を作りたい

Go言語でブラウザを作成するプロジェクト、承知いたしました。非常に挑戦的で興味深いテーマですね!
「最低限のHTML/CSS/JSを描画できる」というご要望を踏まえ、現実的なミニマム仕様として以下を提案します。
Day49: Go ミニブラウザ
目標:
Go言語とGUIライブラリ(Fyneを想定)を使い、基本的なHTMLコンテンツを表示できる最小限のブラウザを作成します。レンダリングエンジンの複雑さを体験し、ウェブページの基本的な仕組みを理解することを主眼とします。フル機能のブラウザではなく、学習目的のためのシンプルな実装を目指します。
主な機能:
-
URLナビゲーション:
- ユーザーがURLを入力し、「Go」ボタンでページを読み込みます。
-
HTTPリクエスト:
- 指定されたURLのHTMLコンテンツを
net/http
を用いて取得します。
- 指定されたURLのHTMLコンテンツを
-
HTMLパース:
- 取得したHTMLを
golang.org/x/net/html
でDOMツリーにパースします。
- 取得したHTMLを
-
基本HTML要素レンダリング:
- 以下の要素をFyneウィジェットを使って描画します:
- テキストコンテンツ (
<p>
,<h1>
~<h6>
,<span>
,<div>
等のテキストノード)。 - リンク (
<a>
): テキストとして表示し、href
属性値をログに出力します(実際のナビゲーションは発展課題とします)。 - 画像 (
<img>
):src
属性から画像を非同期で読み込み、表示します。
- テキストコンテンツ (
- Fyneの
RichText
ウィジェットや、要素に応じた複数のウィジェット(Label
,Image
など)を垂直コンテナに配置することを検討します。
- 以下の要素をFyneウィジェットを使って描画します:
-
超基本CSSサポート (努力目標):
- インラインスタイル (
style="..."
) の一部プロパティ(例:color
,background-color
,font-weight: bold
,font-style: italic
)を解釈し、テキストスタイルに反映させます。 - 複雑なCSSセレクタやカスケード処理、ボックスモデルに基づくレイアウトエンジンは実装しません。Fyneのレイアウト機能に依存し、基本的には上から下に要素を配置するシンプルなものとします。
- インラインスタイル (
-
JavaScriptサポート (範囲外 → 超限定的サポートに検討の余地あり):
- フル機能のJSエンジン搭載は今回のスコープでは非常に困難なため、原則として
<script>
タグは無視します。 - もし余裕があれば、Goで書かれたJSインタプリタ(
goja
など)を組み込み、alert()
のようなごく一部の超基本的なDOM操作を伴わない関数呼び出しのみを試みることを検討できますが、優先度は低とします。
- フル機能のJSエンジン搭載は今回のスコープでは非常に困難なため、原則として
UI構成 (Fyne):
- URL入力用の
widget.Entry
- ページ読み込み実行用の
widget.Button
- レンダリングされたHTMLコンテンツ表示用のスクロール可能なエリア (
container.Scroll
の中に、HTML要素に対応するウィジェットを動的に配置するcontainer.VerticalBox
などを使用) - (オプション) 読み込み状況やエラーを表示するステータスバー的なラベル
技術スタック:
- 言語: Go
- GUI: Fyne (Day48での経験を活かします)
- HTMLパーサー:
golang.org/x/net/html
- HTTPクライアント:
net/http
- 画像処理:
image
標準パッケージ (PNG, JPEGデコード) - (JSを試す場合) JSインタプリタ:
github.com/dop251/goja
(オプション)
期待される学び:
- ウェブブラウザの基本的な動作原理(HTML取得、パース、レンダリングフロー)の理解。
- DOMツリーの構造とトラバーサル。
- GUIアプリケーションにおける非同期処理(画像読み込みなど)。
- Go言語での比較的複雑なアプリケーション構築。
- (CSS/JSサポートに挑戦する場合) スタイリングやスクリプティングの基礎的な処理。
主な作業ステップ案:
-
プロジェクト初期化 (約0.5時間):
-
template_go
(Goプロジェクト用のテンプレートがあれば。なければtemplate
から必要なものを流用・調整) をコピーしてday49_go_simple_browser
ディレクトリを作成。 -
go.mod
を初期化し、モジュール名をgithub.com/lirlia/100day_challenge_backend/day49_go_simple_browser
に設定。 - Fyne (
fyne.io/fyne/v2
) およびその他の必要な依存関係 (golang.org/x/net/html
は標準ライブラリに近いですが明示的に取得が必要な場合あり) をgo get
。 -
README.md
にアプリ概要、PROGRESS.md
に作業ステップを記載。 - 基本的な
main.go
を作成 (空のFyneウィンドウ表示)。
-
-
基本UIレイアウト作成 (約1時間):
-
main.go
に、FyneでURL入力欄、Goボタン、コンテンツ表示エリア(最初は空のスクロール可能なコンテナ)の骨組みを作成。
-
-
HTTPリクエスト機能 (約0.5時間):
-
network/http_client.go
を作成。 - URL文字列を受け取り、HTTP GETリクエストを送信し、レスポンスボディ (HTML) を文字列として返す関数を実装。エラーハンドリングも行う。
-
-
HTMLパースとテキスト抽出 (約1.5時間):
-
parser/html_parser.go
を作成。 - HTML文字列を受け取り、
golang.org/x/net/html
を使ってパースし、DOMツリーを構築する。 - まずはDOMツリーから全てのテキストノードを再帰的に抽出し、結合した文字列を返す関数を実装。
- GoボタンクリックでHTMLを取得・パースし、抽出したテキストをコンテンツ表示エリアに表示する。
-
-
HTML要素ごとのレンダリング (約3-4時間):
-
renderer/renderer.go
を作成。 - DOMツリーのノードを受け取り、そのタイプに応じて適切なFyneウィジェット(またはウィジェットのツリー)を返す関数群を実装。
- テキストノード:
widget.Label
(またはwidget.RichText
のセグメント) -
<h1>
~<h6>
: テキストサイズや太さを変えたwidget.Label
-
<p>
:widget.Label
-
<a>
:widget.Hyperlink
(ただし、実際の遷移はまだ行わず、URLをログに出力する程度) または装飾したwidget.Label
-
<img>
:src
属性から画像を非同期でダウンロードし、widget.Image
で表示。プレースホルダーやエラー表示も考慮。 -
<div>
,<span>
など: 内容に応じてコンテナやラベルを適切に使う。
- テキストノード:
- DOMツリーを再帰的に処理し、生成されたFyneウィジェットをコンテンツ表示エリアの垂直コンテナに順に追加していく。
-
-
(オプション) 基本インラインCSS対応 (約2-3時間):
-
parser/css_parser.go
(またはrenderer
内) で、HTML要素のstyle
属性を解析する簡単な関数を実装。 -
color
,background-color
,font-weight
,font-style
などの基本的なプロパティに対応。 - レンダリング時にこれらのスタイルをFyneウィジェットに適用する。
-
-
(超オプション) ごく限定的なJavaScript
alert()
サポート (約2-3時間):-
goja
を導入。 -
<script>
タグの内容を取得。 -
alert()
関数呼び出しのみを検出し、Fyneのダイアログで表示する。他のJSは無視またはエラーログ。
-
-
テスト・デバッグ・調整 (約1-2時間):
- いくつかの簡単なHTMLファイルや実在のシンプルなウェブページで動作確認。
- エラーハンドリング(ネットワークエラー、パースエラー、画像読み込みエラーなど)の強化。
- スクロール、ウィンドウリサイズ時の挙動調整。
- 不要なコードの削除、リファクタリング。
-
ドキュメント更新 (約0.5時間):
-
README.md
に最終的な機能、使い方、学んだことなどを追記。 -
PROGRESS.md
の完了チェック。
-
ディレクトリ構成案:
/day49_go_simple_browser
├── go.mod
├── go.sum
├── main.go // アプリケーションのエントリポイント、UI構築、イベントハンドリング
├── network/
│ └── http_client.go // HTTPリクエスト処理
├── parser/
│ ├── html_parser.go // HTMLパースとDOMツリー構築
│ └── css_parser.go // (オプション) 簡単なインラインCSSパーサー
├── renderer/
│ └── renderer.go // DOMノードからFyneウィジェットへの変換・描画ロジック
├── js_engine/ // (超オプション)
│ └── goja_engine.go // gojaを使った超限定的JS実行
├── README.md
└── PROGRESS.md
この仕様と作業手順で進めてよろしければ、最初のステップであるプロジェクト初期化から開始します。
特にJavaScriptの扱いについてはご希望に応じて調整可能です。まずはHTMLと基本的なCSSのレンダリングに注力するのが良いかと考えています。
いかがでしょうか?

このホームページを描画できるようにする

阿部寛さんのホームページですね!承知いたしました。あのサイトはシンプルながらも、ブラウザの基本的な機能をテストするには非常に良い題材です。
目標を「阿部寛さんのホームページ ( http://abehiroshi.la.coocan.jp/ ) の主要なコンテンツ(テキスト、画像)をそれなりに表示できること」と設定し、仕様を調整しましょう。
Day49: Go ミニブラウザ (阿部寛サイト対応版)
主な変更点・考慮事項:
-
文字エンコーディング:
- 阿部寛さんのサイトは Shift_JIS でエンコードされています。HTTPレスポンスヘッダの
Content-Type
を確認し、Shift_JISであればUTF-8に変換する処理が必須です。Goのgolang.org/x/text/encoding/japanese
とgolang.org/x/text/transform
パッケージを利用します。
- 阿部寛さんのサイトは Shift_JIS でエンコードされています。HTTPレスポンスヘッダの
-
HTML構造とレンダリング:
-
<table>
レイアウト: サイトはテーブルでレイアウトされています。Fyneで忠実なテーブルレイアウトを再現するのは複雑なので、まずはテーブル内のセルコンテンツを上から順番に表示することを目標とします。 -
<font>
タグ: 色やサイズの指定に使われています。Fyneのwidget.RichText
などで限定的にスタイルを反映させることを試みます。 -
<center>
タグ: Fyneのコンテナオプションで中央揃えを試みます。 -
<img>
タグ: GIF画像が使われている可能性があります。Go標準のimage/gif
パッケージでデコードできるようにします。 -
<br>
タグ: 改行として適切に処理します。 - その他、基本的なタグ (
<b>
,<a>
など) に対応します。
-
-
CSS:
- 外部CSSや複雑な
<style>
ブロックは無いと想定し、主に<font>
タグと基本的なHTML属性によるスタイリングに注力します。
- 外部CSSや複雑な
-
JavaScript:
- 使用されていない可能性が高いですが、もし使われていてもコンテンツ表示に必須ではないと想定し、引き続きスコープ外とします。
更新された作業ステップ案:
-
プロジェクト初期化 (約0.5時間):
-
template_go
ディレクトリがないため、template
ディレクトリをコピーしてday49_go_simple_browser
を作成します。 -
day49_go_simple_browser
に移動します。 -
go.mod
ファイルを作成し、モジュール名をgithub.com/lirlia/100day_challenge_backend/day49_go_simple_browser
に設定します。(手動またはgo mod init
) - 必要な依存パッケージ (
fyne.io/fyne/v2
,golang.org/x/net/html
,golang.org/x/text
) をgo get
で取得します。 -
README.md
とPROGRESS.md
を作成・初期記述します。 -
main.go
を作成し、Fyneで空のウィンドウが表示されることを確認します。
-
-
基本UIレイアウト作成 (約1時間):
-
main.go
に、URL入力欄 (widget.Entry
)、読み込みボタン (widget.Button
)、コンテンツ表示用スクロールエリア (container.Scroll
内に*fyne.Container
を配置) を作成します。
-
-
HTTPリクエストと文字コード変換 (約1.5時間):
-
network/http_client.go
を作成。 - 指定されたURLからHTTP GETでコンテンツを取得する関数を実装。
- レスポンスヘッダの
Content-Type
からcharset
を判別し、shift_jis
の場合はgolang.org/x/text/encoding/japanese
を使ってUTF-8に変換する処理を実装。 - テスト: 阿部寛のサイトURLを指定し、HTMLコンテンツがUTF-8文字列として取得できることを確認。
-
-
HTMLパースとDOMツリー構築 (約1時間):
-
parser/html_parser.go
を作成。 - 取得したHTML文字列を
golang.org/x/net/html
を使ってパースし、DOMツリーを構築する関数を実装。 - テスト: 阿部寛のサイトのHTMLをパースし、エラーなくDOMツリーが構築できることを確認。
-
-
基本HTML要素レンダリング (Fyneウィジェットへの変換) (約4-5時間):
-
renderer/renderer.go
を作成。 - DOMツリーを再帰的にトラバースし、各HTMLノードに対応するFyneウィジェットを生成してコンテナに追加するロジックを実装。
- テキストノード:
widget.NewLabel
またはwidget.NewRichTextFromSegments
の一部として。 -
<h1>
~<h6>
,<p>
: スタイルを調整したwidget.RichText
またはwidget.Label
。 -
<a>
:widget.NewHyperlink
(URLはログ出力)。 -
<img>
:src
から画像を非同期でダウンロード (GIF, PNG, JPEG対応)。image/gif
,image/png
,image/jpeg
を利用。 -
<br>
: 新しいwidget.Label
を追加するか、RichText
内で改行として扱う。 -
<b>
,<i>
:RichText
のセグメントスタイルで対応。 -
<font>
:color
,size
属性を読み取り、RichText
のスタイルに(限定的に)反映。 -
<table>
,<tr>
,<td>
,<th>
: セル内のコンテンツを順に垂直に並べる形で表示。 -
<center>
: ウィジェットを中央揃えのコンテナに入れることを試みる。 - その他未知のタグ: テキストコンテンツのみ抽出して表示するか、無視。
- テキストノード:
- テスト: 阿部寛のサイトのHTMLをレンダリングし、テキストや画像が部分的にでも表示されることを確認。
-
-
UIイベント処理と全体結合 (約1時間):
-
main.go
で、Goボタンが押されたらURL取得→パース→レンダリングを行い、結果をコンテンツ表示エリアに反映するよう接続。 - エラー発生時の表示(例: ステータスラベルやダイアログ)。
-
-
テスト・デバッグ・調整 (約2時間):
- 阿部寛のホームページを実際に表示し、文字化け、画像表示、レイアウト崩れ(ある程度は許容)などを確認・修正。
- 特にShift_JISの変換、GIF画像の表示、
<font>
タグの基本的な解釈を中心にデバッグ。 - Fyneのウィジェットやコンテナの適切な使い方を調整。
-
ドキュメント作成 (約0.5時間):
-
README.md
に実装内容、使い方、阿部寛サイトの表示結果(スクリーンショットなどあれば理想的ですが、テキストベースでOK)、学んだことを記載。 -
PROGRESS.md
の完了チェック。
-
この計画で進めてよろしければ、最初のプロジェクト初期化ステップから着手します。
ディレクトリ構成は、以前提案したものをベースにします。
/day49_go_simple_browser
├── go.mod
├── go.sum
├── main.go
├── network/
│ └── http_client.go
├── parser/
│ └── html_parser.go
├── renderer/
│ └── renderer.go
├── README.md
└── PROGRESS.md
いかがでしょうか?承認いただければ、ディレクトリ作成と go mod init
から始めます。