前言

为什么要生成一个统一的入口文件,其实也是为了简化引入,在Nestjs中我们会根据业务产生很多的DTO和Entity文件,还有一些公共的封装,比如封装的拦截器、过滤器、管道等;这些东西为了方便管理,我们都会存放在不同的目录下。

比如我个人常用的:common/guards

这是一个自定义守卫的目录,但是不同的守卫它会有自己的一些文件,这是我就会做二次划分:

bash
复制代码
common/guards/jwt-auth

common/guards/content-type

common/guards/index.ts

我会将不同的守卫单独再用一个文件夹存放,然后在同级的情况下使用一个index.ts文件来做统一的入口,这样引入就可以这样:

typescript
复制代码
import { ContentType } from "@/common/guards"; 

不管引入多少个守卫,都只需要从这一处引入即可,这样简洁更加有利于维护。

但是,维护这个index文件有时候会感觉真麻烦,因为每次都是重复的逻辑操作:

typescript
复制代码
export * from "./xxxx.ts"

所以我就在想,有没有自动化的插件可以使用,我看了下element-ui的处理,它是自己写了个js脚本处理的,定制化太高了,搜了好久发现了一个插件:barrelsby

这个插件可以实现自动生成index入口文件,但是使用上有些蛋疼。

教程

 

基本使用

先安装依赖:

bash
复制代码
pnpm i barrelsby

安装完毕后我们在项目根目录创建一个:barrelsby.json文件。

内容如下:

json
复制代码
{
  "directory": [
    "./src/common/guards"
  ],
  "exclude": [
    "node_modules",
    "test",
    ".spec.ts",
    "types.ts"
  ],
  "include": [
    ".ts"
  ],
  "location": "top",
  "structure": "flat",
  "noHeader": true
}

首先我会通过exclude排除一些不要引入的目录或者文件,比如types.ts,因为为了方便,我都是会将类型在对应的功能中引入并导出,不需要在单独引入types.ts文件。

include表示插件需要扫描.ts的文件。

其他的就自己看文档吧,照抄就行了。

directory表示引入生成桶文件的目录,为什么说它蛋疼呢,蛋疼就在这,你必须一个个填入对应的目录,它不会说自动扫描,或者配置一个正则来匹配,你必须手动填,如果你觉得这样也不错,那也不是不行,手动填就是了。

配置命令

在package.json中配置命令:

json
复制代码
{
  "scripts": {
    "barrelsby": "barrelsby --config barrelsby.json --delete"
  }
}

我们运行这个命令就行了:

nginx
复制代码
pnpm run barrelsby

此时他就会在./src/common/guards目录下生成index.ts文件。

一键生成barrelsby.json

每次新增新的目录就要改动barrelsby.json文件我感觉有点麻烦,为此,我创建了一个脚本文件来一键生成这个json文件。

创建目录:scripts/generate-barrelsby-json

在里面创建文件:template.ts

typescript
复制代码
// template.ts

interface Options {
    directory: string[];
    include: string[];
    exclude: string[];
}

export function template(options: Options) {
    return JSON.stringify(
        {
            directory: options.directory,
            exclude: options.exclude,
            include: options.include,
            location: "top",
            structure: "flat",
            noHeader: true
        },
        null,
        2
    );
}

在创建:index.ts

typescript
复制代码
// index.ts

import { readdirSync, statSync, writeFileSync } from "fs";
import { dirname, join } from "path";
import { template } from "./template";

/** 项目目录 */
const baseDir = join(__dirname, "../../src");
/** 过滤 */
const exclude = ["node_modules", "test", ".spec.ts", "types.ts"];
/** 包含 */
const include = [".ts"];
/** json文件保存路径 */
const jsonFilePath = join(__dirname, "../../");
/** json文件名 */
const jsonFileName = "barrelsby.json";

function main() {
    /** 需要获取子级目录的目录数组 */
    const directoryList: string[] = [];
    directoryList.push(...getModulesChildDirs());
    directoryList.push(...getCommonChildDirs());
    // 通过模板生成json文件
    const jsonContent = template({
        directory: directoryList,
        exclude: exclude,
        include: include
    });
    // 写入
    writeFileSync(join(jsonFilePath, jsonFileName), jsonContent, "utf-8");
}
main();

/** 获取modules下的子级目录 */
function getModulesChildDirs() {
    const modulesDir = join(baseDir, "modules");
    return getParentDirs(modulesDir, [".dto.ts", ".entity.ts"]);
}

/** 获取common下的子级目录 */
function getCommonChildDirs() {
    const commonDir = join(baseDir, "common");
    return getSubdirectoriesFromArray([commonDir]);
}

/** 获取指定条件的父级目录 */
function getParentDirs(baseDir: string, extensions: string[]) {
    const parentDirs = new Set<string>();

    function findFilesInDir(currentDir) {
        const files = readdirSync(currentDir);

        files.forEach((file) => {
            const filePath = join(currentDir, file);
            const stat = statSync(filePath);

            if (stat.isDirectory()) {
                findFilesInDir(filePath); // 递归进入子目录
            } else {
                // 检查文件扩展名是否在提供的数组中
                for (const ext of extensions) {
                    if (file.endsWith(ext)) {
                        parentDirs.add(dirname(filePath)); // 存储父级目录
                        break; // 找到一个匹配的扩展名后跳出循环
                    }
                }
            }
        });
    }

    findFilesInDir(baseDir);

    return Array.from(parentDirs); // 返回父级目录集合
}

// 获取单个目录的子级目录
function getSubdirectories(dir: string) {
    try {
        const files = readdirSync(dir, { withFileTypes: true });
        // 过滤出目录
        const subDirs = files.filter((file) => file.isDirectory()).map((file) => join(dir, file.name));
        return subDirs;
    } catch (err) {
        console.error(`Error reading directory ${dir}:`, err);
        return []; // 如果发生错误,返回空数组
    }
}

// 获取多个目录的子级目录(同步)
function getSubdirectoriesFromArray(dirs: string[]) {
    const dirList: string[] = [];
    for (const dir of dirs) {
        const subDirs = getSubdirectories(dir);
        dirList.push(...subDirs);
    }
    return dirList;
}

我做了两个目录扫描,方法为:getModulesChildDirsgetCommonChildDirs

  • getModulesChildDirs用户获取src/modules中每个模块的dto和entity目录。
  • getCommonChildDirs用于获取src/common下的每个子目录。

然后目录有了,通过writeFileSync生成json文件就行了。

然后我们调用这个脚本就可以了,为了方便,也写一个命令在package.json中:

json
复制代码
{
  "scripts": {
    "generate-barrelsby-json": "ts-node ./scripts/generate-barrelsby-json/index.ts",
    "barrelsby": "barrelsby --config barrelsby.json --delete"
  }
}

每次需要生成就先调用generate-barrelsby-json再调用barrelsby就行了。

简化命令

每次运行两个命令有点麻烦,所以我们把他们简化成一个命令,我们可以安装一下依赖:npm-run-all2

bash
复制代码
pnpm i npm-run-all2

然后修改命令:

json
复制代码
{
  "scripts": {
    "barrel": "run-s generate-barrelsby-json barrelsby",
    "generate-barrelsby-json": "ts-node ./scripts/generate-barrelsby-json/index.ts",
    "barrelsby": "barrelsby --config barrelsby.json --delete"
  }
}

这样运行一个脚本barrel命令就行了。

bash
复制代码
pnpm run barrel

总结

支持快速生成入口文件就完成了,可惜的就是没办法和nestjs的watch一起使用,我更希望能够在dev模式下,当我们创建了目录和对应的文件,index.ts就自动生成了,但是好像做不到,所以这也算是遗憾吧,但总好过自己手搓一个个index文件吧。

声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。