const fs = require("fs");
const path = require("path");
const { exec } = require("child_process");
const axios = require("axios");
const { default: routerConfig } = require("../config/router.config");
const pluginName = "DependencesAnalysisPlugin";
const ignoreDependenciesArr = Object.keys(
require("../package.json").dependencies
);
const analysisBreak = (request, issuer) => {
return (
(issuer && issuer.includes("node_modules")) ||
(request && request.includes("node_modules"))
);
};
const getRouterInfo = (config) => {
if (!config.component && (!config.routes || !config.routes.length)) {
return null;
}
const pagePath = path.resolve(__dirname, "../src/pages/");
const proRoot = path.resolve(__dirname, "../");
let children = undefined;
if (config.routes && config.routes.length) {
children = config.routes.map(getRouterInfo).filter(Boolean);
}
return {
route: config.path,
path: config.component
? path.relative(
proRoot,
require.resolve(path.resolve(pagePath, config.component))
)
: undefined,
children,
};
};
class DependencyTree {
constructor() {
this.uniqNodes = [];
}
/**
* 遍历树找到对应的结点列表
* @param {*} issuer
*/
traverseTree(issuer) {
const nodes = this.uniqNodes.filter((n) => n.name === issuer);
return nodes;
}
/**
* 为结点列表添加子节点
* @param {*} nodes
* @param {*} request
*/
addChildNode(nodes, request) {
let uniqNode = this.uniqNodes.find((n) => n.name === request);
if (!uniqNode) {
uniqNode = {
name: request,
};
this.uniqNodes.push(uniqNode);
}
nodes.forEach((node) => {
if (!node.children) {
node.children = [uniqNode];
} else {
if (!node.children.find((n) => n.name === request)) {
node.children.push(uniqNode);
}
}
});
}
/**
* 增加一个依赖信息
* @param {*} issuer
* @param {*} request
*/
addDependency(issuer, request) {
if (!this.root) {
const firstChildNode = {
name: request,
};
const rootNode = {
name: issuer,
children: [firstChildNode],
};
this.root = rootNode;
this.uniqNodes.push(rootNode, firstChildNode);
return;
}
const nodes = this.traverseTree(issuer);
this.addChildNode(nodes, request);
}
/**
* 遍历树,将同一条链路上的重复出现的结点删除,第一次出现的保留
*/
clearTree(root, nodes) {
if (root.changed) {
nodes.forEach((n) => {
n.changed = true;
});
}
if (!root.children) {
return;
}
const indexes = root.children
.map((child) => nodes.find((n) => n.name === child.name))
.reduce((iter, cur, index) => iter.concat(cur ? index : []), []);
root.children = root.children.reduce((iter, cur, index) => {
if (indexes.includes(index)) {
return iter;
}
return iter.concat(cur);
}, []);
root.children.forEach((child) => {
this.clearTree(child, nodes.concat(root));
});
}
getFiles(filePath) {
const sum = [];
const files = fs.readdirSync(filePath);
for (const file of files) {
const stat = fs.statSync(path.resolve(filePath, file));
if (stat.isFile()) {
sum.push(path.resolve(filePath, file));
} else if (stat.isDirectory()) {
sum.push(...this.getFiles(path.resolve(filePath, file)));
}
}
return sum;
}
unusedFiles() {
const root = path.resolve(__dirname, "../src/");
const proRoot = path.resolve(__dirname, "../");
const proFiles = this.getFiles(root).map((filePath) =>
path.relative(proRoot, filePath)
);
const unused = proFiles.filter(
(filePath) => !this.uniqNodes.find((node) => node.name === filePath)
);
const filePath = `unused.json`;
fs.writeFileSync(filePath, JSON.stringify(unused, null, 2));
}
gitDiffFiles(commitHash, branch) {
return axios
.get(
`xxx` // 获取此次打包的信息
)
.then((res) => res && res.data)
.then((res) => {
if (!res || !res.results || !res.results.length) {
return Promise.reject("当前分支无上线版本,无法进行依赖分析");
}
const index = res.results.findIndex(
(r) => r.base_commit_hash === commitHash
);
return res.results[
index === -1 || index >= res.results.length - 1 ? 0 : index + 1
].base_commit_hash;
})
.then((lastHash) => {
console.log("Diff hash from: ", lastHash, " to: ", commitHash);
return axios.get(
`xxx` // get git diff files from gitlab
);
})
.then((res) => res.data)
.then((res) => res.diffs.map((diff) => diff.new_path));
}
generate(commitHash, branch) {
const router = getRouterInfo(routerConfig[0]);
return this.gitDiffFiles(commitHash, branch)
.then((filesTemp) => {
const files = filesTemp.filter((p) => p.match("^src/"));
console.log("Diff files: ", files);
return files;
})
.then((files) => {
this.uniqNodes.forEach((node) => {
if (files.includes(node.name)) {
node.changed = true;
}
});
this.clearTree(this.root, []);
return this.root;
})
.then((data) =>
axios({
url: "xxx", // upload result
method: "POST",
data: { body: data, commitHash, router, branch },
headers: { "Content-Type": "application/json" },
})
)
.then((res) => res.data)
.then((res) => {
if (res.code === 0) {
console.log("依赖分析构建成功!");
} else {
console.error("依赖分析构建失败:", res.message);
}
})
.catch((err) => {
console.error("依赖分析构建失败:", err);
});
}
}
const tree = new DependencyTree();
class DependencesAnalysisPlugin {
constructor(options = {}) {
this.options = options;
}
afterResolve(result, callback) {
const { resourceResolveData } = result;
const {
context: { issuer },
path,
descriptionFileRoot,
} = resourceResolveData;
if (!analysisBreak(path, issuer)) {
tree.addDependency(
issuer && issuer.replace(new RegExp(`^${descriptionFileRoot}/`), ""),
path.replace(new RegExp(`^${descriptionFileRoot}/`), "")
);
}
callback(null, result);
}
apply(compiler) {
compiler.hooks.normalModuleFactory.tap(pluginName, (nmf) => {
nmf.hooks.afterResolve.tapAsync(pluginName, this.afterResolve);
});
compiler.hooks.done.tap(pluginName, (
stats /* stats is passed as an argument when done hook is tapped. */
) => {
tree.generate(this.options.commitId, this.options.branch);
});
}
}
module.exports = DependencesAnalysisPlugin;