🔧 手把手教你开发一个自己的cli工具(一)

前言

🔧 手把手教你开发一个自己的cli工具(二)juejin.cn/post/735947...

这里呢,我准备分享一下如何开发自己的cli工具,以及介绍一下cli工具的具体能做哪些事情

涉及到的技术栈

  • typescript:类型提示方便我们的开发,
  • node.js:需要path和fs模块的基本使用即可
  • rollup.js:非常简易而且轻量级的打包工具📦,但是同时又有很强大的打包能力,我们熟知的vite的📦打包方式就是rollup.js
  • 服务端:如果有些可能要实现打开网页的效果就会涉及到服务端框架,这里有express.js、nest.js等服务端框架
  • npm:这里需要对npm包和package.json文件等有一些了解,毕竟会涉及到发包

初始化

创建工程

创建一个npm工程

sh 复制代码
npm init -y

文件结构

sh 复制代码
├── bin // 🌸 运行目录
│   └── index.js
├── index.ts
├── lib // 🌸 打包后的文件
│   └── main.js
├── src // 🌸 核心代码
├── package-lock.json
├── package.json
└── tsconfig.json

项目架构

这里我们最好采用monorepo的架构,至于lerna、pnpm+monorepo、yarn+workspace大家可以去自行研究,我这里就采用pnpm+monorepo的架构

先在全局安装pnpm

sh 复制代码
npm i pnpm -g
# mac Linux
sudo npm i pnpm -g

在我们的项目的根目录创建一个pnpm-workspace.yaml 这里声明packages和scripts、docs、template-lite文件夹下的所有文件都可以存放我们的项目,也就是一个项目多个子项目,通俗来讲,但凡在这个目录下发现了package.json文件,那么那个目录就会被视作一个子项目进行管理,这样统一进行管理。

sh 复制代码
packages:
  - "packages/**"
  - "scripts/**"
  - "docs/**"
  - "template-lite/**"

假如我们创建了一个 packages/cli的目录,然后终端打开这个目录,输入pnmp init,那么这个cli目录就会被视作一个子项目

安装依赖

会用到的dependencies

  • commander: 生成命令行的一个js工具包
  • inquirer: 同样也是生成命令行工具包,这个是会提供问答的功能
  • chalk: 为我们的命令行提供颜色,美化命令行

会用到的devDependencies

  • rollup: 这里包含rollup的一堆plugins我先省略了,大家安装的时候复制黏贴即可
  • @types/node: 给node类型提示
  • rimraf: 删除文件(I/O)
  • eslint

这里我给大家列出来,大家复制粘贴即可

先打开我们的子项目目录的终端 ,我这里是 packages/cli,然后输入以下脚本 终端

sh 复制代码
pnpm add chalk commander inquirer

pnpm add @rollup/plugin-node-resolve @rollup/plugin-terser @rollup/plugin-commonjs @rollup/plugin-json @rollup/plugin-babel rollup-plugin-esbuild rollup @types/node @types/inquirer --save-dev

下载完之后,你会发现在项目的根目录会多一个pnpm-lock.yaml,我们点进去看一下就会发现,pnpm已经将我们的package/cli视为一个子项目,然后下面列出了该项目下载的依赖和第三方包.

代码规范检查(可选)

sh 复制代码
pnpm add eslint @typescript-eslint/eslint-plugin @typescript-eslint/parser prettier

创建一下 eslint和prettier的config文件,这里直接复制我的就可以啦

.eslintrc

json 复制代码
{
  "root": true,
  "parser": "@typescript-eslint/parser",
  "plugins": [
    "@typescript-eslint"
  ],
  "extends": [
    "eslint:recommended",
    "plugin:@typescript-eslint/eslint-recommended",
    "plugin:@typescript-eslint/recommended"
  ],
  "rules": {
    "no-console": "off"
  },
  // set eslint env
  "env": {
    "node": true
  }
}

.prettierrc.js

js 复制代码
export default {
  // 一行的字符数,如果超过会进行换行,默认为80
  printWidth: 80,
  // 一个tab代表几个空格数,默认为2
  tabWidth: 2,
  // 是否使用tab进行缩进,默认为false,表示用空格进行缩减
  useTabs: false,
  // 字符串是否使用单引号,默认为false,使用双引号
  singleQuote: true,
  // 行位是否使用分号,默认为true
  semi: false,
  // 是否使用尾逗号,有三个可选值"<none|es5|all>"
  trailingComma: 'none',
  // 对象大括号直接是否有空格,默认为true,效果:{ foo: bar }
  bracketSpacing: true
}

然后在package.json添加一下js脚本

json 复制代码
{
"lint": "eslint ./src --ext .ts --fix",
"format": "prettier --write \"./**/*.{ts,js,json,md}\""
}

代码提交规范(可选)

如果想一步到位直接看full-featured-commit标题下的内容

首先初始化一下git仓库

sh 复制代码
git init

这里涉及到的包括:commitlint+husky+cz-customable+commitlint-config-gitmoji

这里要用到cz-customable,但是他的导入导出方式是cjs,因此他的config文件:cz-config.js也是cjs格式,但是我们的项目会用到ts,所以package.json的type为"module",那么这就和配置文件的模式冲突了。

那么有同学问:如果把js文件改成cjs不就行了,但是cz-customable源代码中导入config文件的匹配格式是cz-config.js,会找不到config文件。

那么我们的解决办法是下载full-featured-cz(我自己魔改的让他支持cjs文件),如果有更好的方式还请小伙伴们在评论区教给我,我学习一下嘿嘿。

那让我们下载依赖

sh 复制代码
    pnpm add husky full-featured-cz commitlint commitlint-config-gitmoji --save-dev

创建cz-config.cjs文件

js 复制代码
module.exports = {
  types: [
    {
      value: ':sparkles: feat',
      name: '✨ feat:     新功能'
    },
    {
      value: ':bug: fix',
      name: '🐛 fix:      修复 bug'
    },
    {
      value: ':tada: init',
      name: '🎉 init:     初始化'
    },
    {
      value: ':pencil2: docs',
      name: '✏️  docs:     文档变更'
    },
    {
      value: ':lipstick: style',
      name: '💄 style:    代码样式美化'
    },
    {
      value: ':recycle: refactor',
      name: '♻️  refactor: 重构'
    },
    {
      value: ':zap: perf',
      name: '⚡️ perf:     性能优化'
    },
    {
      value: ':white_check_mark: test',
      name: '✅ test:     测试'
    },
    {
      value: ':rewind: revert',
      name: '⏪️ revert:   回退'
    },
    {
      value: ':package: build',
      name: '📦️ build:    打包'
    },
    {
      value: ':rocket: chore',
      name: '🚀 chore:    构建/工程依赖/工具'
    },
    {
      value: ':construction_worker: ci',
      name: '👷 ci:       CI 相关变更'
    }
  ],
  messages: {
    type: '请选择提交类型(必填)',
    customScope: '请输入文件修改范围(可选)',
    subject: '请简要描述提交(必填)',
    body: '请输入详细描述(可选)',
    breaking: '列出任何 BREAKING CHANGES(可选)',
    footer: '请输入要关闭的 issue(可选)',
    confirmCommit: '确定提交此说明吗?'
  },
  allowCustomScopes: true,
  allowBreakingChanges: [':sparkles: feat', ':bug: fix'],
  subjectLimit: 72
}

创建.commitlintrc.cjs

js 复制代码
module.exports = {
  extends: ['./node_modules/commitlint-config-gitmoji', 'cz'],
  rules: {
    'type-empty': [
      2,
      'never',
      [
        ':art:',
        ':newspaper:',
        ':pencil:',
        ':memo:',
        ':zap:',
        ':fire:',
        ':books:',
        ':bug:',
        ':ambulance:',
        ':penguin:',
        ':apple:',
        ':checkered_flag:',
        ':robot:',
        ':green_ale:',
        ':tractor:',
        ':recycle:',
        ':white_check_mark:',
        ':microscope:',
        ':green_heart:',
        ':lock:',
        ':arrow_up:',
        ':arrow_down:',
        ':fast_forward:',
        ':rewind:',
        ':rotating_light:',
        ':lipstick:',
        ':wheelchair:',
        ':globe_with_meridians:',
        ':construction:',
        ':gem:',
        ':bookmark:',
        ':tada:',
        ':loud_sound:',
        ':mute:',
        ':sparkles:',
        ':speech_balloon:',
        ':bulb:',
        ':construction_worker:',
        ':chart_with_upwards_trend:',
        ':ribbon:',
        ':rocket:',
        ':heavy_minus_sign:',
        ':heavy_plus_sign:',
        ':wrench:',
        ':hankey:',
        ':leaves:',
        ':bank:',
        ':whale:',
        ':twisted_rightwards_arrows:',
        ':pushpin:',
        ':busts_in_silhouette:',
        ':children_crossing:',
        ':iphone:',
        ':clown_face:',
        ':ok_hand:',
        ':boom:',
        ':bento:',
        ':pencil2:',
        ':package:',
        ':alien:',
        ':truck:',
        ':age_facing_up:',
        ':busts_in_silhouette:',
        ':card_file_box:',
        ':loud-sound:',
        ':mute:',
        ':egg:',
        ':see-no-evil:',
        ':camera-flash:',
        ':alembic:',
        ':mag:',
        ':wheel-of-dharma:',
        ':label:'
      ]
    ],
    'subject-empty': [2, 'never']
  }
}

配置husky

sh 复制代码
npx husky install

然后在husky下添加pre-commit文件(也可以在官方生成的_/下加入也可以,不过这样更直观一些)

yaml 复制代码
#!/usr/bin/env sh
  . "$(dirname -- "$0")/_/husky.sh"
  npm run lint && npm run format 

这样我们的配置文件就添加结束了

如果觉得上面的配置浪费时间或者不想去操作,尝试一下🔧full-featured工具包

full-featured是我自己写的cli命令行工具,其实是一个工具包,旗下又一个工具包是full-featured-commit,功能是给项目一键生成代码风格检查和规范化、代码提交信息规范化和美化功能full-featured-commit会自动安装依赖,添加脚本,检查项目环境,添加config文件,从而达到开箱即用的效果,当然你可以选择不用git或者不添加eslint等,是一个可定制方案。

full-featured当然还有更多功能,其目的和核心观念就是给开发者提供便利,节省时间。

github:github.com/liliphoenix...

npm:www.npmjs.com/package/ful...

项目文档(正在补全):liliphoenix.github.io/full-featur...

liliphoenix.github.io/full-featur...

安装

sh 复制代码
npm install full-featured-cli -g

full-featrued -v

给项目添加功能

sh 复制代码
full-featured init --commit

rollup

配置文件

rollup配置其实不算复杂,其实看官方文档就可以自己写出来,我这里给出我的方案和解释,大家可以参考一下

首先创建一个rollup.config.default.js、rollup.config.dev.js和rollup.config.prod.js 这里我们配置了两个环境dev和prod所以我们要先写一个default文件

rollup.config.default.js

js 复制代码
import commonjs from '@rollup/plugin-commonjs'
import resolve from '@rollup/plugin-node-resolve'
import babel from '@rollup/plugin-babel'
import json from '@rollup/plugin-json'
import esbuild from 'rollup-plugin-esbuild'

const plugins = [
// 转换成es5一下来适配低版本浏览器
  babel({ babelHelpers: 'bundled' }),
// 用来插件可以让 Rollup 找到外部模块
  resolve({
    preferBuiltins: true
  }),
  // 识别和处理json文件
  json(),
  // 传唤成commonjs
  commonjs(),
  // 使用esbuild处理esm格式,和vite预处理一个原理
  esbuild()
]

const entries = ['src/index.ts']

export default [
  ...entries.map((input) => ({
    input,
    output: [
      {
        file: input.replace('src/', 'lib/').replace('.ts', '.js'),
        format: 'esm'
      }
    ],
    // 处理循环依赖直接外部引入即可
    external: ['readable-stream', 'chalk', 'semver'],
    plugins
  }))
]

从上往下看,我们配置了几个plugins,然后因为rollup可以配置多入口,所以我们这里把input定义为一个数组,这样保证多入口配置,然后output->file是输出文件名字和路径,format是输出文件的格式,我们这里是esm格式即可,external的作用是为了防止一些个依赖出现循环依赖和冲突问题,我们将这些依赖设置为外部依赖

然后创建 rollup.config.dev.js和rollup.config.prod.js

js 复制代码
//prod
import config from './rollup.config.default.js'
import replace from '@rollup/plugin-replace'
export default config.map((config) => {
  config.plugins.push(
    replace({
      values: {
        // 🌸 替换打包文件关于环境变量的部分
        'process.env.NODE_ENV': JSON.stringify('development')
      },
        // 🌸 防止字符串后面有等号然后进行替换
      preventAssignment: true
    })
  )
  return config
})

//dev
import config from './rollup.config.default.js'
import replace from '@rollup/plugin-replace'
export default config.map((config) => {
  config.plugins.push(
    replace({
      values: {
        // 🌸 替换打包文件关于环境变量的部分
        'process.env.NODE_ENV': JSON.stringify('production')
      },
        // 🌸 防止字符串后面有等号然后进行替换
      preventAssignment: true
    })
  )
  return config
})

然后我们添加一下脚本,这里给大家推荐一种开发方式,分别在开发目录打开两个终端,一个终端输入npm run dev 这样rollup会监听入口文件的变化,只要入口文件内容变化就会自动打包,因为我们用了esbuild所以打包速度很快,然后在另一个终端测试功能即可

脚本添加

json 复制代码
"dev": "rimraf ./lib && rollup  -c rollup.config.dev.js --watch",
"build": "rimraf ./lib && rollup  -c rollup.config.prod.js",

入口文件添加

在 bin/index.js 添加 一定不要忘记写上面那句注释!!!这是告诉系统用node.js来解析该文件

js 复制代码
#!/usr/bin/env node
import main from '../lib/index.js'
main()

在 src/index.ts 添加

ts 复制代码
funciton main(){
    console.log("\nHello David Tao\n")
}

export default main

然后我们运行一下

sh 复制代码
npm run dev
node ./bin/index.js

打包成功📦

运行成功✅

剩下的内容我们下一篇文章来讲

🔧 手把手教你开发一个自己的cli工具(二)juejin.cn/post/735947...

相关推荐
崔庆才丨静觅7 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby60618 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了8 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅8 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅8 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅9 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment9 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅9 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊9 小时前
jwt介绍
前端
爱敲代码的小鱼9 小时前
AJAX(异步交互的技术来实现从服务端中获取数据):
前端·javascript·ajax