- Dockerfile 切换为 Next.js output:standalone 模式 - 解决 pnpm symlink 在 runtime 下无法解析 react/jsx-runtime 的问题 - 修复 tsconfig.base.json 未 COPY 到 builder 阶段的问题 - next.config.js 添加 output:"standalone" 配置 - Docker Desktop 验证通过: - 镜像构建成功 (1.32GB) - 容器启动 Ready in 69ms - /api/health 健康检查返回 200 - 27 静态路由 + 11 动态路由全部 HTTP 200 - 新增 nextstep.md: - 下游依赖清单(teacher-bff 61 GraphQL operations + iam + push-gateway) - 本模块待补工作(单元测试/设计令牌硬化/i18n/ui-components 扩展/性能/A11y) - Docker 部署注意事项 + 路由清单 + 已知问题 - workline.md §5.6 记录 Docker 验证结果
145 lines
4.9 KiB
JavaScript
145 lines
4.9 KiB
JavaScript
/**
|
||
* teacher-portal Next.js 配置
|
||
*
|
||
* 角色:MF Shell 宿主(ARB-002 §2.3)
|
||
* - exposes: AppShell + GraphQLProvider + 共享 hooks + 共享 UI 组件
|
||
* - shared: react/react-dom/urql/graphql/@edu/* 设为 singleton
|
||
* - remotes: P2 为空(NEXT_PUBLIC_MF_ENABLED=false),P3+ 逐步接入
|
||
*
|
||
* 维护者:ai13(teacher-portal)
|
||
* 关联:ARB-002 §2.2 暴露清单、03-long-term-architecture.md §1.3 绞杀者模式
|
||
*/
|
||
|
||
/** @type {import('next').NextConfig} */
|
||
|
||
// NextFederationPlugin 动态 require,避免构建时硬依赖(MF 2.0 在 Next 14 App Router 下按需加载)
|
||
let NextFederationPlugin;
|
||
try {
|
||
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
||
NextFederationPlugin = require("@module-federation/nextjs-mf").NextFederationPlugin;
|
||
} catch {
|
||
// 依赖未安装时跳过 MF 配置(如纯 typecheck 环境)
|
||
NextFederationPlugin = null;
|
||
}
|
||
|
||
/**
|
||
* 构建 Remote 列表(P2 为空,P3+ 按 NEXT_PUBLIC_MF_ENABLED 启用)
|
||
*
|
||
* ARB-002 §2.3:P2 Remote 数量 = 0,NEXT_PUBLIC_MF_ENABLED=false 默认关
|
||
* P3 首个 Remote:student-portal(总裁 §1.3 step 4)
|
||
*/
|
||
function buildRemotes(isServer) {
|
||
if (process.env.NEXT_PUBLIC_MF_ENABLED !== "true") return {};
|
||
const chunkPath = isServer ? "ssr" : "chunks";
|
||
return {
|
||
student: `student_app@http://localhost:4001/_next/static/${chunkPath}/remoteEntry.js`,
|
||
parent: `parent_app@http://localhost:4002/_next/static/${chunkPath}/remoteEntry.js`,
|
||
admin: `admin_app@http://localhost:4003/_next/static/${chunkPath}/remoteEntry.js`,
|
||
};
|
||
}
|
||
|
||
/**
|
||
* ARB-002 §2.2 MF Shell 暴露清单
|
||
*/
|
||
function buildExposes() {
|
||
return {
|
||
// 1. AppShell(布局 + 导航 + 认证守卫)
|
||
"./AppShell": "./src/components/AppShell.tsx",
|
||
// 2. GraphQLProvider(urql client 单例,总裁 §2.17 方案 A)
|
||
"./GraphQLProvider": "./src/app/providers.tsx",
|
||
// 3. 共享 hooks
|
||
"./useAuth": "./packages/hooks/src/use-auth.ts",
|
||
"./usePermission": "./packages/hooks/src/use-permission.ts",
|
||
"./useGraphQLClient": "./packages/hooks/src/use-graphql-client.ts",
|
||
// 4. 共享 UI 组件
|
||
"./ErrorBoundary": "./packages/ui-components/src/error-boundary.tsx",
|
||
"./Loading": "./packages/ui-components/src/loading.tsx",
|
||
"./Empty": "./packages/ui-components/src/empty.tsx",
|
||
"./RequirePermission": "./packages/ui-components/src/require-permission.tsx",
|
||
};
|
||
}
|
||
|
||
/**
|
||
* ARB-002 §2.2 MF shared singleton 配置
|
||
* 避免 Remote 多实例 + 缓存不一致
|
||
*/
|
||
function buildShared() {
|
||
return {
|
||
react: { singleton: true, requiredVersion: "^18.3.0" },
|
||
"react-dom": { singleton: true, requiredVersion: "^18.3.0" },
|
||
urql: { singleton: true, requiredVersion: "^2.2.0" },
|
||
graphql: { singleton: true, requiredVersion: "^16.8.0" },
|
||
"@edu/ui-tokens": { singleton: true },
|
||
"@edu/ui-components": { singleton: true },
|
||
"@edu/hooks": { singleton: true },
|
||
};
|
||
}
|
||
|
||
const nextConfig = {
|
||
reactStrictMode: true,
|
||
|
||
// standalone 模式:Next.js 自动打包所有依赖到 .next/standalone
|
||
// 解决 pnpm symlink 在 Docker runtime 下无法解析 react/jsx-runtime 的问题
|
||
output: "standalone",
|
||
|
||
// ESM 模式:transpilePackages 对齐 NestJS ESM 规范
|
||
// @edu/hooks 等包使用 .js 后缀导入源码,需 transpile + extensionAlias
|
||
transpilePackages: [
|
||
"@edu/ui-components",
|
||
"@edu/ui-tokens",
|
||
"@edu/hooks",
|
||
"@edu/contracts",
|
||
"@edu/shared-ts",
|
||
],
|
||
|
||
// ARB-002:MF Shell 配置(exposes + shared,P2 无 remotes)
|
||
// 仅当 NEXT_PUBLIC_MF_ENABLED=true 时实例化 NextFederationPlugin
|
||
// (nextjs-mf 不支持 App Directory,P2 阶段 MF 未启用时跳过避免报错)
|
||
webpack(config, { isServer }) {
|
||
// ESM 包使用 .js 后缀导入源码(TS 文件),需映射 .js → .ts
|
||
config.resolve = config.resolve || {};
|
||
config.resolve.extensionAlias = {
|
||
...config.resolve.extensionAlias,
|
||
".js": [".ts", ".tsx", ".js"],
|
||
};
|
||
|
||
if (
|
||
NextFederationPlugin &&
|
||
process.env.NEXT_PUBLIC_MF_ENABLED === "true"
|
||
) {
|
||
config.plugins.push(
|
||
new NextFederationPlugin({
|
||
name: "teacher_app",
|
||
filename: "static/chunks/remoteEntry.js",
|
||
remotes: buildRemotes(isServer),
|
||
exposes: buildExposes(),
|
||
shared: buildShared(),
|
||
extraOptions: {
|
||
exposePages: false,
|
||
},
|
||
}),
|
||
);
|
||
}
|
||
return config;
|
||
},
|
||
|
||
// 反向代理:/api/v1/* → api-gateway :8080
|
||
async rewrites() {
|
||
const gateway = process.env.API_GATEWAY_URL || "http://localhost:8080";
|
||
return [
|
||
{
|
||
source: "/api/v1/:path*",
|
||
destination: `${gateway}/api/v1/:path*`,
|
||
},
|
||
{
|
||
// 登录端点(非 GraphQL,认证前提,F12: JWT 存 localStorage)
|
||
// 映射到 iam REST controller 路径 /v1/iam/*(iam.controller.ts @Controller("v1/iam"))
|
||
source: "/api/auth/:path*",
|
||
destination: `${gateway}/api/v1/iam/:path*`,
|
||
},
|
||
];
|
||
},
|
||
};
|
||
|
||
module.exports = nextConfig;
|