一个可以透视项目文件和代码的webpack插件

先来看下安装插件后的统计结果显示

分别统计了整个项目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 诸如启动编译、观测文件变动、将编译结果文件写入本地等一系列核心方法。

一些事件钩子介绍

  1. entryOption: 在读取配置的entry选项后触发。
  2. afterPlugins: 在设置完初始插件后触发。
  3. compile: 在一个新的编译创建后,但在编译开始之前触发。
  4. compilation: 在创建compilation对象时触发。
  5. make: 在编译阶段开始时触发。
  6. afterCompile: 在编译阶段完成后触发。
  7. emit: 在生成资源到output目录之前触发。
  8. afterEmit: 在生成资源到output目录之后触发。
  9. done: 在编译完成时触发。
  10. failed: 在编译失败时触发。

其中compilation对象代表了一次单独的版本构建。当运行webpack开发环境中间件时,每当检测到一个文件变化,一次新的编译将被创建,从而生成一组新的编译资源。其上也有很多钩子

  1. normalModuleLoader:模块被加载时触发

  2. buildModule: 在模块构建开始时触发。

  3. finishModules: 在所有模块构建完成时触发。

  4. seal: 在封装编译内容前触发。

  5. optimize: 在编译优化阶段触发。

........................

还有很多就不列举了。这里主要使用normalModuleLoader钩子,该钩子在webpack4上是在compilation对象上的,后来webpack5变换到NormalModule.getCompilationHooks(compilation).loader上。

就在normalModuleLoader钩子里面统计信息。

统计信息

本插件统计了四个指标

  1. 项目src目录下路径结构
  2. 每个文件的文件信息:文件名、文件内容行数、文件路径、文件大小和最后修改时间
  3. 项目当中组件的使用总数量
  4. 每个文件当中组件的使用数量分布

项目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.templatedescriptor.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插件,总计指标以及目前进展

  1. 项目src目录下路径结构
  2. 每个文件的文件信息:文件名、文件内容行数、文件路径、文件大小和最后修改时间
  3. 项目当中组件的使用总数量
  4. 每个文件当中组件的使用数量分布
  5. 插件只在每次启动项目时候启动
  6. 插件目前可以支持webpack4和webpack5
  7. 目前只支持.vue和.js文件统计
  8. 通过修改参数RegExp,匹配想要统计的组件,默认是element-ui组件

github地址:github.com/zhensg123/s...

(本文完)

相关推荐
GISer_Jing2 小时前
前端面试通关:Cesium+Three+React优化+TypeScript实战+ECharts性能方案
前端·react.js·面试
落霞的思绪3 小时前
CSS复习
前端·css
咖啡の猫5 小时前
Shell脚本-for循环应用案例
前端·chrome
百万蹄蹄向前冲7 小时前
Trae分析Phaser.js游戏《洋葱头捡星星》
前端·游戏开发·trae
朝阳5818 小时前
在浏览器端使用 xml2js 遇到的报错及解决方法
前端
GIS之路8 小时前
GeoTools 读取影像元数据
前端
ssshooter8 小时前
VSCode 自带的 TS 版本可能跟项目TS 版本不一样
前端·面试·typescript
你的人类朋友8 小时前
【Node.js】什么是Node.js
javascript·后端·node.js
Jerry9 小时前
Jetpack Compose 中的状态
前端
dae bal10 小时前
关于RSA和AES加密
前端·vue.js