工程化实践:实现模板 CLI 最小闭环
概述
将上节实现的模板 CLI 核心逻辑(index.ts)通过工程化手段打包为可分发的 CLI 工具。核心挑战:TypeScript 源码无法被 Node.js 直接执行,需要使用构建工具(unbuild)进行编译。本节实现从源码到可执行 CLI 的完整闭环。
最小闭环流程
TypeScript 源码 (index.ts)
→ unbuild 构建编译
→ 输出 CJS + ESM + 类型声明
→ package.json 配置 bin 字段
→ npm link 本地测试
→ npm publish 发布
→ npx create-vue-template 使用
text
项目结构
create-vue-template/
├── build.config.ts # unbuild 构建配置
├── package.json # 包配置(含 bin 字段)
├── index.ts # CLI 入口文件
├── templates/ # 模板文件目录
│ ├── base/
│ └── cdn/
└── dist/ # 构建输出
├── index.cjs # CommonJS 格式
├── index.mjs # ESM 格式
└── index.d.ts # 类型声明
text
构建配置
build.config.ts
import { defineBuildConfig } from 'unbuild'
export default defineBuildConfig({
entries: ['./index'],
outDir: 'dist',
declaration: 'compatible', // 生成 .d.ts + .d.mts + .d.cts
rollup: {
emitCJS: true, // 同时输出 CJS 格式
esbuild: {
target: 'node18',
minify: false
}
}
})
typescript
package.json 关键配置
{
"name": "create-vue-template",
"version": "1.0.0",
"type": "module",
"bin": {
"create-vue-template": "./dist/index.mjs"
},
"exports": {
".": {
"import": "./dist/index.mjs",
"require": "./dist/index.cjs"
}
},
"main": "./dist/index.cjs",
"types": "./dist/index.d.ts",
"files": ["dist", "templates"],
"scripts": {
"build": "unbuild",
"dev": "unbuild --stub",
"prepublishOnly": "pnpm build"
},
"devDependencies": {
"unbuild": "^3.6.1"
}
}
json
关键字段说明:
| 字段 | 用途 | 说明 |
|---|---|---|
bin | 注册 CLI 命令 | 安装后可通过 npx create-vue-template 执行 |
exports | 模块导出映射 | 同时支持 ESM 和 CJS 导入 |
files | 发布白名单 | 仅包含 dist 和 templates 目录 |
type | 模块系统 | "module" 表示项目使用 ESM |
CLI 入口文件
// index.ts
#!/usr/bin/env node
import { createTemplate } from './core'
import { parseArgs } from './utils/args'
async function main(): Promise<void> {
const args = parseArgs(process.argv.slice(2))
await createTemplate(args)
}
main().catch((err) => {
console.error('Error:', err.message)
process.exit(1)
})
typescript
开发与测试流程
本地调试
# 方式一:stub 模式(无需每次构建)
pnpm dev # 生成 stub 文件,修改即时生效
# 方式二:link 到全局
pnpm build
npm link # 全局注册命令
create-vue-template # 直接运行测试
# 方式三:直接使用 node 执行
node --import tsx index.ts
bash
构建与验证
# 构建
pnpm build
# 验证输出
ls dist/
# index.cjs index.mjs index.d.ts
# 验证 CLI 执行
node dist/index.mjs
# 验证后取消 link
npm unlink -g create-vue-template
bash
发布准备
# 1. 初始化 git 仓库(首次)
git init
pnpm prepare # 执行 husky 安装(仅需一次)
# 2. 构建并测试
pnpm build
npm link
create-vue-template --help
# 3. 提交代码
git add .
git commit -m "feat: 初始化 CLI 工具"
# 4. 发布到 npm
npm publish --access public
bash
CJS vs ESM 构建对比
| 格式 | 文件扩展名 | 加载方式 | Node.js 支持 |
|---|---|---|---|
| CJS | .cjs | require() | 所有版本 |
| ESM | .mjs | import | v14+(stable v16+) |
unbuild 通过 rollup.emitCJS: true 同时输出两种格式,通过 exports 字段让 Node.js 自动选择合适的版本。
实践要点
- TypeScript 文件不能被 Node.js 直接执行,必须通过 unbuild 等构建工具编译
- 使用
unbuild --stub开发模式可避免每次修改都重新构建,提升开发效率 package.json的bin字段是 CLI 工具的核心配置,决定了命令名和入口文件files字段控制 npm 发布范围,避免不必要的文件被打包- Git 仓库初始化后执行
pnpm prepare安装 git hooks(如 husky),只需执行一次 #!/usr/bin/env nodeshebang 行确保 CLI 文件可直接作为可执行文件运行
↑