|
@@ -1,4 +1,6 @@
|
|
|
import { type RouteRecordRaw } from 'vue-router'
|
|
import { type RouteRecordRaw } from 'vue-router'
|
|
|
|
|
+import fs from 'node:fs'
|
|
|
|
|
+import path from 'node:path'
|
|
|
|
|
|
|
|
interface ExposeItem {
|
|
interface ExposeItem {
|
|
|
key: string
|
|
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 路由配置
|
|
* @param routes 路由配置
|
|
@@ -157,10 +209,13 @@ export function generateExposesFromRoutes(
|
|
|
const exposeItems = extractExposesFromRoutes(routes, '', onlyLeafNodes)
|
|
const exposeItems = extractExposesFromRoutes(routes, '', onlyLeafNodes)
|
|
|
|
|
|
|
|
// 转换为 exposes 对象
|
|
// 转换为 exposes 对象
|
|
|
|
|
+ // ⭐ 关键改动:每个 expose 指向自动生成的 wrapper 文件,而不是直接指向 Vue 组件
|
|
|
const exposes: Record<string, string> = {}
|
|
const exposes: Record<string, string> = {}
|
|
|
|
|
|
|
|
exposeItems.forEach((item) => {
|
|
exposeItems.forEach((item) => {
|
|
|
- exposes[item.key] = item.value
|
|
|
|
|
|
|
+ const fileName = exposeKeyToFileName(item.key)
|
|
|
|
|
+ const wrapperPath = generateWrapperFile(item.value, fileName)
|
|
|
|
|
+ exposes[item.key] = wrapperPath
|
|
|
})
|
|
})
|
|
|
|
|
|
|
|
return {
|
|
return {
|