@meethive/vite 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +2118 -0
- package/dist/index.mjs +2096 -0
- package/dist/src/federation/src/dev/expose-development.d.ts +5 -0
- package/dist/src/federation/src/dev/expose-development.d.ts.map +1 -0
- package/dist/src/federation/src/dev/remote-development.d.ts +5 -0
- package/dist/src/federation/src/dev/remote-development.d.ts.map +1 -0
- package/dist/src/federation/src/dev/shared-development.d.ts +5 -0
- package/dist/src/federation/src/dev/shared-development.d.ts.map +1 -0
- package/dist/src/federation/src/index.d.ts +7 -0
- package/dist/src/federation/src/index.d.ts.map +1 -0
- package/dist/src/federation/src/prod/expose-production.d.ts +5 -0
- package/dist/src/federation/src/prod/expose-production.d.ts.map +1 -0
- package/dist/src/federation/src/prod/remote-production.d.ts +7 -0
- package/dist/src/federation/src/prod/remote-production.d.ts.map +1 -0
- package/dist/src/federation/src/prod/shared-production.d.ts +5 -0
- package/dist/src/federation/src/prod/shared-production.d.ts.map +1 -0
- package/dist/src/federation/src/public.d.ts +40 -0
- package/dist/src/federation/src/public.d.ts.map +1 -0
- package/dist/src/federation/src/runtime/dynamic-remote.d.ts +79 -0
- package/dist/src/federation/src/runtime/dynamic-remote.d.ts.map +1 -0
- package/dist/src/federation/src/utils/html.d.ts +12 -0
- package/dist/src/federation/src/utils/html.d.ts.map +1 -0
- package/dist/src/federation/src/utils/index.d.ts +29 -0
- package/dist/src/federation/src/utils/index.d.ts.map +1 -0
- package/dist/src/federation/src/utils/semver/compare.d.ts +10 -0
- package/dist/src/federation/src/utils/semver/compare.d.ts.map +1 -0
- package/dist/src/federation/src/utils/semver/constants.d.ts +11 -0
- package/dist/src/federation/src/utils/semver/constants.d.ts.map +1 -0
- package/dist/src/federation/src/utils/semver/parser.d.ts +10 -0
- package/dist/src/federation/src/utils/semver/parser.d.ts.map +1 -0
- package/dist/src/federation/src/utils/semver/satisfy.d.ts +2 -0
- package/dist/src/federation/src/utils/semver/satisfy.d.ts.map +1 -0
- package/dist/src/federation/src/utils/semver/utils.d.ts +12 -0
- package/dist/src/federation/src/utils/semver/utils.d.ts.map +1 -0
- package/dist/src/monaco-editor/index.d.ts +35 -0
- package/dist/src/monaco-editor/index.d.ts.map +1 -0
- package/dist/src/monaco-editor/languageWork.d.ts +10 -0
- package/dist/src/monaco-editor/languageWork.d.ts.map +1 -0
- package/dist/src/monaco-editor/workerMiddleware.d.ts +9 -0
- package/dist/src/monaco-editor/workerMiddleware.d.ts.map +1 -0
- package/dist/src/sharp/index.d.ts +12 -0
- package/dist/src/sharp/index.d.ts.map +1 -0
- package/index.ts +3 -0
- package/package.json +48 -0
- package/src/federation/src/dev/expose-development.ts +29 -0
- package/src/federation/src/dev/remote-development.ts +435 -0
- package/src/federation/src/dev/shared-development.ts +29 -0
- package/src/federation/src/index.ts +242 -0
- package/src/federation/src/prod/expose-production.ts +333 -0
- package/src/federation/src/prod/federation_fn_import.js +75 -0
- package/src/federation/src/prod/remote-production.ts +658 -0
- package/src/federation/src/prod/shared-production.ts +268 -0
- package/src/federation/src/public.ts +54 -0
- package/src/federation/src/runtime/dynamic-remote.ts +247 -0
- package/src/federation/src/utils/html.ts +165 -0
- package/src/federation/src/utils/index.ts +255 -0
- package/src/federation/src/utils/semver/compare.ts +131 -0
- package/src/federation/src/utils/semver/constants.ts +46 -0
- package/src/federation/src/utils/semver/parser.ts +253 -0
- package/src/federation/src/utils/semver/satisfy.ts +151 -0
- package/src/federation/src/utils/semver/utils.ts +93 -0
- package/src/federation/types/dynamic-remote.d.ts +105 -0
- package/src/federation/types/index.d.ts +344 -0
- package/src/federation/types/pluginHooks.d.ts +4 -0
- package/src/federation/types/virtual-modules.d.ts +48 -0
- package/src/federation/types/viteDevServer.d.ts +22 -0
- package/src/monaco-editor/index.ts +205 -0
- package/src/monaco-editor/languageWork.ts +36 -0
- package/src/monaco-editor/workerMiddleware.ts +78 -0
- package/src/sharp/index.ts +93 -0
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
// *****************************************************************************
|
|
2
|
+
// Copyright (C) 2022 Origin.js and others.
|
|
3
|
+
//
|
|
4
|
+
// This program and the accompanying materials are licensed under Mulan PSL v2.
|
|
5
|
+
// You can use this software according to the terms and conditions of the Mulan PSL v2.
|
|
6
|
+
// You may obtain a copy of Mulan PSL v2 at:
|
|
7
|
+
// http://license.coscl.org.cn/MulanPSL2
|
|
8
|
+
// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
|
|
9
|
+
// EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
|
|
10
|
+
// MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
|
|
11
|
+
// See the Mulan PSL v2 for more details.
|
|
12
|
+
//
|
|
13
|
+
// SPDX-License-Identifier: MulanPSL-2.0
|
|
14
|
+
// *****************************************************************************
|
|
15
|
+
|
|
16
|
+
import type { PluginHooks } from '../../types/pluginHooks'
|
|
17
|
+
import { NAME_CHAR_REG, parseSharedOptions, removeNonRegLetter } from '../utils'
|
|
18
|
+
import { parsedOptions } from '../public'
|
|
19
|
+
import type { ConfigTypeSet, VitePluginFederationOptions } from '../../types'
|
|
20
|
+
import { basename, join, resolve } from 'path'
|
|
21
|
+
import { readdirSync, readFileSync, statSync } from 'fs'
|
|
22
|
+
const sharedFilePathReg = /__federation_shared_(.+)-.{8}\.js$/
|
|
23
|
+
|
|
24
|
+
// @ts-ignore
|
|
25
|
+
import federation_fn_import from './federation_fn_import.js?raw'
|
|
26
|
+
|
|
27
|
+
export function prodSharedPlugin(
|
|
28
|
+
options: VitePluginFederationOptions
|
|
29
|
+
): PluginHooks {
|
|
30
|
+
parsedOptions.prodShared = parseSharedOptions(options)
|
|
31
|
+
const shareName2Prop = new Map<string, any>()
|
|
32
|
+
parsedOptions.prodShared.forEach((value) =>
|
|
33
|
+
shareName2Prop.set(removeNonRegLetter(value[0], NAME_CHAR_REG), value[1])
|
|
34
|
+
)
|
|
35
|
+
let isHost
|
|
36
|
+
let isRemote
|
|
37
|
+
const id2Prop = new Map<string, any>()
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
function negotiateManualChunksCompatibility(inputOptions: any) {
|
|
41
|
+
if (!inputOptions.output) return
|
|
42
|
+
|
|
43
|
+
const outputs = Array.isArray(inputOptions.output)
|
|
44
|
+
? inputOptions.output
|
|
45
|
+
: [inputOptions.output]
|
|
46
|
+
|
|
47
|
+
outputs.forEach(output => {
|
|
48
|
+
if (!output.manualChunks) return
|
|
49
|
+
|
|
50
|
+
const sharedModuleNames = [...shareName2Prop.keys()]
|
|
51
|
+
const conflictModules = new Set<string>()
|
|
52
|
+
|
|
53
|
+
// 检测冲突模块
|
|
54
|
+
if (typeof output.manualChunks === 'object') {
|
|
55
|
+
Object.values(output.manualChunks).flat().forEach((mod:any) => {
|
|
56
|
+
if (sharedModuleNames.some(shared => mod.includes(shared) || mod === shared)) {
|
|
57
|
+
conflictModules.add(mod)
|
|
58
|
+
}
|
|
59
|
+
})
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (conflictModules.size > 0) {
|
|
63
|
+
console.info(
|
|
64
|
+
`[Federation] 检测到模块 [${Array.from(conflictModules).join(', ')}] ` +
|
|
65
|
+
`同时配置在 shared 和 manualChunks 中,启用协商共存模式`
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
// 协商策略:修改 shared 配置以适配 manualChunks
|
|
69
|
+
adaptSharedToManualChunks(conflictModules)
|
|
70
|
+
}
|
|
71
|
+
})
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function adaptSharedToManualChunks(conflictModules: Set<string>) {
|
|
75
|
+
// 为冲突模块调整 federation 共享策略
|
|
76
|
+
parsedOptions.prodShared.forEach((sharedItem, index) => {
|
|
77
|
+
const [moduleName, config] = sharedItem as any
|
|
78
|
+
|
|
79
|
+
if (conflictModules.has(moduleName)) {
|
|
80
|
+
// 保持 federation 共享逻辑,但标记为兼容模式
|
|
81
|
+
config.manualChunkCompat = true
|
|
82
|
+
config.chunkLoading = 'defer' // 延迟加载,让 manualChunk 先处理
|
|
83
|
+
|
|
84
|
+
console.info(
|
|
85
|
+
`[Federation] 模块 "${moduleName}" 设置为兼容模式:` +
|
|
86
|
+
`manualChunks 负责分块,federation 负责运行时共享`
|
|
87
|
+
)
|
|
88
|
+
}
|
|
89
|
+
})
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return {
|
|
93
|
+
name: 'originjs:shared-production',
|
|
94
|
+
virtualFile: {
|
|
95
|
+
__federation_fn_import: federation_fn_import
|
|
96
|
+
},
|
|
97
|
+
options(inputOptions) {
|
|
98
|
+
isRemote = !!parsedOptions.prodExpose.length
|
|
99
|
+
isHost = options.isHost
|
|
100
|
+
|
|
101
|
+
if (shareName2Prop.size) {
|
|
102
|
+
// remove item which is both in external and shared
|
|
103
|
+
inputOptions.external = (
|
|
104
|
+
inputOptions.external as (string | RegExp)[]
|
|
105
|
+
)?.filter((item) => {
|
|
106
|
+
if (item instanceof RegExp)
|
|
107
|
+
return ![...shareName2Prop.keys()].some((key) => item.test(key))
|
|
108
|
+
return !shareName2Prop.has(removeNonRegLetter(item, NAME_CHAR_REG))
|
|
109
|
+
})
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// 协商共存:处理 manualChunks 与 shared 的兼容
|
|
113
|
+
negotiateManualChunksCompatibility(inputOptions)
|
|
114
|
+
|
|
115
|
+
return inputOptions
|
|
116
|
+
},
|
|
117
|
+
async buildStart() {
|
|
118
|
+
// Cannot emit chunks after module loading has finished, so emitFile first.
|
|
119
|
+
if (parsedOptions.prodShared.length && isRemote) {
|
|
120
|
+
this.emitFile({
|
|
121
|
+
name: '__federation_fn_import',
|
|
122
|
+
type: 'chunk',
|
|
123
|
+
id: '__federation_fn_import',
|
|
124
|
+
preserveSignature: 'strict'
|
|
125
|
+
})
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// forEach and collect dir
|
|
129
|
+
const collectDirFn = (filePath: string, collect: string[]) => {
|
|
130
|
+
const files = readdirSync(filePath)
|
|
131
|
+
files.forEach((name) => {
|
|
132
|
+
const tempPath = join(filePath, name)
|
|
133
|
+
const isDir = statSync(tempPath).isDirectory()
|
|
134
|
+
if (isDir) {
|
|
135
|
+
collect.push(tempPath)
|
|
136
|
+
collectDirFn(tempPath, collect)
|
|
137
|
+
}
|
|
138
|
+
})
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const monoRepos: { arr: string[]; root: string | ConfigTypeSet }[] = []
|
|
142
|
+
const dirPaths: string[] = []
|
|
143
|
+
const currentDir = resolve()
|
|
144
|
+
// try to get every module package.json file
|
|
145
|
+
for (const arr of parsedOptions.prodShared) {
|
|
146
|
+
if (isHost && !arr[1].version && !arr[1].manuallyPackagePathSetting) {
|
|
147
|
+
const packageJsonPath = (
|
|
148
|
+
await this.resolve(`${arr[1].packagePath}/package.json`)
|
|
149
|
+
)?.id
|
|
150
|
+
if (packageJsonPath) {
|
|
151
|
+
const packageJson = JSON.parse(
|
|
152
|
+
readFileSync(packageJsonPath, { encoding: 'utf-8' })
|
|
153
|
+
)
|
|
154
|
+
arr[1].version = packageJson.version
|
|
155
|
+
} else {
|
|
156
|
+
arr[1].removed = true
|
|
157
|
+
const dir = join(currentDir, 'node_modules', arr[0])
|
|
158
|
+
const dirStat = statSync(dir)
|
|
159
|
+
if (dirStat.isDirectory()) {
|
|
160
|
+
collectDirFn(dir, dirPaths)
|
|
161
|
+
} else {
|
|
162
|
+
this.error(`cant resolve "${arr[1].packagePath}"`)
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
if (dirPaths.length > 0) {
|
|
166
|
+
monoRepos.push({ arr: dirPaths, root: arr })
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
if (!arr[1].removed && !arr[1].version) {
|
|
171
|
+
this.error(
|
|
172
|
+
`No description file or no version in description file (usually package.json) of ${arr[0]}. Add version to description file, or manually specify version in shared config.`
|
|
173
|
+
)
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
parsedOptions.prodShared = parsedOptions.prodShared.filter(
|
|
178
|
+
(item) => !item[1].removed
|
|
179
|
+
)
|
|
180
|
+
// assign version to monoRepo
|
|
181
|
+
if (monoRepos.length > 0) {
|
|
182
|
+
for (const monoRepo of monoRepos) {
|
|
183
|
+
for (const id of monoRepo.arr) {
|
|
184
|
+
try {
|
|
185
|
+
const idResolve = await this.resolve(id)
|
|
186
|
+
if (idResolve?.id) {
|
|
187
|
+
(parsedOptions.prodShared as any[]).push([
|
|
188
|
+
`${monoRepo.root[0]}/${basename(id)}`,
|
|
189
|
+
{
|
|
190
|
+
id: idResolve?.id,
|
|
191
|
+
import: monoRepo.root[1].import,
|
|
192
|
+
shareScope: monoRepo.root[1].shareScope,
|
|
193
|
+
root: monoRepo.root
|
|
194
|
+
}
|
|
195
|
+
])
|
|
196
|
+
}
|
|
197
|
+
} catch (e) {
|
|
198
|
+
// ignore
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
if (parsedOptions.prodShared.length && isRemote) {
|
|
205
|
+
for (const prod of parsedOptions.prodShared) {
|
|
206
|
+
id2Prop.set(prod[1].id, prod[1])
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
},
|
|
210
|
+
|
|
211
|
+
outputOptions: function (outputOption) {
|
|
212
|
+
// remove rollup generated empty imports,like import './filename.js'
|
|
213
|
+
outputOption.hoistTransitiveImports = false
|
|
214
|
+
|
|
215
|
+
const manualChunkFunc = (id: string) => {
|
|
216
|
+
// if id is in shared dependencies, return id ,else return vite function value
|
|
217
|
+
const find = parsedOptions.prodShared.find((arr) =>
|
|
218
|
+
arr[1].dependencies?.has(id)
|
|
219
|
+
)
|
|
220
|
+
return find ? find[0] : undefined
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// only active when manualChunks is function,array not to solve
|
|
224
|
+
if (typeof outputOption.manualChunks === 'function') {
|
|
225
|
+
outputOption.manualChunks = new Proxy(outputOption.manualChunks, {
|
|
226
|
+
apply(target, thisArg, argArray) {
|
|
227
|
+
const result = manualChunkFunc(argArray[0])
|
|
228
|
+
return result ? result : target(argArray[0], argArray[1])
|
|
229
|
+
}
|
|
230
|
+
})
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// The default manualChunk function is no longer available from vite 2.9.0
|
|
234
|
+
if (outputOption.manualChunks === undefined) {
|
|
235
|
+
outputOption.manualChunks = manualChunkFunc
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
return outputOption
|
|
239
|
+
},
|
|
240
|
+
|
|
241
|
+
generateBundle(options, bundle) {
|
|
242
|
+
if (!isRemote) {
|
|
243
|
+
return
|
|
244
|
+
}
|
|
245
|
+
const needRemoveShared = new Set<string>()
|
|
246
|
+
for (const key in bundle) {
|
|
247
|
+
const chunk = bundle[key]
|
|
248
|
+
if (chunk.type === 'chunk') {
|
|
249
|
+
if (!isHost) {
|
|
250
|
+
const regRst = sharedFilePathReg.exec(chunk.fileName)
|
|
251
|
+
if (
|
|
252
|
+
regRst &&
|
|
253
|
+
shareName2Prop.get(removeNonRegLetter(regRst[1], NAME_CHAR_REG))
|
|
254
|
+
?.generate === false
|
|
255
|
+
) {
|
|
256
|
+
needRemoveShared.add(key)
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
if (needRemoveShared.size !== 0) {
|
|
262
|
+
for (const key of needRemoveShared) {
|
|
263
|
+
delete bundle[key]
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
// *****************************************************************************
|
|
2
|
+
// Copyright (C) 2022 Origin.js and others.
|
|
3
|
+
//
|
|
4
|
+
// This program and the accompanying materials are licensed under Mulan PSL v2.
|
|
5
|
+
// You can use this software according to the terms and conditions of the Mulan PSL v2.
|
|
6
|
+
// You may obtain a copy of Mulan PSL v2 at:
|
|
7
|
+
// http://license.coscl.org.cn/MulanPSL2
|
|
8
|
+
// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
|
|
9
|
+
// EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
|
|
10
|
+
// MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
|
|
11
|
+
// See the Mulan PSL v2 for more details.
|
|
12
|
+
//
|
|
13
|
+
// SPDX-License-Identifier: MulanPSL-2.0
|
|
14
|
+
// *****************************************************************************
|
|
15
|
+
|
|
16
|
+
import type { ConfigTypeSet, RemotesConfig } from '../types'
|
|
17
|
+
import type { ResolvedConfig } from 'vite'
|
|
18
|
+
import { Remote } from './utils'
|
|
19
|
+
// for generateBundle Hook replace
|
|
20
|
+
export const EXPOSES_MAP = new Map()
|
|
21
|
+
export const EXPOSES_KEY_MAP = new Map()
|
|
22
|
+
export const SHARED = 'shared'
|
|
23
|
+
export const DYNAMIC_LOADING_CSS = 'dynamicLoadingCss'
|
|
24
|
+
export const DYNAMIC_LOADING_CSS_PREFIX = '__v__css__'
|
|
25
|
+
export const DEFAULT_ENTRY_FILENAME = 'remoteEntry.js'
|
|
26
|
+
export const EXTERNALS: string[] = []
|
|
27
|
+
export const ROLLUP = 'rollup'
|
|
28
|
+
export const VITE = 'vite'
|
|
29
|
+
export const VitePluginFederationVersion = '1.5.0'
|
|
30
|
+
export const builderInfo = {
|
|
31
|
+
builder: 'rollup',
|
|
32
|
+
version: '',
|
|
33
|
+
assetsDir: '',
|
|
34
|
+
isHost: false,
|
|
35
|
+
isRemote: false,
|
|
36
|
+
isShared: false,
|
|
37
|
+
}
|
|
38
|
+
export const parsedOptions = {
|
|
39
|
+
prodExpose: [] as (string | ConfigTypeSet)[],
|
|
40
|
+
prodRemote: [] as (string | ConfigTypeSet)[],
|
|
41
|
+
prodShared: [] as (string | ConfigTypeSet)[],
|
|
42
|
+
devShared: [] as (string | ConfigTypeSet)[],
|
|
43
|
+
devExpose: [] as (string | ConfigTypeSet)[],
|
|
44
|
+
devRemote: [] as (string | ConfigTypeSet)[]
|
|
45
|
+
}
|
|
46
|
+
export const devRemotes: {
|
|
47
|
+
id: string
|
|
48
|
+
regexp: RegExp
|
|
49
|
+
config: RemotesConfig
|
|
50
|
+
}[] = []
|
|
51
|
+
export const prodRemotes: Remote[] = []
|
|
52
|
+
export const viteConfigResolved: { config: ResolvedConfig | undefined } = {
|
|
53
|
+
config: undefined
|
|
54
|
+
}
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
// *****************************************************************************
|
|
2
|
+
// Copyright (C) 2022 Origin.js and others.
|
|
3
|
+
//
|
|
4
|
+
// This program and the accompanying materials are licensed under Mulan PSL v2.
|
|
5
|
+
// You can use this software according to the terms and conditions of the Mulan PSL v2.
|
|
6
|
+
// You may obtain a copy of Mulan PSL v2 at:
|
|
7
|
+
// http://license.coscl.org.cn/MulanPSL2
|
|
8
|
+
// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
|
|
9
|
+
// EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
|
|
10
|
+
// MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
|
|
11
|
+
// See the Mulan PSL v2 for more details.
|
|
12
|
+
//
|
|
13
|
+
// SPDX-License-Identifier: MulanPSL-2.0
|
|
14
|
+
// *****************************************************************************
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Dynamic remote loader for runtime
|
|
18
|
+
* Provides utilities to dynamically load and manage remote components at runtime
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
export interface RemoteOptions {
|
|
22
|
+
url: string | (() => string | Promise<string>);
|
|
23
|
+
format?: 'esm' | 'systemjs' | 'var';
|
|
24
|
+
from?: 'vite' | 'webpack';
|
|
25
|
+
shareScope?: string;
|
|
26
|
+
external?: string | string[];
|
|
27
|
+
externalType?: 'url' | 'promise';
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface RemoteComponentConfig {
|
|
31
|
+
name: string;
|
|
32
|
+
component: string;
|
|
33
|
+
url: string | (() => string | Promise<string>);
|
|
34
|
+
options?: RemoteOptions;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Dynamic remote component manager
|
|
39
|
+
*/
|
|
40
|
+
export class DynamicRemoteManager {
|
|
41
|
+
private remoteCache = new Map<string, any>();
|
|
42
|
+
private loadingPromises = new Map<string, Promise<any>>();
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Add a remote dynamically at runtime
|
|
46
|
+
* @param name Remote name
|
|
47
|
+
* @param config Remote configuration
|
|
48
|
+
*/
|
|
49
|
+
async addRemote(name: string, config: RemoteOptions): Promise<void> {
|
|
50
|
+
try {
|
|
51
|
+
// 直接导入虚拟模块,避免使用Function构造器
|
|
52
|
+
// @ts-ignore
|
|
53
|
+
const federationModule = await import('virtual:__federation__');
|
|
54
|
+
const { __federation_method_add_origin_setRemote } = federationModule;
|
|
55
|
+
|
|
56
|
+
// @ts-ignore
|
|
57
|
+
await __federation_method_add_origin_setRemote(name, config.url, {
|
|
58
|
+
format: config.format || 'esm',
|
|
59
|
+
from: config.from || 'vite',
|
|
60
|
+
shareScope: config.shareScope || 'default',
|
|
61
|
+
external: config.external,
|
|
62
|
+
externalType: config.externalType || 'url'
|
|
63
|
+
});
|
|
64
|
+
} catch (error) {
|
|
65
|
+
throw new Error(`Failed to add dynamic remote '${name}': ${error.message}. Make sure to enable dynamic remotes in your plugin configuration with 'enableDynamicRemotes: true'.`);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Load a remote component dynamically
|
|
71
|
+
* @param remoteName Remote name
|
|
72
|
+
* @param componentName Component name to load from remote
|
|
73
|
+
* @returns Promise of the loaded component
|
|
74
|
+
*/
|
|
75
|
+
async loadRemoteComponent(remoteName: string, componentName: string): Promise<any> {
|
|
76
|
+
const cacheKey = `${remoteName}/${componentName}`;
|
|
77
|
+
|
|
78
|
+
// Check cache first
|
|
79
|
+
if (this.remoteCache.has(cacheKey)) {
|
|
80
|
+
return this.remoteCache.get(cacheKey);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Check if already loading
|
|
84
|
+
if (this.loadingPromises.has(cacheKey)) {
|
|
85
|
+
return this.loadingPromises.get(cacheKey);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Start loading
|
|
89
|
+
const loadingPromise = this.doLoadRemoteComponent(remoteName, componentName);
|
|
90
|
+
this.loadingPromises.set(cacheKey, loadingPromise);
|
|
91
|
+
|
|
92
|
+
try {
|
|
93
|
+
const component = await loadingPromise;
|
|
94
|
+
this.remoteCache.set(cacheKey, component);
|
|
95
|
+
return component;
|
|
96
|
+
} finally {
|
|
97
|
+
this.loadingPromises.delete(cacheKey);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
private async doLoadRemoteComponent(remoteName: string, componentName: string): Promise<any> {
|
|
102
|
+
try {
|
|
103
|
+
// 直接导入虚拟模块,避免使用Function构造器
|
|
104
|
+
// @ts-ignore
|
|
105
|
+
const federationModule = await import('virtual:__federation__');
|
|
106
|
+
const { __federation_method_getRemote } = federationModule;
|
|
107
|
+
return __federation_method_getRemote(remoteName, componentName);
|
|
108
|
+
} catch (error) {
|
|
109
|
+
throw new Error(`Failed to load remote component '${remoteName}/${componentName}': ${error.message}. Make sure to enable dynamic remotes in your plugin configuration with 'enableDynamicRemotes: true'.`);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Preload a remote component
|
|
115
|
+
* @param remoteName Remote name
|
|
116
|
+
* @param componentName Component name
|
|
117
|
+
*/
|
|
118
|
+
async preloadRemoteComponent(remoteName: string, componentName: string): Promise<void> {
|
|
119
|
+
try {
|
|
120
|
+
await this.loadRemoteComponent(remoteName, componentName);
|
|
121
|
+
} catch (error) {
|
|
122
|
+
console.warn(`Failed to preload remote component ${remoteName}/${componentName}:`, error);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Remove a remote component from cache
|
|
128
|
+
* @param remoteName Remote name
|
|
129
|
+
* @param componentName Component name
|
|
130
|
+
*/
|
|
131
|
+
clearRemoteComponentCache(remoteName: string, componentName?: string): void {
|
|
132
|
+
if (componentName) {
|
|
133
|
+
const cacheKey = `${remoteName}/${componentName}`;
|
|
134
|
+
this.remoteCache.delete(cacheKey);
|
|
135
|
+
} else {
|
|
136
|
+
// Clear all components for this remote
|
|
137
|
+
for (const key of this.remoteCache.keys()) {
|
|
138
|
+
if (key.startsWith(`${remoteName}/`)) {
|
|
139
|
+
this.remoteCache.delete(key);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Get all cached remote components
|
|
147
|
+
*/
|
|
148
|
+
getCachedRemotes(): string[] {
|
|
149
|
+
return Array.from(this.remoteCache.keys());
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Check if a remote component is cached
|
|
154
|
+
* @param remoteName Remote name
|
|
155
|
+
* @param componentName Component name
|
|
156
|
+
*/
|
|
157
|
+
isRemoteComponentCached(remoteName: string, componentName: string): boolean {
|
|
158
|
+
const cacheKey = `${remoteName}/${componentName}`;
|
|
159
|
+
return this.remoteCache.has(cacheKey);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Global instance
|
|
164
|
+
export const dynamicRemoteManager = new DynamicRemoteManager();
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Convenience function to dynamically load remote component
|
|
168
|
+
* @param config Remote component configuration
|
|
169
|
+
*/
|
|
170
|
+
export async function loadDynamicRemoteComponent(config: RemoteComponentConfig): Promise<any> {
|
|
171
|
+
// Add remote if not already added
|
|
172
|
+
if (config.options) {
|
|
173
|
+
await dynamicRemoteManager.addRemote(config.name, {
|
|
174
|
+
url: config.url,
|
|
175
|
+
...config.options
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// Load the component
|
|
180
|
+
return dynamicRemoteManager.loadRemoteComponent(config.name, config.component);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Vue 3 composable for dynamic remote loading
|
|
185
|
+
* @param remoteName Remote name
|
|
186
|
+
* @param componentName Component name
|
|
187
|
+
*/
|
|
188
|
+
export function useDynamicRemote(remoteName: string, componentName: string) {
|
|
189
|
+
const loading = ref(true);
|
|
190
|
+
// @ts-ignore
|
|
191
|
+
const error = ref<Error | null>(null);
|
|
192
|
+
// @ts-ignore
|
|
193
|
+
const component = ref<any>(null);
|
|
194
|
+
|
|
195
|
+
const loadComponent = async () => {
|
|
196
|
+
try {
|
|
197
|
+
loading.value = true;
|
|
198
|
+
error.value = null;
|
|
199
|
+
component.value = await dynamicRemoteManager.loadRemoteComponent(remoteName, componentName);
|
|
200
|
+
} catch (err) {
|
|
201
|
+
error.value = err instanceof Error ? err : new Error('Failed to load remote component');
|
|
202
|
+
console.error(`Failed to load remote component ${remoteName}/${componentName}:`, err);
|
|
203
|
+
} finally {
|
|
204
|
+
loading.value = false;
|
|
205
|
+
}
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
// Auto-load on mount
|
|
209
|
+
onMounted(() => {
|
|
210
|
+
loadComponent();
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
return {
|
|
214
|
+
component: readonly(component),
|
|
215
|
+
loading: readonly(loading),
|
|
216
|
+
error: readonly(error),
|
|
217
|
+
reload: loadComponent
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// Helper function to check if we're in a Vue environment
|
|
222
|
+
function isVueAvailable(): boolean {
|
|
223
|
+
try {
|
|
224
|
+
return typeof ref !== 'undefined' && typeof onMounted !== 'undefined';
|
|
225
|
+
} catch {
|
|
226
|
+
return false;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// Import Vue composables only if Vue is available
|
|
231
|
+
let ref: any, onMounted: any, readonly: any;
|
|
232
|
+
if (isVueAvailable()) {
|
|
233
|
+
try {
|
|
234
|
+
// 使用动态导入而不是 require
|
|
235
|
+
// @ts-ignore
|
|
236
|
+
import('vue').then(vue => {
|
|
237
|
+
ref = vue.ref;
|
|
238
|
+
onMounted = vue.onMounted;
|
|
239
|
+
readonly = vue.readonly;
|
|
240
|
+
}).catch(() => {
|
|
241
|
+
// Vue not available, composable won't work
|
|
242
|
+
console.warn('Vue not available for dynamic import');
|
|
243
|
+
});
|
|
244
|
+
} catch {
|
|
245
|
+
// Vue not available, composable won't work
|
|
246
|
+
}
|
|
247
|
+
}
|