- Published on
从0搭建自己的CLI
背景
- 自己开发玩具项目时 会频繁做一些重复的工作(项目初始化、配置),浪费时间
- 需要一些基础功能、如路由、eslint 等
所以有了设计一个快速生成项目模板脚手架的想法,有更多精力去 coding
调研
参考自己最常使用的工具 vite,给用户提供一些可交互的选项,然后生成项目模板
提供了几个基础选项(自己比较常用)
- router
- eslint
- cssinjs
- ...
开发
实现的大致思想是 配置 + 模版
通过用户选择的配置,结合项目中的模板,快速生成项目
用prompts
库命令行交互,获取用户需要的模块
const result = await prompts(
[
// name
{
type: 'text',
name: 'projectName',
message: reset('Project name:'),
initial: defaultProjectName,
},
// modules
{
type: 'multiselect',
name: 'modules',
message: 'Select the required modules',
instructions: false,
choices: [
{ title: 'router', value: 'router' },
{ title: 'eslint', value: 'eslint' },
{ title: 'css in js', value: 'cssinjs' },
],
},
],
{
onCancel: () => {
throw new Error(red('✖') + ' Operation cancelled')
},
}
)
获取其模块对应的依赖,
export type PackageParam = Partial<{
dependencies?: Record<string, string>
devDependencies?: Record<string, string>
scripts: Record<string, string>
}>
const deps: PackageParam = {
eslint: {
devDependencies: {
'vite-plugin-eslint': '^1.8.1',
eslint: '^8.31.0',
'@mmc-cloud/eslint-config': '^1.0.4',
},
scripts: {
fix: 'eslint --ext .js,.jsx,.ts,.tsx --fix --quiet ./',
},
},
}
动态生成一个 package.json
文件, 实现如下
// 获取对应依赖
const getDeps = (deps: string[]) =>
deps.reduce<Omit<PackageParam, 'name'>>((pre, cur) => {
const { dependencies, devDependencies, scripts } = depsCharset[cur] ?? {}
pre.dependencies = { ...pre.dependencies, ...dependencies }
pre.devDependencies = { ...pre.devDependencies, ...devDependencies }
pre.scripts = { ...pre.scripts, ...scripts }
return pre
}, {})
// 生成 package.json
const generatePackage = (param: PackageParam) => {
const { name, dependencies, devDependencies, scripts } = param
const template: Record<string, unknown> = {
name,
private: true,
version: '0.0.0',
type: 'module',
scripts: {
// dev ...
...scripts,
},
dependencies: {
// react ...
...dependencies,
},
devDependencies: {
// ts ...
...devDependencies,
},
}
return template
}
const deps = getDeps(modules as string[])
const packageJson = generatePackage({ name: projectName, ...deps })
这样我们就可以动态生成一个package.json
文件
再通过基础模版 + 用户选择的模版,来生成最终项目
新建一个 template
文件夹,下面存放一些模块模版
- base 基础模版
- eslint
- router
- ...
for (const module of ['base', ...modules]) {
const template = `${templateDir}/${module}`
const files = readdirSync(template)
for (const file of files) {
copy(resolve(template, file), resolve(projectDir, file))
}
}
在把对应模板拷贝到项目目录下
拓展
- deps 依赖表
- template 模块对应依赖
维护以上两个目录,可以实现自定义拓展
启动
在package.json
文件下添加
"bin": {
"create-mmc": "index.js"
},
index.js
最上面一行注释不要删除
#!/usr/bin/env node
import './dist/index.mjs'
再执行npm link
命令
最后可以在命令行执行 create-mmc
总结
还不够完善,后续可以实现更多模板内容。
初步了解了 cli 工具的搭建