webpack-code-dependency-analysis-plugin
Version:
A Webpack Plugin About Code Dependencies Analysis.
161 lines (140 loc) • 4.33 kB
JavaScript
const fs = require('fs');
const http = require('http');
const opener = require('opener');
const path = require('path');
const resolve = require("enhanced-resolve");
let myResolve;
function renderViewer(jsonString) {
return new Promise((resolve) => {
fs.readFile(path.resolve(__dirname, './dependencies.html'), 'utf-8', (err, data) => {
if (err) throw err;
const html = data.replace(/<%=(\w+)%>/g, (match, $1) => jsonString);
resolve(html);
});
});
}
function openBrowser(url, info) {
try {
opener(url);
console.log(info);
} catch (err) {
console.error(`Opener failed to open "${url}":\n${err}`);
}
}
async function startServer(jsonString) {
const port = 8888;
const host = '127.0.0.1';
const isOpenBrowser = true;
const html = await renderViewer(jsonString);
http.createServer((req, res) => {
if (req.method === 'GET' && req.url === '/') {
res.writeHead(200, { 'Content-Type': 'text/html' });
res.end(html);
} else {
res.end('blank page');
}
}).listen(port, host, () => {
const url = `http://${host}:${port}`;
const logInfo = (
`Webpack Source Code Dependencies Analyzer is started at ${(url)}\n`
+ `Use ${('Ctrl+C')} to close it`
);
if (isOpenBrowser) {
openBrowser(url, logInfo);
}
});
}
const transformArrayToTree = (array) => {
if (!array || array?.length === 0) return null;
array.forEach(v => {
(v.children || []).forEach(k => {
const childDep = array.find(l => l.resource === k.resource);
childDep && (k.children = childDep.children);
})
})
return array[0];
};
/**
* 通过source获取真实文件路径
* @param parser
* @param source
*/
function getResource(parser, source){
if(!myResolve){
myResolve = resolve.create.sync(parser.state.options.resolve);
}
return myResolve(parser.state.current.context,source);
}
class WebpackCodeDependenciesAnalysis {
constructor() {
this.pluginName = 'WebpackCodeDependenciesAnalysisPlugin';
// 文件数组
this.files = [];
// 当前编译的文件
this.currentFile = null;
}
apply(compiler) {
compiler.hooks.compilation.tap(this.pluginName, (compilation, { normalModuleFactory }) => {
const collectFile = (parser) => {
const { rawRequest, resource } = parser.state.current;
if (resource !== this.currentFile) {
this.currentFile = resource;
this.files.push({
name: rawRequest,
resource,
children: []
});
}
}
const handler = (parser, options) => {
parser.hooks.importCall.tap(this.pluginName, (expr) => {
// 跳过node_modules
if (parser.state.current.resource.includes('node_modules')) {
return;
}
collectFile(parser);
let ast = {};
const isWebpack5 = "webpack" in compiler;
// webpack@5 has webpack property, webpack@4 don't have the property
if(isWebpack5){
// webpack@5
ast = expr.source;
} else {
//webpack@4
const { arguments: arg } = expr;
ast = arg[0];
}
const { type, value } = ast;
if (type === 'Literal') {
const resource = getResource(parser, value);
this.files[this.files.length - 1].children.push({
name: value,
resource
});
}
})
parser.hooks.import.tap(this.pluginName, (statement, source) => {
if (parser.state.current.resource.includes('node_modules')
) {
return;
}
collectFile(parser);
this.files[this.files.length - 1].children.push({
name: source,
resource: getResource(parser, source)
});
});
}
normalModuleFactory.hooks.parser
.for("javascript/auto")
.tap(this.pluginName, handler);
});
compiler.hooks.make.tap(this.pluginName, (compilation) => {
compilation.hooks.finishModules.tap(this.pluginName, (modules) => {
// const tree = transformArrayToTree(this.files);
startServer(JSON.stringify(this.files));
});
});
}
}
module.exports = WebpackCodeDependenciesAnalysis;