描述 grunt 插件标准目录结构与 tasks、lib 职责
解读
国内面试官问这道题,不是让你背文件夹名,而是验证三件事:
- 你是否真的写过并发布过 Grunt 插件(npm 包);
- 你是否理解 Grunt“约定优于配置”的设计哲学;
- 你是否能把“任务注册”与“核心逻辑解耦”,方便单元测试与持续集成。
答不到“为什么这样分”,只会罗列目录,会被直接判为“用过但没深入”。
知识点
- npm 包规范与Grunt 插件命名公约(grunt-contrib-xxx 或 grunt-xxx)。
- Grunt 插件入口只能是
tasks/目录下的.js文件,通过grunt.loadTasks自动加载。 - tasks/ 只放“任务壳”:注册 task、解析 options、调用 lib;零业务逻辑。
- lib/ 放“纯函数”或“Class”,100% 脱离 grunt,可被 jest/mocha 直接 require。
- test/ 必须覆盖 lib,** Istanbul 覆盖率 ≥ 80%** 是国内大厂 CI 门禁。
- Gruntfile.js 只在插件自身用作自测脚手架,不会发布到 npm。
- README 中文示例与CHANGELOG.md是国内私有 Nexus/Verdaccio 仓库的强制合规项。
答案
一个通过 npm publish 且被 cnpm 源正确同步的 Grunt 插件,目录结构如下:
grunt-xxx/
├── package.json // 必须声明 "peerDependencies": {"grunt": ">=1.0.0"}
├── README.md // 中文快速开始、options 表格、常见错误
├── CHANGELOG.md // 符合国内审计要求
├── LICENSE // 国内大厂只看 MIT 或 Apache-2.0
├── Gruntfile.js // 插件作者自测用,**不会被打包**
├── tasks/
│ └── xxx.js // **唯一入口**,只做三件事:
│ // 1. grunt.registerMultiTask('xxx', description, function() {})
│ // 2. 合并 this.options() 与 this.data
│ // 3. 调用 lib/xxx.js 并回传 done()
├── lib/
│ └── xxx.js // **纯逻辑**,无 grunt 依赖,**可独立单元测试**
│ // 暴露一个 run(files, options) 返回 Promise
├── test/
│ ├── lib.test.js // 只测 lib,**mock 掉 grunt**
│ ├── fixtures/ // 输入输出样例,**必须包含中文路径样本**
│ └── expected/
└── .github/workflows/ci.yml // 国内 GitLab 同理,**Node 14/16/18 矩阵**
职责边界:
- tasks/:负责“怎么跑”,包括校验 grunt 版本、合并 options、回写 grunt.log。
- lib/:负责“跑什么”,包括读文件、AST 处理、缓存优化、sourcemap 生成,完全无 console。
拓展思考
- 微前端场景:把 lib 再拆成 @your-org/grunt-xxx-core,同时供 gulp/webpack 复用,实现“一次核心,多构建器适配”,国内阿里、字节已落地。
- 性能优化:在 lib 里实现 mmap 方式读大文件 + worker_threads 压缩图片,可把 10 s 任务降到 2 s,CI 并行度提升 5 倍。
- 合规审计:在 tasks 里加入 snyk 漏洞扫描钩子,不符合等保 2.0 的直接中断构建,这是国内金融客户的强制要求。