前端项目文件瘦身

416 阅读3分钟

背景

在项目开发中,经常会遇到通过搜索关键词字快速定位对应代码文件,结果往往会出现多个对应文件,其中就充斥着部分废弃无依赖的文件影响开发者判断,降低了开发效率。

人为在上千个依赖文件中找出这些废弃代码文件是不现实的,这时我们就可以借助工具自动化处理。

先睹为快

使用node.js、webpack能力统计文件并自动删除无依赖文件,同时生成统计结果文件。

设计思路

所有文件:开发者在src目录下创建的文件

依赖文件:项目打包后实际用到的文件

无用文件 = 所有文件 - 依赖文件

如何获取所有文件

我们可以使用node.js的能力,通过node递归读取项目入口目录下所有的文件。

如何获取依赖文件

webpack插件允许用户直接介入编译过程,只需开发一个自定义插件,在合适的编译阶段执行就可以了。

在webpack编译完成后,即将退出的时候通过compilerdone钩子拿到最终打包文件的信息。

如何获取无用文件

所有文件减去依赖文件就拿到了无用文件并生成一个统计结果文件。

具体代码实现

dependencyAnalysisPlugin.js

const fs = require('fs');
const path = require('path');

class DependencyAnalysisPlugin {
  constructor(options = {}) {
    this.options = options;
    this.entry = options.entry || 'src'; // 入口
    this.include = options.include || ''; // 包含哪些文件'.vue|.js'
    this.exclude = options.exclude || ''; // 排除哪些文件夹 ['src/assets', 'src/views']
    this.isDelete = options.isDelete || false; // 是否主动删除文件
    this.originFile = []; // node读取的源文件目录 处理过include及exclude 后的数据 最全的数据
    this.dependenciesFile = []; // webpack依赖数据 处理过include及exclude 后的数据 依赖数据
    this.noUseFile = []; // 最全的数据 减去 依赖数据  可删除的数据
    this.init(); // 初始化
  }

  apply(compiler) {
    compiler.hooks.done.tapAsync('DependencyAnalysisPlugin', (factory, cb) => {
      // 获取依赖资产
      let curFile = [];
      factory.compilation.fileDependencies.forEach((item) => {
        curFile.push(item);
      });
      // 排除node_modules 与 确认入口文件
      curFile = curFile.filter((item) => {
        if (item.indexOf('node_modules') == -1 && item.indexOf(this.resolve(this.entry)) > -1) {
          return item;
        }
      });
      // 处理include规则
      const includeFile = this.includeHandle(curFile);
      // 处理exclude规则
      const excludeFile = this.excludeHandle(includeFile);
      this.dependenciesFile = excludeFile;
      // 从 originFile 及 dependenciesFile 数据中分析出 未被使用的数据
      this.originFile.forEach((item) => {
        if (this.dependenciesFile.findIndex((el) => el == item) == -1) {
          this.noUseFile.push(item);
        }
      });
      // 处理资源 写入文件
      this.writeDirPathHandle();
      cb();
    });
  }

  // 初始化
  init() {
    console.log('[dependency] ##启动依赖分析功能');
    console.log('[dependency] ##是否自动删除文件', this.isDelete);
    // 读取指定node文件
    this.readOriginFile();
  }

  // 转换路径
  resolve(pathname = '') {
    return path.join(path.resolve('./'), pathname);
  }

  // 读取源文件目录
  readOriginFile() {
    const files = this.readFiles(this.entry);
    // 处理include规则
    const includeFile = this.includeHandle(files);
    // 处理exclude规则
    const excludeFile = this.excludeHandle(includeFile);
    this.originFile = excludeFile;
  }

  // 读取指定目录文件
  readFiles(path) {
    let allFile = [];
    const curPath = this.resolve(path);
    const files = fs.readdirSync(curPath);
    for (const file of files) {
      const obj = fs.statSync(this.resolve(`${path}/${file}`));
      if (obj.isDirectory()) {
        allFile = [...allFile, ...this.readFiles(`${path}/${file}`)];
      } else {
        // 排除 .gitkeep 等隐藏文件
        const isHideFile = new RegExp(/^./).test(file);
        // 排除 md文件
        const isMdFile = new RegExp(/.md$/).test(file);
        if (!isHideFile && !isMdFile) {
          allFile.push(this.resolve(`${path}/${file}`));
        }
      }
    }
    return allFile;
  }

  // 处理规则
  includeHandle(list) {
    if (!this.include) {
      return list;
    }
    // 指定类型的文件
    const includeArr = this.include.split('|');
    const filterFile = list.filter((item) => {
      const index = includeArr.findIndex((el) => item.indexOf(el) > -1);
      if (index > -1) {
        return item;
      }
    });
    return filterFile;
  }

  // 处理规则
  excludeHandle(list) {
    if (!this.exclude) {
      return list;
    }
    // 过滤指定文件夹
    const excludeList = [];
    this.exclude.forEach((item) => {
      excludeList.push(this.resolve(item));
    });
    const filterFile = list.filter((item) => {
      const index = excludeList.findIndex((el) => item.indexOf(el) > -1);
      if (index == -1) {
        return item;
      }
    });
    return filterFile;
  }

  // 写入文件
  writeDirPathHandle() {
    let content = `所有文件-length[${this.originFile.length}]、依赖文件-length[${this.dependenciesFile.length}]、无用文件-length[${this.noUseFile.length}]`;
    content += `\r\n##########所有文件-length[${this.originFile.length}]##########\r\n${this.originFile.join('\n')}\r\n`;
    content += `\r\n##########依赖文件-length[${this.dependenciesFile.length}]##########\r\n${this.dependenciesFile.join('\n')}\r\n`;
    content += `\r\n##########无用文件-length[${this.noUseFile.length}]##########\r\n${this.noUseFile.join('\n')}\r\n`;
    fs.writeFile('dependency.txt', content, (err) => {
      if (err) {
        console.error(err);
        return;
      }
      console.log('[dependency] ## 文件已写入dependency.txt');
      // 判断是否执行删除
      if (this.isDelete) {
        this.deleteFileHandle();
      }
    });
  }

  // 删除文件
  deleteFileHandle() {
    this.noUseFile.forEach((item) => {
      fs.unlink(item, (err) => {
        if (err) throw err;
        console.log(`[dependency] ## 已删除文件:${item}`);
      });
    });
  }
}
module.exports = DependencyAnalysisPlugin;

使用步骤

1.项目中新建自定义插件 dependencyAnalysisPlugin.js 文件

2.在webpack配置文件中引入文件 dependencyAnalysisPlugin.js 并配置参数

3.执行代码构建即可

const dependencyAnalysisPlugin = require('../dependencyAnalysisPlugin')
plugins:[
    new dependencyAnalysisPlugin() //isDelete 可配置是否主动删除文件
]

项目构建完成后会生成 dependency.txt 文件,里面记录的文件使用情况,也可通过配置isDelete参数实现自动删除无依赖文件。

Api

属性说明类型可选值默认值
entry入口String'src'
include包含哪些文件类型String'.js.vue.jpg'
exclude排除哪些文件夹Array例:['src/assets', 'src/views']
isDelete是否主动删除无依赖文件booleantrue falsefalse