fineui是帆软报表和BI产品线所使用的前端框架。
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

446 lines
12 KiB

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 _ = require("lodash");
// const XTYPE_ONLY = false;
// const THIS_REPLACE = false;
function objHaveFunction(obj) {
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',
});
fs.writeFileSync(path, prettierCode);
new Promise(res => {
exec(`yarn eslint --fix ${path}`, () => {
res();
});
});
}
const target = [
"isNull",
"toPix",
"isKey",
"isObject",
"map",
"extend",
"isFunction",
"isEmptyArray",
"isArray",
"Controller",
"createWidget",
"Events",
"emptyFn",
"nextTick",
"bind",
"i18nText",
"isNotNull",
"isString",
"isNumber",
"isEmpty",
"isEmptyString",
"any",
"deepContains",
"isNotEmptyString",
"each",
"contains",
"remove",
"createItems",
"makeArrayByArray",
"VerticalAlign",
"pushDistinct",
"endWith",
"transformItems",
"print",
"Tree",
"Func",
"Selection",
];
// 加载模块
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();
const result = /BI\.(.*?)\s=\sBI\.inherit\(/.exec(sourceCode);
if (!result) {
console.log(`已经es6过,替换 xtype => ${srcName}`);
// 处理 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
_.forEach(importMap, (depts, module) => {
depts.forEach(dept => {
if (!G[module]) {
G[module] = {};
}
G[module][dept] = true;
});
});
// 合并 core
const crossPackages = fs.readdirSync("src");
_.forEach(G, (depts, module) => {
crossPackages.forEach(crosspackage => {
if (module.indexOf(crosspackage) >= 0) {
if (!G[`@/${crosspackage}`]) {
G[`@/${crosspackage}`] = {};
}
Object.assign(G[`@/${crosspackage}`], depts);
}
});
});
const tmpG = {};
_.forEach(G, (depts, module)=> {
const flag = _.some(crossPackages, crosspackage => module.indexOf(crosspackage) >= 0 && !module.startsWith("@"))
if (!flag) {
tmpG[module] = depts;
}
});
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`;
});
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}
@shortcut()
export class ${clzName} extends ${superName} {
\tstatic 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(() => {
const content = fs.readFileSync("src/core/2.base.js").toString();
let result = content.match(/export function (.*?)\(/g);
target.push(
...result.map(el =>
el.replace("export function ", "").replace("(", "")
)
);
result = content.match(/export const (.*?) =/g);
target.push(
...result.map(el => el.replace("export const ", "").replace(" =", ""))
);
traverse(srcName);
});