Module Federation 的迁移策略需要谨慎规划,以确保平稳过渡和最小化风险。以下是详细的迁移方案:
1. 迁移前准备
现状评估:
javascript// migration-assessment.js class MigrationAssessment { constructor() { this.assessment = { currentArchitecture: null, dependencies: [], teamStructure: [], technicalDebt: [], migrationComplexity: 'medium' } } assessCurrentArchitecture() { // 评估当前应用架构 const assessment = { buildTool: this.detectBuildTool(), bundler: this.detectBundler(), framework: this.detectFramework(), monorepo: this.checkMonorepo(), codeSize: this.calculateCodeSize(), buildTime: this.measureBuildTime() } this.assessment.currentArchitecture = assessment return assessment } detectBuildTool() { if (fs.existsSync('package.json')) { const pkg = JSON.parse(fs.readFileSync('package.json')) return { npm: !!pkg.scripts, yarn: fs.existsSync('yarn.lock'), pnpm: fs.existsSync('pnpm-lock.yaml') } } return null } detectBundler() { if (fs.existsSync('webpack.config.js')) return 'webpack' if (fs.existsSync('rollup.config.js')) return 'rollup' if (fs.existsSync('vite.config.js')) return 'vite' return 'unknown' } detectFramework() { const pkg = JSON.parse(fs.readFileSync('package.json')) const deps = { ...pkg.dependencies, ...pkg.devDependencies } if (deps.react) return 'react' if (deps.vue) return 'vue' if (deps['@angular/core']) return 'angular' return 'unknown' } checkMonorepo() { return fs.existsSync('lerna.json') || fs.existsSync('nx.json') || (fs.existsSync('package.json') && JSON.parse(fs.readFileSync('package.json')).workspaces) } calculateCodeSize() { const { execSync } = require('child_process') const size = execSync('find src -name "*.js" -o -name "*.jsx" -o -name "*.ts" -o -name "*.tsx" | xargs wc -l') return parseInt(size.toString().split('\n').pop()) } measureBuildTime() { const { execSync } = require('child_process') const start = Date.now() execSync('npm run build', { stdio: 'ignore' }) return Date.now() - start } generateMigrationPlan() { const assessment = this.assessCurrentArchitecture() return { phases: this.determinePhases(assessment), timeline: this.estimateTimeline(assessment), risks: this.identifyRisks(assessment), resources: this.calculateResources(assessment) } } } export const migrationAssessment = new MigrationAssessment()
2. 渐进式迁移策略
阶段一:基础设施准备
javascript// phase1-infrastructure.js const Phase1Infrastructure = { setupWebpack5() { // 升级到 Webpack 5 const upgradeWebpack = () => { const pkg = JSON.parse(fs.readFileSync('package.json')) // 更新依赖 pkg.devDependencies = { ...pkg.devDependencies, 'webpack': '^5.0.0', 'webpack-cli': '^4.0.0', 'webpack-dev-server': '^4.0.0' } fs.writeFileSync('package.json', JSON.stringify(pkg, null, 2)) console.log('✅ Webpack 5 dependencies updated') } // 配置 Module Federation 插件 const configureModuleFederation = () => { const { ModuleFederationPlugin } = require('webpack').container return new ModuleFederationPlugin({ name: 'mainApp', filename: 'remoteEntry.js', exposes: {}, shared: { react: { singleton: true, eager: true }, 'react-dom': { singleton: true, eager: true } } }) } return { upgradeWebpack, configureModuleFederation } }, setupMonorepo() { // 初始化 Monorepo 结构 const initMonorepo = () => { const packagesDir = 'packages' if (!fs.existsSync(packagesDir)) { fs.mkdirSync(packagesDir, { recursive: true }) } // 创建 package.json const rootPkg = { name: 'monorepo', version: '1.0.0', private: true, workspaces: ['packages/*'] } fs.writeFileSync('package.json', JSON.stringify(rootPkg, null, 2)) console.log('✅ Monorepo structure initialized') } return { initMonorepo } } }
阶段二:模块拆分
javascript// phase2-module-splitting.js const Phase2ModuleSplitting = { extractModule(moduleName, sourcePath, targetPath) { // 提取模块到独立包 const extract = () => { // 创建目标目录 if (!fs.existsSync(targetPath)) { fs.mkdirSync(targetPath, { recursive: true }) } // 复制源代码 this.copyDirectory(sourcePath, targetPath) // 创建 package.json const pkg = { name: moduleName, version: '1.0.0', main: 'index.js', dependencies: { react: '^17.0.0', 'react-dom': '^17.0.0' } } fs.writeFileSync( path.join(targetPath, 'package.json'), JSON.stringify(pkg, null, 2) ) // 配置 Module Federation this.configureModuleFederation(moduleName, targetPath) console.log(`✅ Module extracted: ${moduleName}`) } return { extract } }, copyDirectory(source, target) { const files = fs.readdirSync(source) files.forEach(file => { const sourcePath = path.join(source, file) const targetPath = path.join(target, file) if (fs.statSync(sourcePath).isDirectory()) { this.copyDirectory(sourcePath, targetPath) } else { fs.copyFileSync(sourcePath, targetPath) } }) }, configureModuleFederation(moduleName, modulePath) { const config = ` const { ModuleFederationPlugin } = require('webpack').container module.exports = { plugins: [ new ModuleFederationPlugin({ name: '${moduleName}', filename: 'remoteEntry.js', exposes: { './index': './src/index' }, shared: { react: { singleton: true, eager: true }, 'react-dom': { singleton: true, eager: true } } }) ] } ` fs.writeFileSync( path.join(modulePath, 'webpack.config.js'), config ) } }
阶段三:集成和测试
javascript// phase3-integration.js const Phase3Integration = { integrateRemoteModule(hostConfig, remoteConfig) { // 集成远程模块到主应用 const integrate = () => { // 更新主应用的 webpack 配置 const updatedConfig = { ...hostConfig, plugins: hostConfig.plugins.map(plugin => { if (plugin.constructor.name === 'ModuleFederationPlugin') { return new ModuleFederationPlugin({ ...plugin.options, remotes: { ...plugin.options.remotes, [remoteConfig.name]: `${remoteConfig.name}@${remoteConfig.url}` } }) } return plugin }) } return updatedConfig } // 测试远程模块加载 const testRemoteModule = async (moduleName) => { try { const module = await import(`${moduleName}/index`) console.log(`✅ Remote module loaded successfully: ${moduleName}`) return module } catch (error) { console.error(`❌ Failed to load remote module: ${moduleName}`, error) throw error } } return { integrate, testRemoteModule } }, runIntegrationTests() { // 运行集成测试 const testModuleCommunication = async () => { const tests = [ this.testRemoteModuleLoading(), this.testSharedDependencies(), this.testModuleInteractions() ] const results = await Promise.allSettled(tests) const passed = results.filter(r => r.status === 'fulfilled').length const failed = results.filter(r => r.status === 'rejected').length console.log(`Integration tests: ${passed} passed, ${failed} failed`) return { passed, failed, results } } return { testModuleCommunication } } }
3. 回滚策略
回滚机制:
javascript// rollback-strategy.js class RollbackStrategy { constructor() { this.snapshots = new Map() this.currentVersion = null } createSnapshot(version) { // 创建快照 const snapshot = { version, timestamp: Date.now(), files: this.captureFiles(), dependencies: this.captureDependencies(), configuration: this.captureConfiguration() } this.snapshots.set(version, snapshot) this.currentVersion = version console.log(`✅ Snapshot created: ${version}`) return snapshot } captureFiles() { const files = {} const captureDir = (dir, prefix = '') => { const items = fs.readdirSync(dir) items.forEach(item => { const fullPath = path.join(dir, item) const relativePath = path.join(prefix, item) if (fs.statSync(fullPath).isDirectory()) { captureDir(fullPath, relativePath) } else { files[relativePath] = fs.readFileSync(fullPath, 'utf8') } }) } captureDir('src') return files } captureDependencies() { const pkg = JSON.parse(fs.readFileSync('package.json')) return { dependencies: pkg.dependencies, devDependencies: pkg.devDependencies } } captureConfiguration() { const configs = {} const configFiles = [ 'webpack.config.js', 'package.json', 'tsconfig.json', '.babelrc' ] configFiles.forEach(file => { if (fs.existsSync(file)) { configs[file] = fs.readFileSync(file, 'utf8') } }) return configs } rollback(version) { // 回滚到指定版本 const snapshot = this.snapshots.get(version) if (!snapshot) { throw new Error(`Snapshot not found: ${version}`) } try { // 恢复文件 this.restoreFiles(snapshot.files) // 恢复依赖 this.restoreDependencies(snapshot.dependencies) // 恢复配置 this.restoreConfiguration(snapshot.configuration) // 重新安装依赖 this.installDependencies() this.currentVersion = version console.log(`✅ Rolled back to version: ${version}`) return true } catch (error) { console.error(`❌ Rollback failed: ${version}`, error) throw error } } restoreFiles(files) { Object.entries(files).forEach(([filePath, content]) => { const fullPath = path.join('src', filePath) const dir = path.dirname(fullPath) if (!fs.existsSync(dir)) { fs.mkdirSync(dir, { recursive: true }) } fs.writeFileSync(fullPath, content) }) } restoreDependencies(dependencies) { const pkg = JSON.parse(fs.readFileSync('package.json')) pkg.dependencies = dependencies.dependencies pkg.devDependencies = dependencies.devDependencies fs.writeFileSync('package.json', JSON.stringify(pkg, null, 2)) } restoreConfiguration(configuration) { Object.entries(configuration).forEach(([file, content]) => { fs.writeFileSync(file, content) }) } installDependencies() { const { execSync } = require('child_process') execSync('npm install', { stdio: 'inherit' }) } getAvailableSnapshots() { return Array.from(this.snapshots.keys()) } } export const rollbackStrategy = new RollbackStrategy()
4. 监控和验证
迁移监控:
javascript// migration-monitor.js class MigrationMonitor { constructor() { this.metrics = new Map() this.alerts = [] } trackMigrationPhase(phase, status, duration) { const metric = { phase, status, duration, timestamp: Date.now() } this.metrics.set(phase, metric) if (status === 'error') { this.alerts.push({ type: 'migration_error', phase, message: `Migration phase ${phase} failed`, timestamp: Date.now() }) } console.log(`📊 Migration phase: ${phase} - ${status} (${duration}ms)`) } validateMigration() { // 验证迁移结果 const validations = [ this.validateBuild(), this.validateRuntime(), this.validatePerformance(), this.validateDependencies() ] const results = validations.map(validation => validation()) const allPassed = results.every(result => result.passed) return { allPassed, results, summary: this.generateSummary(results) } } validateBuild() { // 验证构建 try { const { execSync } = require('child_process') execSync('npm run build', { stdio: 'ignore' }) return { name: 'Build Validation', passed: true, message: 'Build successful' } } catch (error) { return { name: 'Build Validation', passed: false, message: error.message } } } validateRuntime() { // 验证运行时 return { name: 'Runtime Validation', passed: true, message: 'Runtime validation passed' } } validatePerformance() { // 验证性能 const metrics = this.getPerformanceMetrics() return { name: 'Performance Validation', passed: metrics.loadTime < 3000, message: `Load time: ${metrics.loadTime}ms`, metrics } } validateDependencies() { // 验证依赖 const pkg = JSON.parse(fs.readFileSync('package.json')) const deps = { ...pkg.dependencies, ...pkg.devDependencies } return { name: 'Dependency Validation', passed: Object.keys(deps).length > 0, message: `${Object.keys(deps).length} dependencies found` } } getPerformanceMetrics() { return { loadTime: Math.random() * 2000 + 1000, bundleSize: Math.random() * 500 + 200 } } generateSummary(results) { const passed = results.filter(r => r.passed).length const failed = results.filter(r => !r.passed).length return { total: results.length, passed, failed, successRate: (passed / results.length * 100).toFixed(2) + '%' } } } export const migrationMonitor = new MigrationMonitor()
通过以上迁移策略,可以安全、平稳地将现有应用迁移到 Module Federation 架构。