🔧 手把手教你开发一个自己的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...

相关推荐
类人_猿1 小时前
ASP.NET Web(.Net Framework) Http服务器搭建以及IIS站点发布
前端·iis·asp.net·.net·http站点服务器
组态软件4 小时前
web组态软件
前端·后端·物联网·编辑器·html
前端Hardy4 小时前
HTML&CSS:MacBook Air 3D 动画跃然屏上
前端·javascript·css·3d·html
cnsxjean7 小时前
SpringBoot集成Minio实现上传凭证、分片上传、秒传和断点续传
java·前端·spring boot·分布式·后端·中间件·架构
ZL_5677 小时前
uniapp中使用uni-forms实现表单管理,验证表单
前端·javascript·uni-app
沉浮yu大海8 小时前
Vue.js 组件开发:构建可重用且高效的 UI 块
前端·vue.js·ui
代码欢乐豆8 小时前
软件工程第13章小测
服务器·前端·数据库·软件工程
莘薪8 小时前
JQuery -- 第九课
前端·javascript·jquery
好青崧9 小时前
CSS 样式入门:属性全知晓
前端·css·tensorflow
光头程序员9 小时前
工程化开发谷歌插件到底有爽
前端·react·工程化·前端工程化·谷歌插件