写点什么

前端工具开发系列 (一) cli 工具开发

  • 2019-09-27
  • 本文字数:3580 字

    阅读完需:约 12 分钟

前端工具开发系列(一) cli 工具开发

前端开发过程中,尤其是新开项目的时候,经常会用到各种脚手架工具,比如 dva-cli、vue-cli 和 ant-design-pro-cli 等,这些都是基于 node 开发的命令行工具,本文以开发一个生成脚手架的命令行工具为例学习具体的开发流程。实现的需求就是通过 npm 全局安装这个 node 工具包,根据提示在命令行进行选择、输入等操作,最终生成一个以 react 或 vue 为基础的脚手架.当然,前提是必须要有现成的 react 或 vue 脚手架模版(这个不是本文的重点)。

1 实现思路

借鉴 ant-design-pro-cli 的实现原理, 把项目模版和命令行工具分离(这样做的目的是为了方便独立维护),cli 运行时收集并解析命令行参数(args)和用户交互内容(prompts),然后根据这些信息从 github 上下载项目模版(templates),如果有需要也可对模版中的目录和文件进行操作后重新渲染(render),比如修改 package.json 中的 name、description 等信息。最终在本地生成一个项目脚手架(local project)。



了解了需求和实现思路,就可以开始本次需求的开发了。

2 初始化项目

创建目录并命名为 gframe(generate frame 的缩写)


npm init -y 生成 package.json 并修改 name,version,description 的值


mkdir bin lib


touch bin/gframe lib/init.js


安装相关依赖包


1.2├── bin3         ├──gframe // 脚本文件4├── lib5         ├──init.js // 逻辑处理文件6├── node_modules7├── package-lock.json8├── package.json // 项目配置文件 1// 项目依赖的第三方包 2{ 3    ... 4    "dependencies": { 5        "bluebird": "^3.5.3", // promise库,让callback以.then的形式执行 6        "chalk": "^2.4.2", // 设置终端输出的样式 7        "commander": "^2.19.0", // 解析命令行参数并触发响应的动作 8        "download-git-repo": "^1.1.0",// 下载git仓库 9        "inquirer": "^6.2.2", // 收集命令行交互信息10        "ora": "^3.2.0" // 终端小图标11    }12  ...13}
复制代码

3 脚本文件 bin/gframe

该文件主要是通过 commander 注册了一个 init 命令,并对终端输入的参数进行收集和解析,如果有匹配的命令,则执行相应的 action。相关 api 介绍及代码如下:


version 方法输出版本信息


command 注册命令,参数是命令名称和回传给 action 方法的参数


description 输出该命令的描述


action 订阅了该命令触发时的回调函数


parse 对传入的参数进行解析并执行相应的回调


 1#!/usr/bin/env node  // 文件头部加上`#!/usr/bin/env node`,它指明了执行这个脚本文件的解释程序 2const fs = require('fs'); 3const chalk = require('chalk'); 4const program = require('commander'); 5const pkg = require('../package.json'); 6const { inquirerFn, downloadFn } = require('../lib/init'); 7program.version(pkg.version, '-v,--version'); 8program 9    .command('init <dirname>')10    .description(pkg.description)11    .action(dirname => {12        // 命令init触发时的回掉函数13        if (fs.existsSync(dirname)) {14            return console.log(chalk.red(`dirname ${dirname} is exist`));15        }16        inquirerFn().then(answers => {17            downloadFn(answers, dirname);18        });19    });20// 如果输入没有注册的命令,输出帮助提示21program.arguments('<command>').action(cmd => {22    program.outputHelp();23    console.log(' ');24    console.log(`error: unknown option '${cmd}'`);25});26program.parse(process.argv);27// 如果没写参数,输出帮助提示28if (!process.argv.slice(2).length) {29    program.outputHelp();30}
复制代码

4 逻辑处理文件 lib/init.js

该文件主要包括两部分逻辑,一部分是收集用户输入的交互信息,另一部分是从 github 上下载项目模版,并根据用户交互的内容渲染并输入模版。


  • 使用 inquirer 进行命令行交互


1.通过 prompt 方法配置交互方式


2.prompt 方法返回一个 promise 对象,可通过.then 中的参数获取交互结果


 1var inquirer = require('inquirer'); 2function inquirerFn() { 3    return inquirer.prompt([ 4        { 5            type: 'list', 6            name: 'frame', 7            message: '请选择开发用的脚手架:', 8            choices: ['react', 'vue'] 9        },10        {11            type: 'input',12            name: 'name',13            message: '请输入项目名称:'14        },15        {16            type: 'input',17            name: 'description',18            message: '请输入项目简介:'19        }20    ]);21}
复制代码


  • 使用 download-git-repo 下载模版


1.通过 bluebird 的 promisify 包装,用.then 替换 callback


2.download 的两个必须参数,repo 指仓库地址,dest 指下载的目标路径


3.需要注意的是仓库地址 com 或者端口号后面的/要改为:,最后面还需要用 #拼上分支名


  • 使用 ora 创建小图标


因为下载模版是个异步的过程,为了提高用户体验,需要加入 loading 效果。使用 ora 要注意的一点就是要手动调用 spinner.stop 方法结束,否则程序不会退出,因为内部是通过 setInterval 定时器实现的


1const fs = require(‘fs’);


2const ora = require(‘ora’);


3const chalk = require(‘chalk’);


4const Promise = require(‘bluebird’);


5const download = Promise.promisify(require(‘download-git-repo’));


6const spinner = ora(‘正在下载模板…’);


7/**


8 * 从 github 上下载已有的模版


9 * @param answers 命令行收集到的交互信息


10 * @param dirname 最终生成的项目名


11 */


12function downloadFn(answers, dirname) {


13 const { frame, name = dirname, description = dirname } = answers;


14 // 从 github 上找了两个 star 比较多的脚手架模版,一个 react,一个 vue


15 let url = ‘https://github.com:bodyno/react-starter-kit#master’;


16 if (frame === ‘vue’) {


17 url = ‘https://github.com:Mrminfive/vue-multiple-page#master’;


18 }


19 spinner.start();


20 download(url, dirname, { clone: false })


21 .then(() => {


22 spinner.stop(); // 关闭 loading 动效


23 console.log(chalk.green(‘download template success’));


24 // 重写 package 中的 name、description 等项目信息


25 const pkg = process.cwd() + /${dirname}/package.json;


26 const content = JSON.parse(fs.readFileSync(pkg, ‘utf8’));


27 content.name = name;


28 content.description = description;


29 const result = JSON.stringify(content);


30 fs.writeFileSync(pkg, result);


31 })


32 .catch(err => {


33 spinner.stop(); // 关闭 loading 动效


34 console.log(chalk.red(‘download template failed’));


35 console.log(err);


36 });


37}


5 本地测试


package.json 中增加 bin 字段,它是一个可执行命令和本地文件名的映射


在项目根目录下执行 npm link,这样会在全局的 node_modules 下生成一个符号链接,此时就可以在全局使用 package.json 中 bin 字段的命令名了


1{2    "bin": {3        "gframe": "./bin/gframe"4    }5}
复制代码


6npm 发布

https://www.npmjs.com上注册账号,已有 npm 账号的直接登录


项目根目录下执行 npm adduser,输入用户名、密码、邮箱后,如果登录成功会提示:Logged in as ‘username’ on https://registry.npmjs.org/。


项目根目录下执行 npm publish


发布过程中如果提示 You do not have permission to publish “packagename”. Are you logged in as the correct user?先检查本地登录的用户是否和 npm 官网上的用户是否一致,如果没问题,则把 package.json 中的 name 的值改掉,因为包名已经被别人使用了,所以不能正常发布


7 使用

1.npm install gframe -g


2.gframe init my-app


3.根据提示选择并输入相关信息


当前执行命令的目录下就会新增一个 my-app 的项目,里边有现成的脚手架,然后就可以愉快地开发了。


8 结束语

除了实现下载和渲染模版的功能,还可以在此基础上进一步拓展,比如重置模版的 git 地址、自动安装依赖等。当然,除了快速生成脚手架, cli 工具还可以做很多事情,比如代码校验、自动化测试等,这些都可以作为开发者的辅助工具,实实在在的提高开发效率。本文只是带大家入门,感兴趣的同学可以继续深入尝试。


作者介绍:


风雷(企业代号名),目前负责贝壳找房租赁轻托管业务前端开发工作。


本文转载自公众号贝壳产品技术(ID:gh_9afeb423f390)。


原文链接:


https://mp.weixin.qq.com/s/_ZBps4oNpnQLlXro2e128A


2019-09-27 13:045555

评论

发布
暂无评论
发现更多内容

极客时间运维进阶训练营第五周作业

独钓寒江

CrossOver2023免费版虚拟机软件安装包下载

茶色酒

CrossOver2023

富士康智能制造加速营:解决行业当下问题,创造核心技术价值

B Impact

性能测试知识科普(七):监控能给你带来什么

老张

性能测试 监控指标

【web 开发基础】PHP的匿名函数和闭包 (40)

迷彩

闭包 closure web开发基础 匿名函数 11月月更

Python基础库-json库

度假的小鱼

11月月更 Python操作json json语法规则

【React技术】JSX在企业级项目的运用and一个元素渲染demo

恒山其若陋兮

前端 11月月更

华为云大数据,帮助企业搭好数据底座

IT科技苏辞

以原子化服务为基石,面向三大场景打造鸿蒙软硬协同新体验

最新动态

三分钟了解client-go

穿过生命散发芬芳

Client-go 11月月更

关于部署策略的遐思

在天涯的海角

Python 操作BeautifulSoup4(爬取网页信息)

度假的小鱼

正则 11月月更 Python BeautifulSoup4

2022-11-28:给定两个数组A和B,比如 A = { 0, 1, 1 } B = { 1, 2, 3 } A[0] = 0, B[0] = 1,表示0到1有双向道路 A[1] = 1, B[1]

福大大架构师每日一题

算法 rust 福大大

python任务调度利器-APScheduler

AIWeker

Python 任务调度 11月月更

C++学习---类型萃取---is_pointer && is_lvalue_reference/is_rvalue_reference

桑榆

C++ STL 11月月更

CDH5部署三部曲之三:问题总结

程序员欣宸

大数据 hadoop CDH 11月月更

一文熟悉 Go 函数

陈明勇

Go golang 闭包 函数 11月月更

EasyRcovery2023绿色免费版U盘数据恢复软件

茶色酒

EasyRcovery2023

Flare Network,跨越互操作性三难困境

股市老人

我们为什么喜欢看疯狂科学家开飞艇?

脑极体

React源码分析3-render阶段(穿插scheduler和reconciler)

goClient1992

React

React源码分析2-深入理解fiber

goClient1992

React

mysql与redis的选型问题实践

苏格拉格拉

MySQL 数据库 持久化 redis'

支持向量机-二分类SVC中的样本不均衡问题:重要参数class_weight

烧灯续昼2002

Python 机器学习 算法 sklearn 11月月更

Gradle入门了解

派大星

Gradle

盘点入职时,那些常见但不合规的操作

石云升

职场 入职 11月月更

华为云大数据BI 解决方案的超高性价比

IT科技苏辞

Python操作XML文件

度假的小鱼

11月月更 Python操作xml xml与html区别

JXcore 打包在企业级项目里的合理运用和模块系统以及网络的配置详解【node.js】

恒山其若陋兮

前端 11月月更

React源码分析1-jsx转换及React.createElement

goClient1992

React

前端工具开发系列(一) cli 工具开发_大前端_风雷_InfoQ精选文章