replace-static-import.cjs 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372
  1. #!/usr/bin/env node
  2. /**
  3. * 静态方法 Import 替换工具
  4. *
  5. * 功能:将 antd 静态方法的 import 替换为全局单例
  6. * 使用方法:node replace-static-import.js <目标目录> [--dry-run] [--backup]
  7. *
  8. * 替换规则:
  9. * - import { message } from 'antd' → import { globalMessage as message } from '@/utils/antd-global'
  10. * - import { notification } from 'antd' → import { globalNotification as notification } from '@/utils/antd-global'
  11. * - import { Modal } from 'antd' → import { globalModal as Modal } from '@/utils/antd-global'
  12. */
  13. const fs = require('fs');
  14. const path = require('path');
  15. // 统计信息
  16. const stats = {
  17. totalFiles: 0,
  18. scannedFiles: 0,
  19. modifiedFiles: 0,
  20. replacements: {
  21. message: 0,
  22. notification: 0,
  23. Modal: 0,
  24. },
  25. errors: [],
  26. };
  27. // 替换详情
  28. const replacementDetails = [];
  29. /**
  30. * 解析 import 语句,提取导入的模块
  31. */
  32. function parseImports(content) {
  33. // 匹配 import { ... } from 'antd'
  34. const importRegex = /import\s+{([^}]+)}\s+from\s+['"]antd['"]/g;
  35. const imports = [];
  36. let match;
  37. while ((match = importRegex.exec(content)) !== null) {
  38. const fullMatch = match[0];
  39. const importedItems = match[1]
  40. .split(',')
  41. .map(item => item.trim())
  42. .filter(item => item.length > 0);
  43. imports.push({
  44. fullMatch,
  45. start: match.index,
  46. end: match.index + fullMatch.length,
  47. items: importedItems,
  48. });
  49. }
  50. return imports;
  51. }
  52. /**
  53. * 生成新的 import 语句
  54. */
  55. function generateNewImports(importedItems) {
  56. const targetItems = ['message', 'notification', 'Modal'];
  57. // 分离需要替换的和不需要替换的
  58. const needReplace = [];
  59. const keepOriginal = [];
  60. importedItems.forEach(item => {
  61. // 处理 as 别名的情况: message as msg
  62. const baseItem = item.split(' as ')[0].trim();
  63. if (targetItems.includes(baseItem)) {
  64. needReplace.push(item);
  65. } else {
  66. keepOriginal.push(item);
  67. }
  68. });
  69. const newImports = [];
  70. // 如果有不需要替换的,保留原 import
  71. if (keepOriginal.length > 0) {
  72. newImports.push(`import { ${keepOriginal.join(', ')} } from 'antd'`);
  73. }
  74. // 添加全局单例的 import
  75. if (needReplace.length > 0) {
  76. const globalImports = needReplace.map(item => {
  77. const parts = item.split(' as ');
  78. const baseItem = parts[0].trim();
  79. const alias = parts[1] ? parts[1].trim() : baseItem;
  80. const mapping = {
  81. 'message': 'globalMessage',
  82. 'notification': 'globalNotification',
  83. 'Modal': 'globalModal',
  84. };
  85. return `${mapping[baseItem]} as ${alias}`;
  86. });
  87. newImports.push(`import { ${globalImports.join(', ')} } from '@/utils/antd-global'`);
  88. }
  89. return {
  90. newImports,
  91. needReplace: needReplace.map(item => item.split(' as ')[0].trim()),
  92. };
  93. }
  94. /**
  95. * 处理单个文件
  96. */
  97. function processFile(filePath, options) {
  98. let content = fs.readFileSync(filePath, 'utf-8');
  99. const originalContent = content;
  100. // 解析 import
  101. const imports = parseImports(content);
  102. if (imports.length === 0) {
  103. return; // 没有 antd import,跳过
  104. }
  105. let modified = false;
  106. const fileReplacements = {
  107. message: false,
  108. notification: false,
  109. Modal: false,
  110. };
  111. // 从后往前替换,避免索引变化
  112. imports.reverse().forEach(importInfo => {
  113. const { fullMatch, start, end, items } = importInfo;
  114. const { newImports, needReplace } = generateNewImports(items);
  115. if (needReplace.length > 0) {
  116. // 记录替换项
  117. needReplace.forEach(item => {
  118. fileReplacements[item] = true;
  119. stats.replacements[item]++;
  120. });
  121. // 替换 import 语句
  122. const before = content.substring(0, start);
  123. const after = content.substring(end);
  124. content = before + newImports.join('\n') + after;
  125. modified = true;
  126. }
  127. });
  128. if (modified) {
  129. stats.modifiedFiles++;
  130. replacementDetails.push({
  131. file: filePath,
  132. replacements: fileReplacements,
  133. });
  134. if (options.dryRun) {
  135. console.log(`\n📝 [DRY RUN] 将修改: ${filePath}`);
  136. if (fileReplacements.message) console.log(' ✓ message → globalMessage');
  137. if (fileReplacements.notification) console.log(' ✓ notification → globalNotification');
  138. if (fileReplacements.Modal) console.log(' ✓ Modal → globalModal');
  139. } else {
  140. // 备份
  141. if (options.backup) {
  142. fs.writeFileSync(filePath + '.bak', originalContent);
  143. }
  144. // 写入
  145. try {
  146. fs.writeFileSync(filePath, content, 'utf-8');
  147. console.log(`✅ 已修改: ${filePath}`);
  148. if (fileReplacements.message) console.log(' ✓ message → globalMessage');
  149. if (fileReplacements.notification) console.log(' ✓ notification → globalNotification');
  150. if (fileReplacements.Modal) console.log(' ✓ Modal → globalModal');
  151. } catch (error) {
  152. stats.errors.push({ file: filePath, error: error.message });
  153. console.error(`❌ 修改失败: ${filePath}`);
  154. console.error(` 错误: ${error.message}`);
  155. }
  156. }
  157. }
  158. }
  159. /**
  160. * 递归扫描目录
  161. */
  162. function scanDirectory(dir, options) {
  163. const files = fs.readdirSync(dir);
  164. files.forEach(file => {
  165. const filePath = path.join(dir, file);
  166. const stat = fs.statSync(filePath);
  167. if (stat.isDirectory()) {
  168. if (!['node_modules', '.git', 'build', 'dist', '.next'].includes(file)) {
  169. scanDirectory(filePath, options);
  170. }
  171. } else if (stat.isFile()) {
  172. if (/\.(tsx?|jsx?)$/.test(file)) {
  173. stats.totalFiles++;
  174. stats.scannedFiles++;
  175. processFile(filePath, options);
  176. }
  177. }
  178. });
  179. }
  180. /**
  181. * 生成报告
  182. */
  183. function generateReport(options) {
  184. console.log('\n' + '='.repeat(80));
  185. console.log('📊 静态方法 Import 替换报告');
  186. if (options.dryRun) {
  187. console.log(' [DRY RUN 模式 - 未实际修改文件]');
  188. }
  189. console.log('='.repeat(80) + '\n');
  190. console.log('📁 文件统计:');
  191. console.log(` 扫描文件总数: ${stats.totalFiles}`);
  192. console.log(` 修改文件数: ${stats.modifiedFiles}\n`);
  193. console.log('📋 替换统计:');
  194. console.log(` message: ${stats.replacements.message} 处`);
  195. console.log(` notification: ${stats.replacements.notification} 处`);
  196. console.log(` Modal: ${stats.replacements.Modal} 处`);
  197. console.log(` 总计: ${Object.values(stats.replacements).reduce((a, b) => a + b, 0)} 处\n`);
  198. if (stats.errors.length > 0) {
  199. console.log('❌ 错误列表:');
  200. stats.errors.forEach(({ file, error }) => {
  201. console.log(` ${file}`);
  202. console.log(` ${error}`);
  203. });
  204. console.log('');
  205. }
  206. console.log('='.repeat(80));
  207. if (options.dryRun) {
  208. console.log('💡 提示: 这是 DRY RUN 模式,文件未被实际修改');
  209. console.log(' 如果确认无误,请去掉 --dry-run 参数重新运行');
  210. } else {
  211. console.log('✅ 替换完成!');
  212. if (options.backup) {
  213. console.log(' 原文件已备份为 .bak 文件');
  214. }
  215. console.log(' 建议使用 git diff 检查改动');
  216. }
  217. console.log('='.repeat(80) + '\n');
  218. // 重要提醒
  219. if (stats.modifiedFiles > 0) {
  220. console.log('⚠️ 重要提醒:');
  221. console.log(' 1. 确保已创建 src/utils/antd-global.ts 文件');
  222. console.log(' 2. 确保 AppProvider 已初始化全局实例');
  223. console.log(' 3. 确保根组件包裹了 <App> 组件');
  224. console.log(' 4. 运行项目测试所有 message/notification/Modal 功能');
  225. console.log(' 5. 参考 static-methods-migration.md 了解详细说明\n');
  226. console.log('📝 下一步:');
  227. console.log(' 1. 检查代码改动: git diff');
  228. console.log(' 2. 运行项目: npm start');
  229. console.log(' 3. 测试静态方法是否正常工作');
  230. console.log(' 4. 如有问题,参考 static-methods-migration.md\n');
  231. }
  232. }
  233. /**
  234. * 生成 antd-global.ts 模板(如果文件不存在)
  235. */
  236. function checkAntdGlobalFile(projectRoot) {
  237. const targetPath = path.join(projectRoot, 'src/utils/antd-global.ts');
  238. if (fs.existsSync(targetPath)) {
  239. console.log(`✅ 已存在: ${targetPath}\n`);
  240. return;
  241. }
  242. console.log(`\n⚠️ 警告: 未找到 src/utils/antd-global.ts 文件`);
  243. console.log(' 这是全局单例工具的核心文件,必须先创建!\n');
  244. console.log('建议操作:');
  245. console.log(' 1. 运行: node migration-tools/component-template.js --global-utils ./src/utils');
  246. console.log(' 或');
  247. console.log(' 2. 参考 static-methods-migration.md 手动创建\n');
  248. }
  249. /**
  250. * 主函数
  251. */
  252. function main() {
  253. const args = process.argv.slice(2);
  254. if (args.length === 0 || args.includes('--help') || args.includes('-h')) {
  255. console.log(`
  256. 静态方法 Import 替换工具
  257. 功能:
  258. 自动将 message/notification/Modal 的 import 替换为全局单例
  259. 用法:
  260. node replace-static-import.js <目标目录> [选项]
  261. 选项:
  262. --dry-run 只显示将要修改的内容,不实际修改文件
  263. --backup 修改前备份原文件(.bak)
  264. --help, -h 显示帮助信息
  265. 示例:
  266. # 预览将要修改的内容
  267. node replace-static-import.js ./src --dry-run
  268. # 执行替换并备份
  269. node replace-static-import.js ./src --backup
  270. 替换规则:
  271. import { message } from 'antd'
  272. → import { globalMessage as message } from '@/utils/antd-global'
  273. import { notification } from 'antd'
  274. → import { globalNotification as notification } from '@/utils/antd-global'
  275. import { Modal } from 'antd'
  276. → import { globalModal as Modal } from '@/utils/antd-global'
  277. 注意事项:
  278. 1. 必须先创建 src/utils/antd-global.ts 文件
  279. 2. 必须在 AppProvider 中初始化全局实例
  280. 3. 参考 static-methods-migration.md 了解详细说明
  281. `);
  282. process.exit(0);
  283. }
  284. const targetDir = args[0];
  285. const options = {
  286. dryRun: args.includes('--dry-run'),
  287. backup: args.includes('--backup'),
  288. };
  289. if (!fs.existsSync(targetDir)) {
  290. console.error(`❌ 目录不存在: ${targetDir}`);
  291. process.exit(1);
  292. }
  293. console.log(`\n🔧 开始处理目录: ${targetDir}`);
  294. if (options.dryRun) {
  295. console.log(' [DRY RUN 模式]');
  296. }
  297. if (options.backup) {
  298. console.log(' [备份模式]');
  299. }
  300. // 检查 antd-global.ts 文件
  301. const projectRoot = path.resolve(targetDir, '..');
  302. checkAntdGlobalFile(projectRoot);
  303. console.log('');
  304. scanDirectory(targetDir, options);
  305. generateReport(options);
  306. }
  307. main();