一个可以透视项目文件和代码的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...

(本文完)

相关推荐
蜗牛快跑2139 分钟前
面向对象编程 vs 函数式编程
前端·函数式编程·面向对象编程
Dread_lxy10 分钟前
vue 依赖注入(Provide、Inject )和混入(mixins)
前端·javascript·vue.js
涔溪1 小时前
Ecmascript(ES)标准
前端·elasticsearch·ecmascript
榴莲千丞1 小时前
第8章利用CSS制作导航菜单
前端·css
奔跑草-1 小时前
【前端】深入浅出 - TypeScript 的详细讲解
前端·javascript·react.js·typescript
羡与1 小时前
echarts-gl 3D柱状图配置
前端·javascript·echarts
guokanglun1 小时前
CSS样式实现3D效果
前端·css·3d
咔咔库奇1 小时前
ES6进阶知识一
前端·ecmascript·es6
前端郭德纲2 小时前
浏览器是加载ES6模块的?
javascript·算法
JerryXZR2 小时前
JavaScript核心编程 - 原型链 作用域 与 执行上下文
开发语言·javascript·原型模式