浪里小青蛙 發表於 2020-12-8 15:30:00

使用 typescript 快速开发一个 cli

<p>cli 的全称 command-line interface(命令行界面),也就是前端同学常用的脚手架,比如 yo、vue cli、react cli 等。</p>
<p>cli 可以方便我们快速创建项目,下图是引用 vue cli 的介绍:<br>
<img src="https://img2020.cnblogs.com/blog/423657/202012/423657-20201202150232018-1502238969.png" alt="vue cli guide" loading="lazy"></p>
<h2 id="创建项目">创建项目</h2>
<p>运行下面的命令,创建一个项目:</p>
<pre><code class="language-sh">npm init
</code></pre>
<p>执行命令完成后,可以看到项目根目录只有一个 package.json 文件。</p>
<p><img src="https://img2020.cnblogs.com/blog/423657/202012/423657-20201202185344046-40286989.png" alt="demo1" loading="lazy"></p>
<p>在 package.json 文件增加 bin 对象,并指定入口文件 dist/index.js。</p>
<blockquote>
<p>在命令行运行需要在入口文件的第一行增加 <code>#!/usr/bin/env node</code>,告诉系统用 node 运行这个文件。</p>
</blockquote>
<pre><code class="language-json">{
"name": "cli-demo",
"version": "0.0.1",
"description": "cli demo",
"keywords": [
    "cli"
],
"bin": {
    "cli-demo": "dist/index.js"
}
...
}
</code></pre>
<h2 id="安装依赖">安装依赖</h2>
<p>命令行工具,也会涉及到用户交互的动作,那么 node.js 是怎么实现呢?早有大佬提供了非常好的库,我们只要拿过来用,主要有两个库:</p>
<ul>
<li>commander:完整的 node.js 命令行解决方案。</li>
<li>inquirer:交互式命令行工具。</li>
</ul>
<p>将这两个库安装到项目里:</p>
<pre><code class="language-sh">yarn add commander inquirer
</code></pre>
<p>由于是用 typescript 开发,再通过 rollup 打包,先安装相关的依赖库:</p>
<pre><code class="language-sh">yarn add typescript rollup rollup-plugin-terser rollup-plugin-typescript2 @types/inquirer -D
</code></pre>
<h2 id="配置">配置</h2>
<p>由于是用 typescript 开发,首先需要配置一下 tsconfig.json。</p>
<pre><code class="language-json">{
"compilerOptions": {
    "target": "ES6",
    "module": "ESNext",
    "sourceMap": false,
    "declaration": false,
    "outDir": "./dist",
    "moduleResolution": "Node",
    "esModuleInterop": true,
    "resolveJsonModule": true,
    "removeComments": false,
    "importHelpers": true,
    "strict": true,
    "lib": ["ES6", "DOM"]
},
"include": ["src"]
}
</code></pre>
<p>接下来在根目录增加一个 rollup.config.js,把 typescript 代码编译成 javascript 代码。前面提到的要在第一行增加 <code>#!/usr/bin/env node</code> 来告诉系统用 node 运行,那么可以在 rollup.config.js 的 <code>banner</code> 选项,把 <code>#!/usr/bin/env node</code> 写在最前面。</p>
<pre><code class="language-js">import typescript from 'typescript'
import json from '@rollup/plugin-json'
import { terser } from 'rollup-plugin-terser'
import typescript2 from 'rollup-plugin-typescript2'

import { dependencies } from './package.json'

const external = Object.keys(dependencies || '')
const globals = external.reduce((prev, current) =&gt; {
const newPrev = prev

newPrev = current
return newPrev
}, {})

const defaultConfig = {
input: './src/index.ts',
output: {
    file: './dist/index.js',
    format: 'cjs',
    banner: '#!/usr/bin/env node',
    globals
},
external,
plugins: [
    typescript2({
      exclude: 'node_modules/**',
      useTsconfigDeclarationDir: true,
      typescript,
      tsconfig: './tsconfig.json'
    }),
    json(),
    terser()
]
}

export default defaultConfig
</code></pre>
<h2 id="实现一个简单的-cli">实现一个简单的 cli</h2>
<p>在根目录创建一个 <code>src</code> 文件夹,然后再创建一个 <code>index.ts</code>。</p>
<h3 id="添加引用">添加引用</h3>
<p>添加引用并实例化 <code>Command</code> 对象。</p>
<pre><code class="language-ts">import { Command } from 'commander'
import pkg from '../package.json'

const program = new Command(pkg.name)
</code></pre>
<h3 id="自定义命令">自定义命令</h3>
<p>实现一个可交互的自定义命令,模拟在终端(命令行)的登录功能。使用 <code>command</code> 方法创建一个命令,<code>description</code> 可以用来描述这个命令的作用,登录处理逻辑则写在 <code>action</code> 方法里。最后使用 <code>parse(process.argv)</code> 方法,解析命令。更多详细介绍和使用,可移步:https://github.com/tj/commander.js/blob/master/Readme_zh-CN.md。</p>
<pre><code class="language-ts">program
    .command('login')
    .description('模拟登录。')
    .action(() =&gt; {
       handleLogin()
    })

program.parse(process.argv)
</code></pre>
<p>交互的话,用到前面说的 <code>inquirer</code> 库,接收输入的用户名和密码。选项的 <code>type</code> 的值有 <code>input</code>、<code>password</code>、<code>number</code>、<code>checkbox</code>、<code>editor</code>、<code>list</code>、<code>rawList</code>、<code>expand</code>、<code>confirm</code>,选项 <code>name</code> 是 <code>inquirer.prompt</code> 方法返回的对象,选项 <code>validate</code> 可用来验证输入是否符合规则。更多详细介绍和使用,可移步:https://github.com/SBoudrias/Inquirer.js/blob/master/README.md</p>
<blockquote>
<p>如果选项 <code>type</code> 是 <code>password</code>,可通过 <code>mask</code> 设置掩码。</p>
</blockquote>
<pre><code class="language-ts">const handleLogin = () =&gt; {
// 配置交互的用户名和密码
const prompt = [
    {
      type: 'input',
      name: 'userName',
      message: '用户名:',
      validate: (value: string) =&gt; value.length &gt; 0 || '用户名不能为空'
    },
    {
      type: 'password',
      name: 'password',
      message: '密码:',
      mask: '🙈 ',
      validate: (value: string) =&gt; value.length &gt; 0 || '密码不能为空'
    }
]

inquirer.prompt(prompt).then(({ userName, password }) =&gt; {
    if (userName === 'demo' || password === '123456') {
      console.log('登录成功')
      return
    }
    console.log('用户名或密码错误')
})
}
</code></pre>
<h3 id="其他">其他</h3>
<p>一个 cli 工具,帮助信息也是必须的,可以通过 <code>on('--help')</code> 修改自定义帮助信息。</p>
<blockquote>
<p>必须在 <code>parse</code> 方法之前。</p>
</blockquote>
<pre><code class="language-ts">program.on('--help', () =&gt; {
   console.log('\n运行 cli-demo -h | --help 查看命令使用。\n')
})
</code></pre>
<p>然后再来修改一下,没有输入任何参数的时候,会出现错误,可以使用 <code>exitOverride</code> 方法重新退出,在终端(命令行)输出帮助信息。</p>
<pre><code class="language-ts">program.exitOverride()

try {
program.parse(process.argv)
} catch (error) {
program.outputHelp()
}
</code></pre>
<p>到这里,一个简单的 cli 工具完成了,先本地来测试下看看。在终端(命令行)输入 <code>npm link</code>,生成一个全局软连接,可以方便调试和测试。</p>
<p>完整代码:https://github.com/long-woo/cli-typescript-template</p>
<p><img src="https://img2020.cnblogs.com/blog/423657/202012/423657-20201208152306853-558166557.gif" alt="show demo" loading="lazy"></p>
<blockquote>
<p>转载请标注来源: https://www.cnblogs.com/JasonLong/p/14075724.html</p>
</blockquote><br><br>
来源:https://www.cnblogs.com/JasonLong/p/14075724.html
頁: [1]
查看完整版本: 使用 typescript 快速开发一个 cli