/** * 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, // 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) source: "/api/auth/:path*", destination: `${gateway}/api/auth/:path*`, }, ]; }, }; module.exports = nextConfig;