先来看下安装插件后的统计结果显示
分别统计了整个项目src目录下路径结构,并将其树形化显示;项目每个文件的基本信息:文件名、文件路径、文件内容行数、文件大小和最后修改时间;项目UI匹配组件的总计使用次数,每个文件匹配组件的使用次数分布。
目前支持.vue和.js文件统计。插件参数
md
| 参数 | 说明 | 类型 | 默认值 |
| --- | --- | --- | --- |
| regex | 正则表达式,用于匹配需要统计的组件 | RegExp | /<(el-[a-z-]+)/g |
| fileTypes | 要统计的文件类型 | String(vue/all) | vue |
插件使用,先安装
css
npm install statistics-webpack-plugin --save-dev
之后配置webpack.config.js或者vue.config.js。具体详情查看:github.com/zhensg123/s... 欢迎start。
接下来介绍一下插件。
为什么搞这个
开发这件事就如同很多事一样,一旦这件事复杂度超过一定程度之后,可能最初这件事的一些状况就被遗忘了,除非刻意回忆。
随着开发项目越来越复杂,开发时间越来越长,再加上不怎么好的开发习惯,回头看整个项目,发现总有一些不合理的情况存在。
而这个插件就可以发现这其中的不合理。比如某个文件行数过长,某个文件过大,文件起名不合理等。此乃此插件的意义之一。
意义之二,比较全面的认识所开发项目,通过查看某种组件的分布以及使用情况,可以判断出项目属于不同的业务类型。比如后台管理平台表单很多,比如to c移动端平台几乎没有表单组件等。
意义之三,了解自己的开发风格。
具体功能实现介绍
webpack插件
webpack插件实际就一个类,一个必须带有apply方法的类。apply
方法是插件的主要入口点。这个方法接收一个参数,通常被命名为compiler
。上面有一系列生命钩子函数。
compiler
它扩展自 Tapable
类,设置了一系列事件钩子和各种配置参数,并定义了 webpack 诸如启动编译、观测文件变动、将编译结果文件写入本地等一系列核心方法。
一些事件钩子介绍
entryOption
: 在读取配置的entry
选项后触发。afterPlugins
: 在设置完初始插件后触发。compile
: 在一个新的编译创建后,但在编译开始之前触发。compilation
: 在创建compilation
对象时触发。make
: 在编译阶段开始时触发。afterCompile
: 在编译阶段完成后触发。emit
: 在生成资源到output
目录之前触发。afterEmit
: 在生成资源到output
目录之后触发。done
: 在编译完成时触发。failed
: 在编译失败时触发。
其中compilation
对象代表了一次单独的版本构建。当运行webpack开发环境中间件时,每当检测到一个文件变化,一次新的编译将被创建,从而生成一组新的编译资源。其上也有很多钩子
-
normalModuleLoader
:模块被加载时触发 -
buildModule
: 在模块构建开始时触发。 -
finishModules
: 在所有模块构建完成时触发。 -
seal
: 在封装编译内容前触发。 -
optimize
: 在编译优化阶段触发。
........................
还有很多就不列举了。这里主要使用normalModuleLoader
钩子,该钩子在webpack4上是在compilation
对象上的,后来webpack5变换到NormalModule.getCompilationHooks(compilation).loader
上。
就在normalModuleLoader
钩子里面统计信息。
统计信息
本插件统计了四个指标
- 项目src目录下路径结构
- 每个文件的文件信息:文件名、文件内容行数、文件路径、文件大小和最后修改时间
- 项目当中组件的使用总数量
- 每个文件当中组件的使用数量分布
项目src目录下路径结构
这个指标相对比较简单
js
function getDirectoryTree(startPath) {
let result = { name: path.basename(startPath), children: [] };
let files = fs.readdirSync(startPath);
files.forEach(file => {
let filePath = path.join(startPath, file);
let stats = fs.statSync(filePath);
if (stats.isDirectory()) {
result.children.push(getDirectoryTree(filePath));
} else {
result.children.push({ name: file });
}
});
return result
}
function waitSrcTree(srcpath){
return new Promise((resolve) => {
const srcTree = getDirectoryTree(srcpath)
resolve(srcTree)
})
}
getDirectoryTree
入参是开始路径,之后递归遍历就可以了。
每个文件的文件信息:文件名、文件路径、文件内容行数、文件大小和最后修改时间
文件信息,使用path.basename
拿到文件名称,使用fs.statSync
拿到其他指标
js
const path = require('path')
const fs = require('fs')
const {transferTime} = require('../util')
module.exports = function statisticFileName(module) {
const data = []
const fileName = path.basename(module.userRequest)
const relativePath = path.relative(process.cwd(), module.userRequest);
const source = fs.readFileSync(module.userRequest, 'utf-8');
// 记录文件名
let stats = fs.statSync(module.userRequest);
data.push({
name: fileName,
path: relativePath,
lineCount: (source && source.split('\n')) ? source.split('\n').length : 0,
size: `${(stats.size/1024).toFixed(2)}KB`,
mtime: transferTime(stats.mtime)
});
return data
}
文件内容行数有些不同,需要读取文件文件内容之后使用split('\n')
获取。
项目当中组件的使用总数量
在normalModuleLoader
触发,也就是模块被加载时,可以获取到文件在电脑上的绝对路径,之后使用nodejs的fs
读取文件的内容,如果是.vue文件,再通过parse
js
const { parse } = require('@vue/compiler-sfc');
从文件内容中解析出descriptor.template
和descriptor.template.content
,之后通过输入参数regex
正则表达式匹配内容,统计完毕后返回
js
exports.statisticVueComponentUsage = function (statswp, module) {
const data = {}
const source = fs.readFileSync(module.resource, 'utf-8');
// 尝试使用@vue/compiler-sfc解析.vue文件
const { descriptor } = parse(source);
let templateContent = descriptor && descriptor.template && descriptor.template.content;
let match;
while ((match = statswp.options.regex.exec(templateContent)) !== null) {
const componentName = match[1];
data[componentName] = (data[componentName] || 0) + 1;
}
return data
}
函数外部,是一个挂载在插件类上的全局变量,每次模块加载都会将匹配的数据,赋给全局统计对象。例如统计组件的使用总数量,统计对象componentUsage
js
const initStatsMetric = function () {
return {
componentUsage: {}, // 统计UI 组件使用情况
fileComponentUsage: {},// 统计每个文件组件使用情况
srcTree: [], // 统计src路径树
fileInfo: [] //统计文件信息
}
}
class StatisticsWebpackPlugin {
constructor(options) {
const defaultOptions = {
regex: /<(el-[a-z-]+)/g,
fileTypes: 'vue'
}
this.options = Object.assign({}, defaultOptions, options)
this.stats = initStatsMetric()
this.id = 0
}
..................
}
具体的统计方法,其下的statswp
为统计插件类
js
exports.mapVueFiles = function (statswp, module) {
const {componentUsage, fileComponentUsage} = statswp.stats
// 每次模块加载都被合并到其上
statswp.stats.componentUsage = {
...componentUsage,
...statisticVueComponentUsage(statswp, module)
}
statswp.stats.fileComponentUsage = {
...fileComponentUsage,
...statisticVueFileComponentUsage(statswp, module)
}
collectFileInfo(statswp, module)
}
以上是统计数据的基本逻辑,如果是js文件则不需要解析器,直接读取到数据统计即可。
每个文件当中组件的使用数量分布
统计这一项,有点麻烦,考虑到项目中文件可能重名,所以需要使用文件的相对路径。之后再进行统计
js
exports.statisticVueFileComponentUsage = function (statswp, module) {
const data = {}
const source = fs.readFileSync(module.userRequest, 'utf-8');
// 尝试使用@vue/compiler-sfc解析.vue文件
const { descriptor } = parse(source);
let templateContent = descriptor && descriptor.template && descriptor.template.content;
// 相对路径
const relativePath = path.relative(process.cwd(), module.userRequest);
if (!data[relativePath]) {
data[relativePath] = {};
}
let match;
while ((match = statswp.options.regex.exec(templateContent)) !== null) {
const componentName = match[1];
data[relativePath][componentName] = (data[relativePath][componentName] || 0) + 1;
}
return data
}
启动服务
启动服务是启动一个server,因为开发环境本身就是nodejs环境,所以使用nodejs启动一个服务,这里使用了express,规定了视图地址和静态资源托管地址。同时对端口错了校验,如果被使用了,使用新的端口,一直到找到可以用的为止
js
const path = require('path')
const http = require('http')
const ejs = require('ejs');
const express = require('express')
const open = require('open');
let port = 30000
module.exports = function ({stats}) {
// 启动一个服务器来显示统计结果
const app = express()
app.set('view engine', 'ejs')
app.set('views', path.resolve(__dirname, '../views')) // 设置视图目录
app.use(express.static(path.resolve(__dirname, '../public'), {
setHeaders: function(res, path, stat) {
if (path.endsWith('.css')) {
res.set('Content-Type', 'text/css');
}
}
}));
app.get('/', (req, res) => {
res.render('index', { ...stats });
});
const server = http.createServer(app)
server.listen(port)
server.on('error', (error) => {
if (error.code === 'EADDRINUSE') {
console.log(`Port ${port} is in use, trying another one...`)
port++
server.close()
server.listen(port)
} else {
throw error
}
})
server.on('listening', () => {
console.log(`Server is running at http://localhost:${port}`)
open(`http://localhost:${port}`)
})
}
open
可以直接在浏览器打开链接。查看统计结果。
其他&总结
还做了一些工作,插件只在每次启动项目时候启动,每次有新的代码,虽然插件会运行,但不会重新统计相关指标。
插件目前可以支持webpack4和webpack5。
总结:一个可以透视开发环境项目的webpack插件,总计指标以及目前进展
- 项目src目录下路径结构
- 每个文件的文件信息:文件名、文件内容行数、文件路径、文件大小和最后修改时间
- 项目当中组件的使用总数量
- 每个文件当中组件的使用数量分布
- 插件只在每次启动项目时候启动
- 插件目前可以支持webpack4和webpack5
- 目前只支持.vue和.js文件统计
- 通过修改参数RegExp,匹配想要统计的组件,默认是element-ui组件
github地址:github.com/zhensg123/s...
(本文完)