feat(portal-shell): add MSW mock layer with production bundle exclusion (P1-5)

MSW v2.7.0 fallback layer covering dashboard/users/exams/grades domains.
NEXT_PUBLIC_MSW=1 enables browser Service Worker + SSR route handler mock
responses without backend. Production build excludes all mock data via
Turbopack resolveAlias redirecting @/mocks to empty stub.

Acceptance: build bundle (client+server) verified clean of mock strings;
typecheck/lint/vitest (231 tests) all pass.
This commit is contained in:
SpecialX
2026-07-22 14:48:30 +08:00
parent da05c9107a
commit 9358372657
16 changed files with 1292 additions and 67 deletions

View File

@@ -1,5 +1,5 @@
/**
* Next.js 配置v2.1 M8 + v0.2 Tailwind v4 + Next 16 Turbopack + P1-4 next-intl
* Next.js 配置v2.1 M8 + v0.2 Tailwind v4 + Next 16 Turbopack + P1-4 next-intl + P1-5 MSW
*
* 角色:插件化仪表盘宿主(单 Next.js App Router · 单 Docker
* - output:standalone单容器部署
@@ -7,17 +7,32 @@
* - 反向代理:/api/v1/* → api-gateway :8080JWT 校验 + 注入 x-user-id/x-user-role
* - GraphQL 查询走 apollo-router :3000M8 验收点,由 Apollo Client 直连)
* - next-intl无 i18n 路由模式locale 由 cookie 决定ARCHITECTURE.md §3.4 V3-A6
* - MSW 兜底层P1-5NEXT_PUBLIC_MSW!=1 时把 @/mocks 与 @/mocks/graphql-data
* 重定向到空 stub确保生产 bundle 不含 mock 数据ARCHITECTURE.md §3.4 V3-A7
*
* Next 16 默认 Turbopack
* - turbopack.resolveExtensions 处理 ESM 包 .js 后缀导入源码 TS 文件的映射
* - webpack 配置保留作为 fallback--webpack flag 时生效)
*
* 关联portal-shell ARCHITECTURE.md §3.4 V3-A6、spec §2、project_rules §3.2
* 关联portal-shell ARCHITECTURE.md §3.4 V3-A6/V3-A7、spec §2、project_rules §3.2
*/
import path from "node:path";
import { fileURLToPath } from "node:url";
import createNextIntlPlugin from "next-intl/plugin";
const withNextIntl = createNextIntlPlugin("./src/i18n/request.ts");
const __dirname = path.dirname(fileURLToPath(import.meta.url));
// P1-5MSW 启用开关。生产构建NEXT_PUBLIC_MSW 非等于 "1")时把
// @/mocks 与 @/mocks/graphql-data 重定向到空 stub
// 确保 bundle 不含真实 mock 数据与 msw 包。
// 注意Turbopack resolveAlias 不支持 Windows 绝对路径("windows imports
// are not implemented yet"),故使用 @/mocks/empty 说明符(由 tsconfig
// paths 解析为 src/mocks/empty.ts而非绝对路径。
const MSW_ENABLED = process.env.NEXT_PUBLIC_MSW === "1";
const MOCKS_STUB_SPECIFIER = "@/mocks/empty";
/** @type {import('next').NextConfig} */
const nextConfig = {
reactStrictMode: true,
@@ -43,6 +58,17 @@ const nextConfig = {
".cjs",
".json",
],
// P1-5MSW 关闭时把 @/mocksMswProvider 静态 import
// @/mocks/graphql-dataroute.ts 静态 import重定向到空 stub。
// 使用 @/mocks/empty 说明符作为目标Turbopack 不支持 Windows 绝对路径)。
...(MSW_ENABLED
? {}
: {
resolveAlias: {
"@/mocks": MOCKS_STUB_SPECIFIER,
"@/mocks/graphql-data": MOCKS_STUB_SPECIFIER,
},
}),
},
// Webpack 配置fallback使用 --webpack flag 时生效)
webpack(config) {
@@ -51,6 +77,16 @@ const nextConfig = {
...config.resolve.extensionAlias,
".js": [".ts", ".tsx", ".js"],
};
// P1-5MSW 关闭时把 @/mocks 与 @/mocks/graphql-data 重定向到空 stub。
// webpack resolve.alias 支持 exact 匹配($ 后缀)与前缀匹配,
// 这里用 exact 匹配避免误伤其他 @/mocks/xxx 路径(实际只有这两个 import 点)。
if (!MSW_ENABLED) {
config.resolve.alias = {
...config.resolve.alias,
"@/mocks$": MOCKS_STUB_SPECIFIER,
"@/mocks/graphql-data$": MOCKS_STUB_SPECIFIER,
};
}
return config;
},
async rewrites() {