# Node.js 前端 Workflow 模板 适用于 Node.js 前端项目的 CI/CD workflow,支持 React、Vue、Vite、Next.js 等框架。 ## 适用场景 - React / Vue / Angular 前端项目 - Vite / Webpack 构建的 SPA - Next.js / Nuxt.js SSR 应用 - 需要构建 Docker 镜像的前端项目 ## 环境要求 | 依赖 | Runner 要求 | |------|------------| | Node.js 20+ | Runner 主机已安装 | | pnpm / npm | Runner 主机已安装或动态安装 | | Docker | Runner 主机已安装 | ## Workflow 骨架模板 ```yaml name: Web Frontend - Build & Publish on: push: paths: - 'web/**' # 修改为实际目录 - '.gitea/workflows/web.yml' tags: - 'web-*' # 修改为实际 tag 前缀 concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true env: SERVICE_PREFIX: web # 修改为实际服务名 SERVICE_DIR: web # 修改为实际目录名 NPM_REGISTRY: https://registry.npmmirror.com jobs: build-and-publish: name: Build & Publish runs-on: darwin-arm64 permissions: contents: read packages: write outputs: git_tag: ${{ steps.vars.outputs.git_tag }} steps: - name: Checkout uses: actions/checkout@v4 - name: Verify Node.js environment run: | node --version npm --version - name: Set up pnpm run: | npm config set registry ${{ env.NPM_REGISTRY }} npm install -g pnpm@latest-10 - name: Get pnpm-hashfiles id: hash-pnpm working-directory: ${{ env.SERVICE_DIR }} run: | HASH=$(sha256sum package.json pnpm-lock.yaml 2>/dev/null | sha256sum | awk '{print $1}' | head -c 16) echo "hash=${HASH}" >> $GITHUB_OUTPUT - name: Cache pnpm modules uses: https://github.com/actions/cache@v3 with: path: | ~/.local/share/pnpm/store ~/.pnpm-store ${{ env.SERVICE_DIR }}/node_modules key: pnpm-${{ env.SERVICE_PREFIX }}-${{ steps.hash-pnpm.outputs.hash }} restore-keys: pnpm-${{ env.SERVICE_PREFIX }}- - name: Set variables id: vars run: | git_tag=$(git describe --tags --abbrev=0 --always) registry=$(echo ${{ github.server_url }} | cut -d '/' -f 3) image_repo="${{ github.event.repository.name }}" { echo "git_tag=${git_tag}" echo "registry=${registry}" echo "image_repo=${image_repo}" echo "latest_tag=${{ env.SERVICE_PREFIX }}-latest" } >> $GITHUB_ENV echo "git_tag=${git_tag}" >> $GITHUB_OUTPUT - name: Install & Build working-directory: ${{ env.SERVICE_DIR }} env: NODE_ENV: production # ============================================ # 用户自定义环境变量(按项目需求修改) # ============================================ # VITE_API_URL: https://api.example.com # VITE_APP_TITLE: My App # NEXT_PUBLIC_API_URL: https://api.example.com run: | # 安装依赖 pnpm install --frozen-lockfile # ============================================ # 用户自定义构建步骤(按项目需求修改) # ============================================ # 类型检查(按需启用) # pnpm run typecheck # pnpm run lint # 构建(必须) pnpm run build - name: Upload artifact uses: actions/upload-artifact@v3 with: name: ${{ env.SERVICE_PREFIX }}-dist path: ${{ env.SERVICE_DIR }}/dist - name: Docker - Login uses: docker/login-action@v3 with: registry: ${{ env.registry }} username: ${{ vars.REGISTRY_USERNAME }} password: ${{ secrets.REGISTRY_PASSWORD }} - name: Docker - Setup Buildx uses: docker/setup-buildx-action@v3 - name: Docker - Build & Push uses: docker/build-push-action@v6 with: context: ./${{ env.SERVICE_DIR }} file: ./${{ env.SERVICE_DIR }}/Dockerfile push: true platforms: linux/amd64 tags: | ${{ env.registry }}/${{ github.repository_owner }}/${{ env.image_repo }}:${{ env.latest_tag }} ${{ env.registry }}/${{ github.repository_owner }}/${{ env.image_repo }}:${{ env.git_tag }} cache-from: type=registry,ref=${{ env.registry }}/${{ github.repository_owner }}/${{ env.image_repo }}:buildcache cache-to: type=registry,ref=${{ env.registry }}/${{ github.repository_owner }}/${{ env.image_repo }}:buildcache,mode=max - name: Notify if: always() continue-on-error: true env: WEBHOOK_URL: ${{ vars.WEBHOOK_URL }} run: | status="${{ job.status }}" [ "$status" = "success" ] && status_text="Build Success" || status_text="Build Failed" curl -s -H "Content-Type: application/json" -X POST -d \ "{\"msg_type\":\"text\",\"content\":{\"text\":\"${{ env.image_repo }}-${{ env.SERVICE_PREFIX }} ${status_text}\"}}" \ "$WEBHOOK_URL" || true release: name: Create Release runs-on: darwin-arm64 needs: build-and-publish if: startsWith(github.ref, 'refs/tags/') steps: - name: Checkout uses: actions/checkout@v4 with: fetch-depth: 0 - name: Download artifact uses: actions/download-artifact@v3 with: name: ${{ env.SERVICE_PREFIX }}-dist path: dist - name: Create Release env: GITEA_TOKEN: ${{ secrets.RELEASE_TOKEN }} run: | git_tag=$(git describe --tags --abbrev=0) api_url="${{ github.server_url }}/api/v1" repo="${{ github.repository }}" # 打包构建产物 cd dist zip -r "${{ env.SERVICE_PREFIX }}-dist.zip" . sha256sum "${{ env.SERVICE_PREFIX }}-dist.zip" > "${{ env.SERVICE_PREFIX }}-dist.zip.sha256" cd .. # 创建 Release release_id=$(curl -s -X POST \ -H "Authorization: token $GITEA_TOKEN" \ -H "Content-Type: application/json" \ -d "{\"tag_name\":\"${git_tag}\",\"name\":\"Release ${git_tag}\"}" \ "${api_url}/repos/${repo}/releases" | jq -r '.id') # 上传附件 curl -s -X POST \ -H "Authorization: token $GITEA_TOKEN" \ -F "attachment=@dist/${{ env.SERVICE_PREFIX }}-dist.zip" \ "${api_url}/repos/${repo}/releases/${release_id}/assets" ``` --- ## Dockerfile 模板 ### 静态文件部署(Nginx) ```dockerfile FROM nginx:alpine # 复制构建产物 COPY dist /usr/share/nginx/html # 复制 nginx 配置(可选) # COPY nginx.conf /etc/nginx/conf.d/default.conf # 健康检查 HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \ CMD wget --no-verbose --tries=1 --spider http://localhost/ || exit 1 EXPOSE 80 CMD ["nginx", "-g", "daemon off;"] ``` ### SPA 路由配置(nginx.conf) ```nginx server { listen 80; server_name localhost; root /usr/share/nginx/html; index index.html; # SPA 路由支持 location / { try_files $uri $uri/ /index.html; } # 静态资源缓存 location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2)$ { expires 1y; add_header Cache-Control "public, immutable"; } # 禁止缓存 HTML location ~* \.html$ { expires -1; add_header Cache-Control "no-store, no-cache, must-revalidate"; } } ``` --- ## 缓存配置 ### pnpm 缓存路径 ```yaml path: | ~/.local/share/pnpm/store # pnpm 全局存储 ~/.pnpm-store # 备选路径 web/node_modules # 项目依赖 ``` ### npm 缓存路径 ```yaml path: | ~/.npm web/node_modules ``` ### Key 计算 ```bash # pnpm HASH=$(sha256sum package.json pnpm-lock.yaml | sha256sum | awk '{print $1}' | head -c 16) # npm HASH=$(sha256sum package.json package-lock.json | sha256sum | awk '{print $1}' | head -c 16) ``` --- ## 包管理器选择 ### pnpm(推荐) ```yaml - name: Set up pnpm run: | npm install -g pnpm@latest-10 - name: Install dependencies run: pnpm install --frozen-lockfile ``` ### npm ```yaml - name: Install dependencies run: npm ci ``` ### yarn ```yaml - name: Set up yarn run: npm install -g yarn - name: Install dependencies run: yarn install --frozen-lockfile ``` --- ## 环境变量注入 ### Vite ```yaml env: VITE_API_URL: https://api.example.com VITE_APP_TITLE: My App ``` ### Next.js ```yaml env: NEXT_PUBLIC_API_URL: https://api.example.com ``` ### Vue CLI / CRA ```yaml env: VUE_APP_API_URL: https://api.example.com REACT_APP_API_URL: https://api.example.com ``` --- ## Secrets 配置 | Secret | 用途 | |--------|------| | `REGISTRY_PASSWORD` | Docker Registry 密码 | | `RELEASE_TOKEN` | Gitea API 令牌(创建 Release) | --- ## 常见构建命令 根据项目使用的框架,在 Build 步骤中添加相应的命令: | 框架 | 构建命令 | |------|---------| | Vite | `pnpm run build` | | Next.js | `pnpm run build` | | Nuxt.js | `pnpm run generate` 或 `pnpm run build` | | Vue CLI | `pnpm run build` | | CRA | `npm run build` | --- ## 使用步骤 1. 复制上方 workflow 模板到 `.gitea/workflows/web.yml` 2. 修改 `SERVICE_PREFIX` 和 `SERVICE_DIR` 为实际值 3. 修改 `paths` 和 `tags` 触发条件 4. 根据项目需求配置环境变量 5. 创建 `Dockerfile` 文件 6. 配置 Secrets 7. 推送代码触发 workflow --- ## 版本发布 ```bash # 创建并推送 tag git tag web-1.0.0 git push origin web-1.0.0 ```