diff --git a/.github/workflows/README.md b/.github/workflows/README.md index d517e37..f8b3196 100644 --- a/.github/workflows/README.md +++ b/.github/workflows/README.md @@ -4,6 +4,8 @@ 在你复刻的仓库中,进入设置标签页(https://github.com/[username]/web-workshop/settings),点击左边栏的 Pages,在 Build and deployment 下方的 Source,选择 Github Actions。意思是通过自定义的 action 来部署静态 Github Pages(与之相对的是根据仓库中的 markdown 文件自动部署) +本仓库最终版本的 Github Pages 根路径用于展示教学文档,`frontend.yml` 会先构建文档站,再把前端构建产物复制到 `demo/` 子路径。因此官方演示页面位于 [https://eesast.github.io/web-workshop/demo/](https://eesast.github.io/web-workshop/demo/)。如果你在自己的复刻仓库中沿用当前 workflow,前端页面对应地址通常是 `https://[username].github.io/web-workshop/demo/`。 + ### 后端(Docker) 1. 注册 Dockers Hub 账号([Signup | Docker](https://app.docker.com/signup)),建议使用 Github 注册。如果使用其他方式注册,请将用户名与 Github 保持一致(大小写不敏感) diff --git a/.github/workflows/build-gh-pages.yml b/.github/workflows/build-gh-pages.yml new file mode 100644 index 0000000..3656836 --- /dev/null +++ b/.github/workflows/build-gh-pages.yml @@ -0,0 +1,55 @@ +name: build-gh-pages + +on: + pull_request: + branches: ["main"] + workflow_dispatch: + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Setup Pages + uses: actions/configure-pages@v5 + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: 20 + cache: yarn + cache-dependency-path: ./frontend/yarn.lock + + - name: Convert TOC syntax + run: node assets/js/convert-toc.js + + - name: Build documentation with Jekyll + uses: actions/jekyll-build-pages@v1 + with: + source: ./ + destination: ./_site + + - name: Fix documentation site permissions + run: sudo chown -R "$(id -u):$(id -g)" ./_site + + - name: Install dependencies + working-directory: frontend + run: yarn install --frozen-lockfile + + - name: Check grammar + working-directory: frontend + run: | + yarn typecheck + yarn lint + + - name: Build + working-directory: frontend + run: yarn build + + - name: Copy frontend demo into documentation site + working-directory: frontend + run: | + mkdir -p ../_site/demo + cp -R build/. ../_site/demo/ diff --git a/.github/workflows/frontend.yml b/.github/workflows/frontend.yml index ae54fbf..de23a6a 100644 --- a/.github/workflows/frontend.yml +++ b/.github/workflows/frontend.yml @@ -1,30 +1,21 @@ -# Simple workflow for deploying static content to GitHub Pages +# Build the documentation site and publish the frontend demo below /demo. name: frontend on: - # Runs on pushes targeting the default branch push: branches: ["main"] - # Allows you to run this workflow manually from the Actions tab workflow_dispatch: -# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages permissions: contents: read pages: write id-token: write -# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued. -# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete. concurrency: group: "pages" cancel-in-progress: false -defaults: - run: - working-directory: frontend - jobs: build: runs-on: ubuntu-latest @@ -33,6 +24,9 @@ jobs: - name: Checkout repository uses: actions/checkout@v4 + - name: Setup Pages + uses: actions/configure-pages@v5 + - name: Set up Node.js uses: actions/setup-node@v4 with: @@ -40,26 +34,42 @@ jobs: cache: yarn cache-dependency-path: ./frontend/yarn.lock + - name: Convert TOC syntax + run: node assets/js/convert-toc.js + + - name: Build documentation with Jekyll + uses: actions/jekyll-build-pages@v1 + with: + source: ./ + destination: ./_site + + - name: Fix documentation site permissions + run: sudo chown -R "$(id -u):$(id -g)" ./_site + - name: Install dependencies - run: | - yarn install --frozen-lockfile + working-directory: frontend + run: yarn install --frozen-lockfile - name: Check grammar + working-directory: frontend run: | yarn typecheck yarn lint - name: Build - run: | - yarn build + working-directory: frontend + run: yarn build - - name: Setup Pages - uses: actions/configure-pages@v5 + - name: Copy frontend demo into documentation site + working-directory: frontend + run: | + mkdir -p ../_site/demo + cp -R build/. ../_site/demo/ - name: Upload artifact uses: actions/upload-pages-artifact@v3 with: - path: "./frontend/build" + path: "./_site" deploy: environment: @@ -71,4 +81,4 @@ jobs: steps: - name: Deploy to GitHub Pages id: deployment - uses: actions/deploy-pages@v4 + uses: actions/deploy-pages@v5 diff --git a/.gitignore b/.gitignore index 5d8e194..b1fc481 100644 --- a/.gitignore +++ b/.gitignore @@ -5,5 +5,8 @@ node_modules build electron +_site +.jekyll-cache +.sass-cache .local.env diff --git a/404.md b/404.md new file mode 100644 index 0000000..0c0ea11 --- /dev/null +++ b/404.md @@ -0,0 +1,5 @@ +# Unavailable + +This resource is unavailable. + +## [Back to Home](./) diff --git a/README.md b/README.md index 871e6c4..83045b8 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ ​ 由于暑培的特性——时间短、覆盖面广、且每人负责一部分,每位主讲人都希望在自己的部分倾囊相授、达到“速成”的效果,因此我们倾向于选择知识密集型的教学方式,或多或少造成了“填鸭式”、“量子波动速读”的效果。一项技术(特别是编程领域)的知识点何其之多,即便主讲人们努力抓住主干脉络,也难免落入长篇累牍堆砌知识点的境地,不仅让听者产生厌烦,也不利于同学们实打实地掌握这门技术。 -​ 在反思这种教学方式的弊端过程中,我们打算在今年对暑培的形式做出新的尝试:贯穿始终的学习型工程。在不影响核心知识点的讲解前提下,主讲者们通过演示一个实际工程的搭建过程,来提高同学们对暑培内容的掌握程度。 +​ 在反思这种教学方式的弊端过程中,我们从去年开始对暑培的形式做出新的尝试:贯穿始终的学习型工程。在不影响核心知识点的讲解前提下,主讲者们通过演示一个实际工程的搭建过程,来提高同学们对暑培内容的掌握程度。 ​ 这个做法有三大好处: @@ -14,6 +14,19 @@ ​ 这个学习型工程的主题是**一个趣味会议软件**,希望实现的基本功能有:用户创建和登录、会议创建和加入、会议中倒计时、随机点名等趣味功能,同学们可以把他理解为不含直播的“雨课堂”或“腾讯会议”,也可以理解成一款桌游辅助工具。 +**项目文档主页:**[https://eesast.github.io/web-workshop/](https://eesast.github.io/web-workshop/) + +**项目演示页面:**[https://eesast.github.io/web-workshop/demo/](https://eesast.github.io/web-workshop/demo/) + +### 项目目录 + +- [HTML & CSS](./tutorials/01-HTML&CSS.md) +- [JS & TS](./tutorials/02-JS&TS.md) +- [DataBase (SQL & GraphQL)](./tutorials/03-Database.md) +- [Backend (NodeJS & Express)](./tutorials/04-Backend.md) +- [Frontend (React & Webpack)](./tutorials/05-Frontend.md) +- [Deployment (CI/CD & Server)](./tutorials/06-Deployment.md) + ​ 以下是各讲对应的演示内容及其在整个工程中的作用: 1. `HTML & CSS` @@ -40,6 +53,23 @@ 在前 5 节中,我们已经在本地完成了网站的全部开发工作,但如何让世界上所有人都能 24 小时访问你的网站呢?在本节,我们将运用 Github CI/CD 来构建前端和后端的 Docker 镜像,使用 Github Pages 来托管前端页面,并尝试自己购买一个云服务器来提供网站的后端和数据库服务。 + 注:本仓库的 Github Pages 根路径用于展示教学文档,最终前端演示页面部署在 [`/demo/`](https://eesast.github.io/web-workshop/demo/) 子路径下;Deployment 一节中介绍的前端构建和 Pages 托管流程仍然适用。 + +### 关于 Vibe Coding +随着 Coding Agent 的迅速发展,截止今日(2026.7),使用先进的大模型已经能轻松完成本项目的大部分内容。要求同学们手动完成作业既浪费过多时间,又难以进行监管。暑培允许使用AI辅助完成作业,但需遵循如下的几条限制: +- 应先在AI协助下理解项目整体框架,并挑选你觉得重要部分的代码进行仔细阅读 +- 避免用简短的 prompt 向 AI 许愿。你应该编写足够详细的 prompt,明确你想要的功能和实现方式(和模型进行多轮交流来明确需求,完善 prompt,保证你对项目的细节有充分的理解) +- AI 生成的所有代码都应该经过人工 review,这对你理解所学内容至关重要 +- **针对 Web Workshop,推荐在 N 选 1 的任务中选一个手动完成,其余的交给 Agent** + +我们相信同学们参加暑培是为了精进开发能力,而不是为了完成而完成。经过暑培的学习,你将具备一名 **Developer** 应有的**品味(taste)**,指引你在软件开发的广阔世界中不断前行。 + +> 在AI时代,大部分简单的需求都能够通过AI在短时间内完成。但现实中的软件系统往往面临着复杂的业务逻辑、多变的需求,以及来自团队协作和长期维护的挑战。 +> +> 一个常见的例子是,AI快速生成了一个功能模块的代码,但带有许多不必要的条件检查和异常处理逻辑,使得代码变得冗长且难以理解(过度的防御性编程)。如果不对这种情况加以审查和优化,时间长了,整个系统便会成为“屎山”。 +> +> 面对复杂的业务需求,如何简洁、高效地实现功能,如何在长期维护中保持代码的可读性和可扩展性,这都需要开发者具备良好的代码品味(taste)。 + ### 使用方法 ##### 复刻仓库(Fork Repo) @@ -185,3 +215,4 @@ git push origin "01-HTML&CSS" 若需修改,按 PR 下方的评论提示进行更改,然后重复 步骤 2 → 步骤 3 提交更新。 + diff --git a/_config.yml b/_config.yml new file mode 100644 index 0000000..9bb5b2a --- /dev/null +++ b/_config.yml @@ -0,0 +1,22 @@ +title: 科协暑培(网站部分)学习型工程 +description: EESAST Web Workshop +theme: jekyll-theme-hacker +plugins: + - jekyll-optional-front-matter + - jekyll-readme-index + - jekyll-default-layout + - jekyll-relative-links + - jekyll-seo-tag +relative_links: + enabled: true + collections: true +include: + - README.md + - tutorials + - database/design.md + - .github/workflows/README.md +exclude: + - frontend + - backend/node_modules + - database/node_modules + - _site diff --git a/_layouts/default.html b/_layouts/default.html new file mode 100644 index 0000000..7c4d09e --- /dev/null +++ b/_layouts/default.html @@ -0,0 +1,153 @@ + + + + + + + + + + + + {% include head-custom.html %} + + {% seo %} + + + + +
+
+
+ +

{{ site.title | default: site.github.repository_name }}

+
+

{{ site.description | default: site.github.project_tagline }}

+ +
+ {% if site.show_downloads %} + Download as .zip + Download as .tar.gz + {% endif %} + Demo + View on + GitHub + +
+
+
+ 返回首页 + +
+
+
+ +
+
+ {{ content }} +
+
+ + + + + + + + diff --git a/assets/css/custom.css b/assets/css/custom.css new file mode 100644 index 0000000..c6987ed --- /dev/null +++ b/assets/css/custom.css @@ -0,0 +1,131 @@ +body, +h1, +h2, +h3, +h4, +h5, +h6 { + font-family: + Monaco, "Bitstream Vera Sans Mono", "Lucida Console", Terminal; +} + +code { + font-family: "Source Code Pro", Consolas, monospace; +} + +.head_wrapper { + display: flex; + justify-content: space-between; + flex-direction: row; +} + +.extra-buttons { + display: flex; + align-items: center; + margin-right: 10px; +} + +.btn-extra { + display: inline-block; + margin: 5px; + white-space: nowrap; +} + +@media screen and (max-width: 768px) { + .head_wrapper { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + } + + .container { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + } +} + +.markdown-alert { + padding: 0.5rem 1rem; + margin: 1rem 0; + border-left: 0.25em solid; + background-color: transparent; +} + +.markdown-alert > :first-child { + margin-top: 0; +} + +.markdown-alert > :last-child { + margin-bottom: 0; +} + +.markdown-alert-title { + display: flex; + align-items: center; + gap: 0.35rem; + font-weight: 600; + margin-bottom: 0.5rem; +} + +.markdown-alert-icon { + fill: currentColor; + flex-shrink: 0; +} + +.markdown-alert-note { + border-left-color: #0969da; +} + +.markdown-alert-note .markdown-alert-title { + color: #0969da; +} + +.markdown-alert-tip { + border-left-color: #1a7f37; +} + +.markdown-alert-tip .markdown-alert-title { + color: #1a7f37; +} + +.markdown-alert-important { + border-left-color: #8250df; +} + +.markdown-alert-important .markdown-alert-title { + color: #8250df; +} + +.markdown-alert-warning { + border-left-color: #9a6700; +} + +.markdown-alert-warning .markdown-alert-title { + color: #9a6700; +} + +.markdown-alert-caution { + border-left-color: #cf222e; +} + +.markdown-alert-caution .markdown-alert-title { + color: #cf222e; +} + +.toc { + padding: 1rem; + margin: 1rem 0 2rem; + border-left: 0.25rem solid #30363d; + background: rgba(110, 118, 129, 0.1); +} + +.toc ul { + margin-bottom: 0; +} + +.toc a { + text-decoration: none; +} diff --git a/assets/js/convert-toc.js b/assets/js/convert-toc.js new file mode 100644 index 0000000..1f04edd --- /dev/null +++ b/assets/js/convert-toc.js @@ -0,0 +1,37 @@ +const fs = require("fs"); +const path = require("path"); + +const root = "."; +const ignoredDirs = new Set([ + ".git", + "_site", + "node_modules", + "build", + "electron", +]); + +function walk(dir) { + for (const item of fs.readdirSync(dir)) { + if (ignoredDirs.has(item)) continue; + + const full = path.join(dir, item); + const stat = fs.statSync(full); + + if (stat.isDirectory()) { + walk(full); + } else if (full.endsWith(".md")) { + convertFile(full); + } + } +} + +function convertFile(file) { + let text = fs.readFileSync(file, "utf8"); + text = text.replace( + /^\[TOC\]\s*$/gm, + '
\n* TOC\n{:toc}\n
', + ); + fs.writeFileSync(file, text, "utf8"); +} + +walk(root); diff --git a/assets/js/prepare.js b/assets/js/prepare.js new file mode 100644 index 0000000..feba56f --- /dev/null +++ b/assets/js/prepare.js @@ -0,0 +1,37 @@ +const fullUrl = window.location.href; +const currentUrl = window.location.origin + window.location.pathname; +const paths = currentUrl.split("/"); +const isMainPage = currentUrl.replace(/\/$/, "") === baseUrl.replace(/\/$/, ""); + +document.documentElement.lang = "zh-CN"; + +const getDemoUrl = () => { + return `${baseUrl.replace(/\/$/, "")}/demo/`; +}; + +const getViewOnGitHubUrl = () => { + if (!repoUrl || isMainPage) { + return `${repoUrl}/`; + } + + let target = `${repoUrl}/blob/${repoBranch}/${repoPath.replace(/\/$/, "")}`; + if (!target.endsWith("/")) { + target += "/"; + } + + target += currentUrl.slice(baseUrl.length + 1); + if (target.endsWith("/")) { + target += "README.md"; + } else if (target.endsWith(".html")) { + target = target.replace(/\.html$/, ".md"); + } + + return target; +}; + +const getReturnToHomeUrl = () => { + return `${baseUrl.replace(/\/$/, "")}/`; +}; + +void fullUrl; +void paths; diff --git a/tutorials/06-Deployment.md b/tutorials/06-Deployment.md index 99bd048..2f144e9 100644 --- a/tutorials/06-Deployment.md +++ b/tutorials/06-Deployment.md @@ -2,6 +2,8 @@ 在前 5 节中,我们已经在本地完成了网站的全部开发工作,但如何让世界上所有人都能 24 小时访问你的网站呢?在本节,我们将运用 Github CI/CD 来构建前端和后端的 Docker 镜像,使用 Github Pages 来托管前端页面,并尝试自己购买一个云服务器来提供网站的后端和数据库服务。 +本仓库的最终 Github Pages 根路径用于展示教学文档,前端演示页面部署在 [https://eesast.github.io/web-workshop/demo/](https://eesast.github.io/web-workshop/demo/);这不会改变本节要学习的 Github Pages 托管前端页面的基本流程。 + ### 已实现的功能 | 哈希值前 7 位 | 提交信息 | 对应知识点 | 实现效果 |