Files
Edu/apps/teacher-portal/next.config.js
SpecialX 8b729a710f feat(teacher-portal): Docker Desktop 验证通过 + 切换 standalone 模式 + nextstep.md
- 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 验证结果
2026-07-13 16:52:13 +08:00

145 lines
4.9 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 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=falseP3+ 逐步接入
*
* 维护者ai13teacher-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.3P2 Remote 数量 = 0NEXT_PUBLIC_MF_ENABLED=false 默认关
* P3 首个 Remotestudent-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. GraphQLProviderurql 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-002MF Shell 配置exposes + sharedP2 无 remotes
// 仅当 NEXT_PUBLIC_MF_ENABLED=true 时实例化 NextFederationPlugin
// nextjs-mf 不支持 App DirectoryP2 阶段 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;