perf(client): restore bundle guardrails
This commit is contained in:
@@ -0,0 +1,33 @@
|
|||||||
|
name: CI
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- dev
|
||||||
|
- main
|
||||||
|
- test
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
verify:
|
||||||
|
name: Quality and Bundle Budgets
|
||||||
|
runs-on: gitea-label
|
||||||
|
container:
|
||||||
|
image: cozsweet-act-runner-node24-docker:latest
|
||||||
|
env:
|
||||||
|
NEXT_TELEMETRY_DISABLED: "1"
|
||||||
|
SENTRY_UPLOAD_SOURCEMAPS: "0"
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: pnpm install --frozen-lockfile
|
||||||
|
|
||||||
|
- name: Run quality checks
|
||||||
|
run: pnpm quality
|
||||||
|
|
||||||
|
- name: Enforce bundle budgets
|
||||||
|
run: pnpm perf:bundle:check
|
||||||
@@ -18,16 +18,10 @@ jobs:
|
|||||||
SENTRY_UPLOAD_SOURCEMAPS: "0"
|
SENTRY_UPLOAD_SOURCEMAPS: "0"
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Skip quality gate for test branch
|
|
||||||
if: ${{ github.ref == 'refs/heads/test' }}
|
|
||||||
run: echo "Quality gate is disabled for the test branch."
|
|
||||||
|
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
if: ${{ github.ref != 'refs/heads/test' }}
|
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
if: ${{ github.ref != 'refs/heads/test' }}
|
|
||||||
run: pnpm install --frozen-lockfile
|
run: pnpm install --frozen-lockfile
|
||||||
|
|
||||||
- name: Run quality checks
|
- name: Run quality checks
|
||||||
@@ -35,7 +29,6 @@ jobs:
|
|||||||
run: pnpm quality
|
run: pnpm quality
|
||||||
|
|
||||||
- name: Enforce bundle budgets
|
- name: Enforce bundle budgets
|
||||||
if: ${{ github.ref != 'refs/heads/test' }}
|
|
||||||
run: pnpm perf:bundle:check
|
run: pnpm perf:bundle:check
|
||||||
|
|
||||||
publish:
|
publish:
|
||||||
|
|||||||
@@ -8,14 +8,14 @@
|
|||||||
|
|
||||||
| Workflow | 触发时机 | 职责 |
|
| Workflow | 触发时机 | 职责 |
|
||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
| `.gitea/workflows/ci.yml` | `dev`、`main`、`test` push / PR | 安装依赖、Lint、单元测试、Next.js 构建 |
|
| `.gitea/workflows/ci.yml` | `dev`、`main`、`test` push / PR | 安装依赖、执行完整质量检查并校验 bundle 预算 |
|
||||||
| `.gitea/workflows/docker-image.yml` | `main`、`test` push / 手动触发 | 构建 Docker 镜像、推送到镜像仓库,并通过 SSH 部署 |
|
| `.gitea/workflows/docker-image.yml` | `main`、`test` push / 手动触发 | 构建 Docker 镜像、推送到镜像仓库,并通过 SSH 部署 |
|
||||||
|
|
||||||
这样可以避免所有开发分支都发布镜像,并确保生产镜像通过发布门禁。
|
这样可以避免所有开发分支都发布镜像,并确保生产镜像通过发布门禁。
|
||||||
|
|
||||||
`main` 分支发布前会执行 Docker workflow 内的质量和 bundle 门禁。`test`
|
`main` 分支发布前会执行 Docker workflow 内的完整质量和 bundle 门禁。`test`
|
||||||
分支为快速部署测试环境,会跳过该门禁;独立的 `ci.yml` 仍会照常执行测试分支检查,
|
分支为快速部署测试环境,会跳过完整质量检查,但仍必须通过 bundle 预算;独立的
|
||||||
但不会阻塞测试镜像构建与部署。
|
`ci.yml` 会同时执行完整检查并提供测试结果。
|
||||||
|
|
||||||
## 必需 Secrets
|
## 必需 Secrets
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# Performance baseline
|
# Performance baseline
|
||||||
|
|
||||||
Baseline date: 2026-07-15
|
Baseline date: 2026-07-16
|
||||||
|
|
||||||
Next.js: 16.2.7 (Turbopack)
|
Next.js: 16.2.7 (Turbopack)
|
||||||
|
|
||||||
@@ -30,10 +30,10 @@ Compressed sizes below are Turbopack module estimates. `Eager` is the route's sy
|
|||||||
|
|
||||||
| Route | Direct-import baseline | Current eager baseline | Change | Current async baseline |
|
| Route | Direct-import baseline | Current eager baseline | Change | Current async baseline |
|
||||||
| --- | ---: | ---: | ---: | ---: |
|
| --- | ---: | ---: | ---: | ---: |
|
||||||
| `/splash` | 646.2 KiB | 599.7 KiB | -46.5 KiB | 49.2 KiB |
|
| `/splash` | 646.2 KiB | 599.9 KiB | -46.3 KiB | 49.2 KiB |
|
||||||
| `/chat` | 669.8 KiB | 637.8 KiB | -32.0 KiB | 49.6 KiB |
|
| `/chat` | 669.8 KiB | 640.7 KiB | -29.1 KiB | 49.6 KiB |
|
||||||
| `/subscription` | 658.4 KiB | 616.9 KiB | -41.5 KiB | 20.6 KiB |
|
| `/subscription` | 658.4 KiB | 617.3 KiB | -41.1 KiB | 20.6 KiB |
|
||||||
| `/tip` | 651.6 KiB | 610.9 KiB | -40.7 KiB | 20.6 KiB |
|
| `/tip` | 651.6 KiB | 611.2 KiB | -40.4 KiB | 20.6 KiB |
|
||||||
|
|
||||||
The earlier direct-import pass replaced root `@/utils` and `@/data/storage`
|
The earlier direct-import pass replaced root `@/utils` and `@/data/storage`
|
||||||
barrel imports with symbol-level module paths. The current Client-boundary pass
|
barrel imports with symbol-level module paths. The current Client-boundary pass
|
||||||
@@ -42,6 +42,10 @@ provider imports, and loads the Dexie-backed chat repository through a dynamic
|
|||||||
import. UA Parser remains an intentional eager dependency because the app still
|
import. UA Parser remains an intentional eager dependency because the app still
|
||||||
requires its general browser, OS, and device detection behavior.
|
requires its general browser, OS, and device detection behavior.
|
||||||
|
|
||||||
|
The 2026-07-16 verification also prevents runtime imports from the root
|
||||||
|
repository and storage barrels. This keeps unrelated repositories from turning
|
||||||
|
the intentionally async Dexie path eager when a new feature is added.
|
||||||
|
|
||||||
Module loading baseline:
|
Module loading baseline:
|
||||||
|
|
||||||
| Module | Before | Baseline |
|
| Module | Before | Baseline |
|
||||||
|
|||||||
@@ -5,6 +5,28 @@ import nextTs from "eslint-config-next/typescript";
|
|||||||
const eslintConfig = defineConfig([
|
const eslintConfig = defineConfig([
|
||||||
...nextVitals,
|
...nextVitals,
|
||||||
...nextTs,
|
...nextTs,
|
||||||
|
{
|
||||||
|
files: ["src/**/*.{ts,tsx}"],
|
||||||
|
rules: {
|
||||||
|
"no-restricted-imports": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
paths: [
|
||||||
|
{
|
||||||
|
name: "@/data/repositories",
|
||||||
|
message:
|
||||||
|
"Import the concrete repository module so unrelated browser storage stays out of the client graph.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "@/data/storage",
|
||||||
|
message:
|
||||||
|
"Import the concrete storage module so unrelated persistence code stays out of the client graph.",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
files: ["src/app/**/*.{ts,tsx}"],
|
files: ["src/app/**/*.{ts,tsx}"],
|
||||||
ignores: ["src/app/api/**", "src/app/**/route.ts"],
|
ignores: ["src/app/api/**", "src/app/**/route.ts"],
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import type {
|
|||||||
FeedbackSubmitResponse,
|
FeedbackSubmitResponse,
|
||||||
SubmitFeedbackInput,
|
SubmitFeedbackInput,
|
||||||
} from "@/data/dto/feedback";
|
} from "@/data/dto/feedback";
|
||||||
import { getFeedbackRepository } from "@/data/repositories";
|
import { getFeedbackRepository } from "@/data/repositories/feedback_repository";
|
||||||
import type { Result } from "@/utils/result";
|
import type { Result } from "@/utils/result";
|
||||||
|
|
||||||
export function submitFeedback(
|
export function submitFeedback(
|
||||||
|
|||||||
Reference in New Issue
Block a user