feat(T-001): 初始化 go 模块 + go-app 最小可运行页面
- go.mod:module lingo,依赖 go-app v10.1.11 - main.go:go-app 装配入口;init() 注册路由(Handler 仅为已注册路由返回 app shell),newHandler() 抽出便于测试,main() 起 http 服务 - main_test.go:in-process httptest 冒烟测试,验证根路径 200 + home 组件预渲染 + wasm 引导脚本注入 - .gitignore:忽略 /bin/ 构建产物 - 06-tasks:T-001 标 DONE;current-state 更新为 Phase 0 进行中、下一任务 T-002 验证:gofmt 干净 · go vet 通过 · go test 通过 · GOOS=js GOARCH=wasm go build 通过 · go build 通过 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -37,6 +37,9 @@ go.work.sum
|
||||
# 前端构建产物
|
||||
*.wasm
|
||||
|
||||
# 后端构建产物
|
||||
/bin/
|
||||
|
||||
# 设计参考图(第三方截图,仅本地参考,不入库)
|
||||
/design_images/
|
||||
|
||||
|
||||
+1
-1
@@ -24,7 +24,7 @@
|
||||
|
||||
| ID | 任务 | 依赖 | 验收要点 | 状态 |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| T-001 | 初始化 go 模块 + go-app 最小可运行页面 | — | `go.mod` 就位;`GOOS=js GOARCH=wasm go build` 通过;浏览器打开能看到一个最简页面 | TODO |
|
||||
| T-001 | 初始化 go 模块 + go-app 最小可运行页面 | — | `go.mod` 就位;`GOOS=js GOARCH=wasm go build` 通过;浏览器打开能看到一个最简页面 | DONE |
|
||||
| T-002 | 后端静态服务 + **音频路径映射** | T-001 | 访问 `/family-album/audio/u0101.mp3` 能播到磁盘 `family-album-usa/audio/u0101.mp3`;wasm/静态资源正常加载 | TODO |
|
||||
| T-003 | 加载并解析 `episodes.json` 为 Go 结构体 | T-001 | struct 与真实 schema 一致(line 仅 `{t,en}`,无 zh);启动时成功载入 26 集;前端可拿到课程数据 | TODO |
|
||||
|
||||
|
||||
+16
-16
@@ -5,9 +5,9 @@
|
||||
## 当前快照
|
||||
|
||||
- 日期:2026-06-21
|
||||
- 阶段:MVP 起步前 / 文档与素材已就位
|
||||
- Go 模块:尚未初始化,当前无 `go.mod`
|
||||
- 生产代码:尚未开始
|
||||
- 阶段:MVP 进行中 / Phase 0 地基起步
|
||||
- Go 模块:已初始化(`go.mod`,module `lingo`,go-app v10.1.11)
|
||||
- 生产代码:`main.go`(go-app 装配入口 + home 骨架组件)、`main_test.go`(app shell 冒烟测试)
|
||||
- 设计稿:已完成 `design_mockups/list.html` 和 `design_mockups/play.html`
|
||||
- 内容素材:`family-album-usa/episodes.json` 和 `family-album-usa/audio/` 已存在
|
||||
|
||||
@@ -21,36 +21,36 @@
|
||||
| `design_mockups/` | 已有 | UI spike,作为视觉参考 |
|
||||
| `scripts/` | 已有 | 内容数据校验等脚本 |
|
||||
| `notion_docs/` | 只读 | Notion 原始导出,不作为事实来源 |
|
||||
| `go.mod` | 不存在 | T-001 需要创建 |
|
||||
| `go.mod` | 已有 | module `lingo`,依赖 go-app v10 |
|
||||
| `main.go` | 已有 | go-app 装配入口(T-001) |
|
||||
|
||||
## 任务看板状态
|
||||
|
||||
以 [`06-tasks.md`](06-tasks.md) 为准。
|
||||
|
||||
当前应领取的第一个开发任务:
|
||||
|
||||
- `T-001`:初始化 go 模块 + go-app 最小可运行页面
|
||||
|
||||
不要跳到课程列表、账号、生词本或 PWA 离线任务。
|
||||
- `T-001` 已 `DONE`(go 模块 + 最小可运行页面)。
|
||||
- 下一个可领取任务:`T-002`(后端静态服务 + 音频路径映射)或 `T-003`(解析 episodes.json),二者都只依赖 T-001,取最靠前的 **T-002**。
|
||||
|
||||
## 当前可运行内容
|
||||
|
||||
目前没有生产 App 可运行。
|
||||
最小 go-app 应用已可构建运行:
|
||||
|
||||
可以直接打开这些 HTML spike 查看设计参考:
|
||||
```bash
|
||||
GOOS=js GOARCH=wasm go build -o web/app.wasm . # 构建前端 wasm
|
||||
go build -o bin/lingo . && ./bin/lingo # 起服务器,访问 http://localhost:8000
|
||||
```
|
||||
|
||||
- `design_mockups/list.html`
|
||||
- `design_mockups/play.html`
|
||||
设计参考 spike:`design_mockups/list.html`、`design_mockups/play.html`。
|
||||
|
||||
## 开始编码前检查
|
||||
|
||||
开始 T-001 前:
|
||||
开始任意任务前:
|
||||
|
||||
1. 读根目录 `AGENTS.md`。
|
||||
2. 读 `CLAUDE.md`。
|
||||
3. 读 `docs/05-coding-rules.md`。
|
||||
4. 确认 `docs/06-tasks.md` 中 T-001 仍是第一个可做任务。
|
||||
5. 将 T-001 状态改为 `DOING`。
|
||||
4. 在 `docs/06-tasks.md` 取第一个 `TODO` 且依赖均 `DONE` 的任务(当前为 `T-002`)。
|
||||
5. 将该任务状态改为 `DOING`。
|
||||
|
||||
## 维护规则
|
||||
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
module lingo
|
||||
|
||||
go 1.23.0
|
||||
|
||||
require github.com/maxence-charriere/go-app/v10 v10.1.11
|
||||
|
||||
require github.com/google/uuid v1.6.0 // indirect
|
||||
@@ -0,0 +1,12 @@
|
||||
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/maxence-charriere/go-app/v10 v10.1.11 h1:4JUSlVQ8zVIeL3acFJEpuerP6FLoEQWg6CUTtHcz7Ro=
|
||||
github.com/maxence-charriere/go-app/v10 v10.1.11/go.mod h1:FqUW4on4nJewVfBnSkuxQd3fvtK2RdKS/z76OOUDAAY=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
@@ -0,0 +1,70 @@
|
||||
// 走遍美国 · 英语精听 PWA —— go-app 装配入口(T-001 最小可运行骨架)。
|
||||
//
|
||||
// 同一份代码两处运行:
|
||||
// - 编译成 wasm 在浏览器跑(app.RunWhenOnBrowser)
|
||||
// - 编译成普通二进制在服务器跑(http.Server + app.Handler)
|
||||
//
|
||||
// 构建:
|
||||
//
|
||||
// GOOS=js GOARCH=wasm go build -o web/app.wasm # 前端
|
||||
// go build -o bin/lingo # 后端
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
"net/http"
|
||||
|
||||
"github.com/maxence-charriere/go-app/v10/pkg/app"
|
||||
)
|
||||
|
||||
// home 是根页面组件,目前仅是最小占位骨架。
|
||||
type home struct {
|
||||
app.Compo
|
||||
}
|
||||
|
||||
// Render 渲染最小首页,验证 go-app 骨架可运行。
|
||||
func (h *home) Render() app.UI {
|
||||
return app.Div().
|
||||
Style("font-family", "system-ui, -apple-system, sans-serif").
|
||||
Style("max-width", "440px").
|
||||
Style("margin", "0 auto").
|
||||
Style("padding", "3rem 1.5rem").
|
||||
Body(
|
||||
app.H1().Text("走遍美国 · 精听"),
|
||||
app.P().
|
||||
Style("color", "#6b6b6b").
|
||||
Text("Family Album, U.S.A. — go-app 骨架已就绪(T-001)。"),
|
||||
)
|
||||
}
|
||||
|
||||
// init 注册前端路由。放在 init 而非 main,确保服务器(渲染 app shell)、
|
||||
// 浏览器 wasm 入口、以及测试三处都能看到路由——go-app 的 Handler 只为
|
||||
// 已注册的路由返回 app shell,未注册的路径会 404。
|
||||
func init() {
|
||||
app.Route("/", func() app.Composer { return &home{} })
|
||||
}
|
||||
|
||||
// newHandler 构造服务器端的 go-app Handler,提供 app shell、wasm 与静态资源
|
||||
// (默认从 web/ 目录读取 app.wasm)。抽出为函数以便测试。
|
||||
func newHandler() *app.Handler {
|
||||
return &app.Handler{
|
||||
Name: "走遍美国精听",
|
||||
ShortName: "精听",
|
||||
Description: "基于「走遍美国」的英语精听 PWA",
|
||||
Lang: "zh",
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
// 浏览器侧:启动 go-app(此调用在 wasm 之外无副作用)。
|
||||
app.RunWhenOnBrowser()
|
||||
|
||||
// 服务器侧。
|
||||
http.Handle("/", newHandler())
|
||||
|
||||
const addr = ":8000"
|
||||
log.Printf("serving on http://localhost%s", addr)
|
||||
if err := http.ListenAndServe(addr, nil); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// TestHandlerServesAppShell 在进程内验证 go-app Handler 能返回 app shell:
|
||||
// - 根路径返回 200;
|
||||
// - home 组件被服务端预渲染进 HTML(最小页面可见);
|
||||
// - 引导 wasm 的脚本(wasm_exec.js / app.js)已注入。
|
||||
//
|
||||
// 用 httptest 而非真实网络,避免依赖本机端口。
|
||||
func TestHandlerServesAppShell(t *testing.T) {
|
||||
h := newHandler()
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "/", nil)
|
||||
rec := httptest.NewRecorder()
|
||||
h.ServeHTTP(rec, req)
|
||||
|
||||
if rec.Code != http.StatusOK {
|
||||
t.Fatalf("根路径状态 = %d, 期望 200", rec.Code)
|
||||
}
|
||||
body := rec.Body.String()
|
||||
for _, want := range []string{
|
||||
`lang="zh"`, // 语言
|
||||
"走遍美国 · 精听", // home 组件预渲染内容
|
||||
"/wasm_exec.js", // wasm 运行时
|
||||
"/app.js", // 加载 app.wasm 的引导脚本
|
||||
} {
|
||||
if !strings.Contains(body, want) {
|
||||
t.Errorf("app shell 未包含 %q", want)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user