const fs = require("fs"); const path = require("path"); const prettier = require("prettier"); const { exec } = require("child_process"); const { search, initDepts, depts } = require("./es6.xtype"); const lodash = require("lodash"); const DEPTS = depts; // const XTYPE_ONLY = false; // const THIS_REPLACE = false; const ConflictImport = []; const CircularDependency = []; function objHaveFunction(obj) { if (obj === null) { return false; } return Object.keys(obj).some(key => { const value = obj[key]; if (typeof value === "object") { return objHaveFunction(value); } else if (typeof value === "function") { return true; } return false; }); } function parserImport(code) { const reg = /import {([\s\S]*?)} from "(.*?)";/g; const importMap = {}; let regResult = reg.exec(code); while (regResult) { importMap[regResult[2]] = regResult[1] .split(",") .map(el => el.trim()) .filter(el => el); regResult = reg.exec(code); } return importMap; } async function saveAndFixCode(path, code) { let _code = code; if (!code) { _code = fs.readFileSync(path).toString(); } const prettierCode = prettier.format(_code, { tabWidth: 4, parser: "babel", printWidth: 120, }); fs.writeFileSync(path, prettierCode); new Promise(res => { exec(`yarn eslint --fix ${path}`, () => { res(); }); }); } function divideFile(srcName) { const targetPath = srcName.match(/.*\//g); const sourceCode = fs.readFileSync(srcName).toString(); const splitSourceCode = sourceCode.match(/[\.\s]{1}[^\s\.]+?\s=\sBI\.inherit\([^]*?BI\.shortcut.*\;/g); const newFileNames = []; for (let i = 0; i < splitSourceCode.length; i++) { // 去除开头的 空格 或者 . const newCode = splitSourceCode[i].slice(1); // 匹配第一个等号之前的组件名 const componentName = /BI\.shortcut\("(.*)"/.exec(newCode)[1]; // 文件名转化 ButtonIcon => demo.button.icon.js const demoFileName = componentName + '.js'; newFileNames.push(demoFileName); // 代码 componentName 前面加上 BI. const fileCode = 'BI.' + newCode; // 规范最后一行的组件为 BI.Button const targetComponet = /(BI\..*)\s=/.exec(fileCode)[1]; // 最后一行的内容 const replaceContext = /BI\.shortcut.*;/.exec(fileCode)[0]; // 替换 const finalCode = fileCode.replace(replaceContext,`BI.shortcut("${componentName}", ${targetComponet});`) // 创建新文件 fs.writeFileSync(targetPath + demoFileName, finalCode); } return newFileNames; } // const target = []; // 加载模块 const loader = { // G: { "@/core": { shortcut: true } }, load(srcName, module) { const G = loader.G; // if (target.indexOf(module) >= 0) { // G["@/core"][module] = true; // return true; // } if (module.startsWith('"bi.')) { const key = search(srcName, module); if (key) { if (!G[key]) { G[key] = {}; } const clzName = depts[module].clzName; G[key][clzName] = true; } return !!key; } else { const key = search(srcName, module); if (key) { if (!G[key]) { G[key] = {}; } G[key][module] = true; } return !!key; } }, }; async function handleFile(srcName) { await saveAndFixCode(srcName); // 全局状态回归 let G = (loader.G = {}); const sourceCode = fs.readFileSync(srcName).toString(); if (sourceCode.match(/shortcut/g).length > 1) { console.log('该文件存在多处BI.shorcut, 需要拆分...'); const newTargets = divideFile(srcName); newTargets.forEach(name => console.log(name)); return; } const result = /BI\.(.*?)\s=\sBI\.inherit\(/.exec(sourceCode); if (!result) { // console.log(`已经es6过,替换 xtype => ${srcName}`); if (!/export class/.test(sourceCode)) { // console.log("忽略文件", srcName); return; } // 处理 xtype const noXtypeCode = sourceCode.replace(/type:\s?"bi\.(.*?)"/g, v => { const matchedSentence = v.replace(/type:\s?/, ""); const loadSuccess = loader.load(srcName, matchedSentence); if (loadSuccess) { const clzName = depts[matchedSentence].clzName; return `type: ${clzName}.xtype`; } else { console.log(`xtype 替换失败 ${matchedSentence} `); return v; } }); // 识别 import const importMap = parserImport(noXtypeCode); // 合并原来的 import 到 G lodash.forEach(importMap, (depts, module) => { depts.forEach(dept => { if (!G[module]) { G[module] = {}; } G[module][dept] = true; }); }); // 合并包 const crossPackages = fs.readdirSync("src").map(el => `@/${el}`); lodash.forEach(G, (depts, module) => { crossPackages.forEach(crosspackage => { if (module.indexOf(crosspackage.replace(/^@\//, "")) >= 0) { if (!G[crosspackage]) { G[crosspackage] = {}; } Object.assign(G[crosspackage], depts); } }); }); const tmpG = {}; lodash.forEach(G, (depts, module) => { const flag = lodash.some( crossPackages, crosspackage => module.indexOf(crosspackage) >= 0 && !module.startsWith("@"), ); if (!flag) { tmpG[module] = depts; } }); let circle = false; // 处理循环依赖,base: ["@/case", "@/base", "@/widget"] 等于 base 不能直接引数组内的包 const forbiddenCrossRules = { base: ["@/case", "@/base", "@/widget"], "case": ["@/case", "@/widget"], widget: ["@/widget"], component: ["@/component"], core: ["@/core", "@base", "@/widget", "@/case"], }; const forbiddenKeys = []; const circleG = {}; lodash.forEach(G, (depts, module) => { // 找出 rule const packages = Object.keys(forbiddenCrossRules); let key = packages.filter( _package => srcName.indexOf(_package) >= 0, ); if (key.length !== 1) { throw new Error( "理论不可能出现这个问题,需要 treecat 优化下找包逻辑1", ); } key = key[0]; const rule = forbiddenCrossRules[key]; if (lodash.includes(rule, module)) { circle = true; const deptsArr = Object.keys(depts); if (deptsArr.filter(dept => !DEPTS[dept]).length > 0) { throw new Error( "理论不可能出现这个问题,需要 treecat 优化下找包逻辑2", ); } deptsArr .filter(dept => DEPTS[dept]) .forEach(dept => { const value = `@${DEPTS[dept].replace(path.resolve("src"), "").replace(/\\/g, "/").replace(/\.js$/, "")}`; if (!tmpG[value]) { tmpG[value] = {}; } tmpG[value][dept] = true; }); forbiddenKeys.push(module); } }); Object.assign(tmpG, circleG); forbiddenKeys.forEach(key => { delete tmpG[key]; }); // 较验手工 import 错误 const map = {}; let conflict = false; lodash.forEach(tmpG, (depts, module) => { lodash.forEach(depts, (_, _import) => { if (map[_import] && map[_import] !== module) { conflict = true; } map[_import] = module; }); }); conflict && ConflictImport.push(srcName); circle && CircularDependency.push(srcName); G = tmpG; const noImportCode = noXtypeCode.replace( /import {([\s\S]*?)} from "(.*?)";/g, "", ); let I = ""; Object.keys(G).forEach(key => { let moduleKey = key; if (moduleKey === path.basename(srcName).replace(/.js$/g, "")) { return; } let i = ""; Object.keys(G[moduleKey]).forEach(key => { i += `${key}, `; }); // 必须以 . 开头 const moduleInValid = /^[^@.]/.test(moduleKey); if (moduleInValid) { moduleKey = `./${moduleKey}`; } I += `import {${i}} from '${moduleKey}'\n`; }); const code = `${I}\n${noImportCode}`; await saveAndFixCode(srcName, code); return; } G["@/core"] = { shortcut: true }; const clzName = result[1]; if (!clzName) { console.log(`${srcName} 不需要 es6 化`); return; } const superName = /inherit\(BI\.(.*?),/.exec(sourceCode)[1]; // const xtype = /BI.shortcut\(\"(.*?)\"/.exec(sourceCode)[1]; const collection = { "static": {}, attr: {}, }; // eslint-disable-next-line no-unused-vars const BI = { [clzName]: clzName, inherit(_, options) { collection.methods = Object.keys(options) .filter(key => typeof options[key] === "function") .map(key => options[key]); Object.keys(options) .filter(key => typeof options[key] !== "function") .forEach(key => { collection.attr[key] = options[key]; }); return collection.static; }, extend(targetClz, o) { Object.assign(collection.static, o); }, shortcut(xtype) { collection.xtype = xtype; }, }; // eslint-disable-next-line no-eval eval(sourceCode); let M = ""; let E = ""; let I = ""; let A = ""; loader.load(srcName, superName); Object.keys(collection.attr).forEach(key => { const value = collection.attr[key]; if (typeof value === "function" || typeof value === "number") { A += `\t${key} = ${value}\n`; } else if (typeof value === "string") { A += `\t${key} = "${value}"\n`; } else if (typeof value === "object") { if (objHaveFunction(value)) { throw new Error("G"); } else { A += `\t${key} = ${JSON.stringify(value)}\n`; } } }); // 静态方法 Object.keys(collection.static).forEach(key => { // console.log(key, collection.static[key], typeof collection.static[key]) const value = collection.static[key]; if (typeof value === "function" || typeof value === "number") { E += `\tstatic ${key} = ${value}\n`; } else if (typeof value === "string") { E += `\tstatic ${key} = "${value}"\n`; } else if (typeof value === "object") { if (objHaveFunction(value)) { throw new Error("G"); } else { E += `\tstatic ${key} = ${JSON.stringify(value)}\n`; } } }); // 对函数进行替换 collection.methods.forEach(el => { let f = `${el.toString().replace(/^function/, el.name)}\n`; // 换 BI.Button.superclass,就说能不能跑吧 for (let i = 0; i < 100; i++) { f = f.replace(`BI.${clzName}.superclass`, "super"); } // 换 super._defaultConfig f = f.replace( /super\._defaultConfig\.apply\(this,\sarguments\)/g, "super._defaultConfig(...arguments)", ); // 换 super.xxx.apply f = f.replace(/super\.(.*?)\.apply\(this,\sarguments\)/g, a => { const f = /super\.(.*?)\.apply\(this,\sarguments\)/.exec(a); return `super.${f[1]}(...arguments)`; }); // 尝试换掉所有的 BI.xxx. BI.xxx( BI.xxx[空白] f = f.replace(/BI\.(.*?)(\.|\(|\s|,)/g, matchedSentence => { const match = /BI\.(.*?)(\.|\(|\s|,)/.exec(matchedSentence); const target = match[1]; const end = match[2]; // 尝试加载 target const loadSuccess = loader.load(srcName, target); if (loadSuccess) { return target + end; } else { console.log(`BI 变量替换失败 BI.${target}`); return matchedSentence; } }); // 尝试对 xtype 进行替换 f = f.replace(/"bi\.(.*?)"/g, matchedSentence => { const loadSuccess = loader.load(srcName, matchedSentence); if (loadSuccess) { const clzName = depts[matchedSentence].clzName; return `${clzName}.xtype`; } else { // console.log(`(没事) xtype 替换失败 ${matchedSentence} `); return matchedSentence; } }); M += `${f}\n`; }); if (!collection.xtype) { delete G["@/core"].shortcut; } Object.keys(G).forEach(key => { let moduleKey = key; if (moduleKey === path.basename(srcName).replace(/.js$/g, "")) { return; } let i = ""; Object.keys(G[moduleKey]).forEach(key => { i += `${key}, `; }); // 必须以 . 开头 const moduleInValid = /^[^@.]/.test(moduleKey); if (moduleInValid) { moduleKey = `./${moduleKey}`; } I += `import {${i}} from '${moduleKey}'\n`; }); const outputCode = ` ${I} ${collection.xtype ? "@shortcut()" : ""} export class ${clzName} extends ${superName} { ${collection.xtype ? `static xtype = "${collection.xtype}"` : ""} ${A} ${E} ${M} } `; await saveAndFixCode(srcName, outputCode); return clzName; } async function traverse(srcName) { if (srcName.indexOf("__test__") >= 0) return; if (srcName.endsWith(".js")) { try { return await handleFile(srcName); } catch (error) { console.log(`文件处理失败 ${srcName} \n`); console.error(error); return; } } else { const stat = fs.statSync(srcName); const flag = stat.isDirectory(); if (flag) { const files = fs.readdirSync(srcName); // let indexContent = ""; for (let i = 0; i < files.length; i++) { const file = files[i]; await traverse(path.resolve(srcName, file)); // const clzName = await traverse(path.resolve(srcName, file)); // const moduleName = path.basename(srcName).replace(/.js$/, ""); // if (clzName) { // indexContent += `export { ${clzName} } from '${moduleName}'\n`; // } } } } } const srcName = process.argv[2]; initDepts().then(async () => { await traverse(srcName); // 对数据处理 ConflictImport.forEach(el => { console.log(`导入冲突 ${el}`); }); CircularDependency.forEach(el => { console.log(`出现循环依赖(已经fixed) ${el}`); }); });