前言
为什么要生成一个统一的入口文件,其实也是为了简化引入,在Nestjs中我们会根据业务产生很多的DTO和Entity文件,还有一些公共的封装,比如封装的拦截器、过滤器、管道等;这些东西为了方便管理,我们都会存放在不同的目录下。
比如我个人常用的:common/guards
这是一个自定义守卫的目录,但是不同的守卫它会有自己的一些文件,这是我就会做二次划分:
common/guards/jwt-auth
common/guards/content-type
common/guards/index.ts
我会将不同的守卫单独再用一个文件夹存放,然后在同级的情况下使用一个index.ts文件来做统一的入口,这样引入就可以这样:
import { ContentType } from "@/common/guards";
不管引入多少个守卫,都只需要从这一处引入即可,这样简洁更加有利于维护。
但是,维护这个index文件有时候会感觉真麻烦,因为每次都是重复的逻辑操作:
export * from "./xxxx.ts"
所以我就在想,有没有自动化的插件可以使用,我看了下element-ui的处理,它是自己写了个js脚本处理的,定制化太高了,搜了好久发现了一个插件:barrelsby
这个插件可以实现自动生成index入口文件,但是使用上有些蛋疼。
教程
基本使用
先安装依赖:
pnpm i barrelsby
安装完毕后我们在项目根目录创建一个:barrelsby.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中配置命令:
{
"scripts": {
"barrelsby": "barrelsby --config barrelsby.json --delete"
}
}
我们运行这个命令就行了:
pnpm run barrelsby
此时他就会在./src/common/guards
目录下生成index.ts文件。
一键生成barrelsby.json
每次新增新的目录就要改动barrelsby.json
文件我感觉有点麻烦,为此,我创建了一个脚本文件来一键生成这个json文件。
创建目录:scripts/generate-barrelsby-json
在里面创建文件:template.ts
// 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
// 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;
}
我做了两个目录扫描,方法为:getModulesChildDirs
和getCommonChildDirs
。
getModulesChildDirs
用户获取src/modules中每个模块的dto和entity目录。getCommonChildDirs
用于获取src/common下的每个子目录。
然后目录有了,通过writeFileSync
生成json文件就行了。
然后我们调用这个脚本就可以了,为了方便,也写一个命令在package.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
pnpm i npm-run-all2
然后修改命令:
{
"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
命令就行了。
pnpm run barrel
总结
支持快速生成入口文件就完成了,可惜的就是没办法和nestjs的watch一起使用,我更希望能够在dev模式下,当我们创建了目录和对应的文件,index.ts就自动生成了,但是好像做不到,所以这也算是遗憾吧,但总好过自己手搓一个个index文件吧。
评论(0)