Răsfoiți Sursa

FEAT:编写出公用文档及公用样式

czm 1 lună în urmă
părinte
comite
852b68de2e
8 a modificat fișierele cu 105 adăugiri și 13 ștergeri
  1. 1 2
      .env.development
  2. 1 2
      .env.production
  3. 3 0
      .gitignore
  4. 56 1
      plugins/generateExposesPlugin.ts
  5. 1 0
      rsbuild.config.ts
  6. 3 8
      src/bootstrap.ts
  7. 28 0
      src/remote-setup.ts
  8. 12 0
      src/styles/global.css

+ 1 - 2
.env.development

@@ -1,3 +1,2 @@
-VUE_APP_RESOURCE_CODE= 'a45d3f641810452390ff99ee4cb1ed12'
-VITE_API_BASE_NAME= 'scienceInformationOfficeUi'
+VUE_APP_RESOURCE_CODE= 'e74c8ec00277421688d4533dc58eb7cd'
 VITE_API_BASE_HOST= http://10.15.0.138:26278/

+ 1 - 2
.env.production

@@ -1,3 +1,2 @@
-VUE_APP_RESOURCE_CODE= 'a45d3f641810452390ff99ee4cb1ed12'
-VITE_API_BASE_NAME= 'scienceInformationOfficeUi'
+VUE_APP_RESOURCE_CODE= 'e74c8ec00277421688d4533dc58eb7cd'
 VITE_API_BASE_HOST= http://10.15.0.138:26278/

+ 3 - 0
.gitignore

@@ -7,6 +7,9 @@
 node_modules
 dist/
 
+# Auto-generated wrapper files (由 generateExposesPlugin 自动生成)
+src/.generated/
+
 # Profile
 .rspack-profile-*/
 

+ 56 - 1
plugins/generateExposesPlugin.ts

@@ -1,4 +1,6 @@
 import { type RouteRecordRaw } from 'vue-router'
+import fs from 'node:fs'
+import path from 'node:path'
 
 interface ExposeItem {
   key: string
@@ -12,6 +14,56 @@ interface ExposeItem {
   }
 }
 
+// wrapper 文件生成目录(相对于项目根目录)
+const GENERATED_DIR = './src/.generated'
+
+// remote-setup.ts 的路径(相对于 wrapper 文件)
+const REMOTE_SETUP_RELATIVE = '../remote-setup'
+
+/**
+ * 将 expose key(如 ./home/dashboard)转为合法的文件名(如 home_dashboard)
+ */
+function exposeKeyToFileName(key: string): string {
+  return key
+    .replace(/^\.\//, '')       // 去掉 ./
+    .replace(/\//g, '_')        // / → _
+    .replace(/-/g, '_')         // - → _
+}
+
+/**
+ * 生成 wrapper 文件并返回 wrapper 的路径
+ * wrapper 文件内容:import remote-setup + re-export Vue 组件
+ */
+function generateWrapperFile(componentPath: string, fileName: string): string {
+  // 确保生成目录存在
+  const generatedDir = path.resolve(process.cwd(), GENERATED_DIR)
+  if (!fs.existsSync(generatedDir)) {
+    fs.mkdirSync(generatedDir, { recursive: true })
+  }
+
+  // 计算从 wrapper 到 Vue 组件的相对路径
+  // wrapper 在 src/.generated/xxx.ts,组件在 src/views/XxxView.vue
+  // 所以相对路径是 ../views/XxxView.vue
+  const relativeComponentPath = componentPath
+    .replace(/^\.\//, '../')    // ./src/views/Xxx.vue → ../src/views/Xxx.vue
+    .replace(/src\//, '')       // ../src/views/Xxx.vue → ../views/Xxx.vue
+
+  // wrapper 文件内容
+  const content = [
+    '// ⚠️ 自动生成文件,请勿手动修改',
+    `import { ensureRemoteSetup } from '${REMOTE_SETUP_RELATIVE}'`,
+    'ensureRemoteSetup()',
+    `export { default } from '${relativeComponentPath}'`,
+    '',
+  ].join('\n')
+
+  const wrapperPath = path.join(generatedDir, `${fileName}.ts`)
+  fs.writeFileSync(wrapperPath, content, 'utf-8')
+
+  // 返回相对于项目根目录的路径,供 exposes 配置使用
+  return `${GENERATED_DIR}/${fileName}.ts`
+}
+
 /**
  * 递归遍历路由,提取所有需要暴露的组件
  * @param routes 路由配置
@@ -157,10 +209,13 @@ export function generateExposesFromRoutes(
   const exposeItems = extractExposesFromRoutes(routes, '', onlyLeafNodes)
 
   // 转换为 exposes 对象
+  // ⭐ 关键改动:每个 expose 指向自动生成的 wrapper 文件,而不是直接指向 Vue 组件
   const exposes: Record<string, string> = {}
 
   exposeItems.forEach((item) => {
-    exposes[item.key] = item.value
+    const fileName = exposeKeyToFileName(item.key)
+    const wrapperPath = generateWrapperFile(item.value, fileName)
+    exposes[item.key] = wrapperPath
   })
 
   return {

+ 1 - 0
rsbuild.config.ts

@@ -34,6 +34,7 @@ export default defineConfig((env:any) => {
   },
   output: {
       assetPrefix: 'auto',
+      injectStyles: true,  // ← 新增:将 CSS 内联到 JS 中
       // 🚀 使用 VITE_API_BASE_NAME 作为输出目录名称
       distPath: {
         root: distDir,

+ 3 - 8
src/bootstrap.ts

@@ -2,18 +2,13 @@ import { createApp } from 'vue';
 import { createPinia } from 'pinia';
 import Antd from 'ant-design-vue';
 import 'ant-design-vue/dist/reset.css';
+import './styles/global.css';
+import { ensureRemoteSetup } from './remote-setup';
+ensureRemoteSetup();
 import App from './App.vue';
 import router from './router';
 // ⭐ 创建 Pinia 实例(Remote 独立运行时需要)
 const pinia = createPinia();
-import { registerRemotes } from '@module-federation/enhanced/runtime';
-registerRemotes([
-  {
-    name: 'hostApp',
-    alias: 'hostApp-1',
-    entry: 'http://10.15.0.138:26278/mf-manifest.json',
-  }
-]);
 const app = createApp(App);
 app.use(router);
 app.use(pinia);  // ⭐ 必须在 mount 之前安装

+ 28 - 0
src/remote-setup.ts

@@ -0,0 +1,28 @@
+/**
+ * 子模块远程加载初始化入口
+ *
+ * 当主模块通过 loadRemote() 加载子模块时,bootstrap.ts 不会执行。
+ * 这个模块作为替代,由每个 expose 的 wrapper 自动引入。
+ *
+ * 用途:
+ * - 导入子模块全局样式
+ * - 初始化 store
+ * - 其他需要在远程加载时执行的初始化逻辑
+ */
+
+// 导入子模块全局样式(injectStyles: true 会将 CSS 内联到 JS 中)
+import './styles/global.css'
+
+let initialized = false
+
+/**
+ * 确保初始化逻辑只执行一次
+ * 多个 expose 组件加载时,只有第一个会触发初始化
+ */
+export function ensureRemoteSetup() {
+  if (initialized) return
+  initialized = true
+  console.log('Remote setup initialized,执行一次')
+  // 在这里添加需要在远程加载时执行的初始化逻辑
+  // 例如:store 初始化、事件注册、全局配置等
+}

+ 12 - 0
src/styles/global.css

@@ -0,0 +1,12 @@
+/**
+ * 子模块全局样式
+ *
+ * 这个文件中的样式会通过 remote-setup.ts → wrapper → expose 自动打包进联邦模块
+ * 主模块加载子模块时,样式会随 JS 一起注入,无需主模块额外处理
+ *
+ * 注意:请谨慎添加全局样式,避免与主模块样式冲突
+ * 推荐使用特定的前缀命名空间,例如:.remote-module-xxx
+ */
+.dashboard{
+  /* background: #0052cc; */
+}