美文网首页
【npm】搞个自己的CLI拉取基础工程

【npm】搞个自己的CLI拉取基础工程

作者: 前端菜篮子 | 来源:发表于2019-12-16 16:09 被阅读0次

    一、CLI原理

    CLIcommand-line interface — 命令行界面):commander 这个node库,有很多cli使用了它,它可以帮助开发者简化实现命令流程。不过我们的第一步还是应该先搞清楚,如何通过npm如何实现命令行。(其实就和普通的bat命令一样)

    一个简单的cli
    package.json 中有一个 bin 字段,指定各个内部命令对应的可执行文件的位置。
    ②在包安装时,如果是全局安装,npm 将会把 package.json 里定义的 bin 文件软连接到全局 node_modules/bin
    ③如果是非全局安装,会软链接到项目文件夹./node_modules/.bin/
    ④根据下面代码的配置,当我们全局安装此包后,在任意位置运行 cli-test,都会执行全局 node_modules 中的 cli-test 文件。

    /*cli-test 的 package.json*/
    "bin": {
        "cli-test": "./bin/cli-test"
    }
    

    cli文件:
    下面的第一行必写,是告诉操作系统这个文件中的代码用node可执行程序去运行它。 后面就做我们要做的事情就行了 。

    #!/usr/bin/env node
    //do something
    

    一个简单的cli就完成了
    其实有很多三方cli也并没有用类似 commandernode 库,如果我们的 cli 足够简单,以上这样就可以了。


    二、commandernode.js命令行界面的完整解决方案

    1. Commander.js中文文档链接一
    2. Commander.js中文文档链接二
    3. 仿vue的前端自定义cmd命令拉取项目脚手架

    那下面开练吧! 我们先来看到脚手架工程中可能会用到的js~

    A. commander.js

    //安装
    npm install commander --save
    //调用
    const program = require("commander");
    

    B. chalk.js:修改控制台中字符串的样式【打印日志的时候,根据日志级别的不同,显示不同的颜色】,包括:①字体样式(加粗、隐藏等);②字体颜色;③背景颜色。 创建log.js如下:

    
    //安装
    npm install chalk --save-dev
    
    //调用
    const chalk = require('chalk');
    const log = console.log;
    
    const success = function (msg) {
      log(chalk.bgGreen(' SUCCESS ') + chalk.green(' ' + msg));
    }
    
    const warn = function (msg) {
      log(chalk.bgYellow(' WARN ') + chalk.yellow(' ' + msg));
    }
    
    const error= function (msg) {
      log(chalk.bgRed(' ERROR') + chalk.red(' ' + msg));
    }
    
    const info = function (msg) {
      log(chalk.bgBlackBright(' INFO ') + chalk.gray(' ' + msg));
    }
    
    const tip = function (msg) {
      log(chalk.bgBlue(' TIP') + chalk.blue(' ' + msg));
    }
    
    module.exports = { success, warn, error, info, tip }
    
    

    C. fs-extra.js:文件操作相关工具库,该模块是系统fs模块的扩展,提供了更多便利的 API,并继承了fs模块的 API

    //安装
    npm install --save-dev fs-extra
    //调用
    const fs = require("fs-extra");
    

    相关API了解下:

    
    a. 文件拷贝:copy(src, dest, [option],callback);【copySync()】
    /**
    option对应:
      ①clobber (boolean): 覆盖现有的文件或目录,默认true
      ②dereference (boolean): dereference symlinks, default is false
      ③preserveTimestamps (boolean): 最后修改和访问时间和原始的源文件一致,默认为false
      ④filter: 函数或正则表达式过滤复制文件,返回true包含,否则排除
    */
    fs.copy('/tmp/myfile', '/tmp/mynewfile', function (err) {
       if (err) return console.error(err); 
       console.log("success!")
    }) //拷贝文件
    fs.copy('/tmp/mydir', '/tmp/mynewdir', function (err) {
       if (err) return console.error(err) 
       console.log('success!')
    }) //拷贝目录
    
    ============================================
    
    b. 清空目录:emptydir() 【emptyDirSync(), emptydirSync()】
    /**
    确保一个目录是空的。如果目录非空删除目录内容。
    如果目录不存在,就创建一个。目录本身并不是删除。
    */
    fs.emptyDir('/tmp/some/dir', function (err) {
      if (!err) console.log('success!')
    })
    
    ============================================
    
    c. 创建文件:ensureFile() 【createFileSync(),ensureFileSync()】
    /**
    确保文件存在。如果被请求的文件的目录不存在,创建这些目录。
    如果文件已经存在,它不修改。
    */
    var file = '/tmp/this/path/does/not/exist/file.txt';
    fs.ensureFile(file, function (err) { 
       console.log(err) ;
    })
    
    =============================================
    
    d.创建目录:ensureDir()  【ensureDirSync()】
    /**
    确保目录的存在。如果目录结构不存在,就创建一个
    */
    var dir = '/tmp/this/path/does/not/exist';
    fs.ensureDir(dir, function (err) {
       console.log(err);
    })
    
    

    D. inquirer.js如果想自己做一个脚手架或者在某些时候要与用户进行交互,这个时候就不得不提到了这个库了。

    由于交互的问题种类不同,inquirer为每个问题提供很多参数:
    type:表示提问的类型,包括:input, confirm, list, rawlist, 
                          expand, checkbox, password, editor
    name: 存储当前问题回答的变量;
    message:问题的描述;
    default:默认值;
    choices:列表选项,在某些type下可用,并且包含一个分隔符(separator);
    validate:对用户的回答进行校验;
    filter:对用户的回答进行过滤处理,返回处理后的值;
    transformer:对用户回答的显示效果进行处理
        (如:修改回答的字体或背景颜色),但不会影响最终的答案的内容;
    when:根据前面问题的回答,判断当前问题是否需要被回答;
    pageSize:修改某些type类型下的渲染行数;
    prefix:修改message默认前缀;
    suffix:修改message默认后缀。
    

    案例如下:

    const promptList = [{
        type: 'input',
        message: '设置一个用户名:',
        name: 'name',
        default: "test_user" // 默认值
    },{
        type: 'input',
        message: '请输入手机号:',
        name: 'phone',
        validate: function(val) {
            if(val.match(/\d{11}/g)) { // 校验位数
                return val;
            }
            return "请输入11位数字";
        }
    },{
        type: "confirm",
        message: "是否使用监听?",
        name: "watch",
        prefix: "前缀"
    },{
        type: "confirm",
        message: "是否进行文件过滤?",
        name: "filter",
        suffix: "后缀",
        when: function(answers) { // 当watch为true的时候才会提问当前问题
            return answers.watch
        }
    },{
        type: 'list',
        message: '请选择一种水果:',
        name: 'fruit',
        choices: [
            "Apple",
            "Pear",
            "Banana"
        ],
        filter: function (val) { // 使用filter将回答变为小写
            return val.toLowerCase();
        }
    },{
        type: "expand",
        message: "请选择一种水果:",
        name: "fruit",
        choices: [
            {
                key: "a",
                name: "Apple",
                value: "apple"
            },
            {
                key: "O",
                name: "Orange",
                value: "orange"
            },
            {
                key: "p",
                name: "Pear",
                value: "pear"
            }
        ]
    },{
        type: "checkbox",
        message: "选择颜色:",
        name: "color",
        choices: [
            "red",
            "blur",
            "green",
            "yellow"
        ],
        pageSize: 2 // 设置行数
    },{
        type: "password", // 密码为密文输入
        message: "请输入密码:",
        name: "pwd"
    },{
        type: "editor",
        message: "请输入备注:",
        name: "editor"
    }]
    
    

    E. execa:据称是更好的子进程管理工具。执行后续的示例,先执行npm install --save execa

    const execa = require("execa");
    execa("ls").then(result => console.log(result.stdout));
    

    F.get-stream:Get a stream as a string, buffer, or array先执行npm install get-stream

    //检查NodeJs版本
    const execa = require('execa');
    const getStream = require('get-stream');
    const stream = execa('node', ['--version']).stdout;
    return getStream(stream).then(data => {
    
     })
    

    G. download-git-repo:Download and extract a git repository (GitHub, GitLab, Bitbucket) from node. 若我们CLI中的具体基础工程是从git上下载的,那就需要用到该库了。
    Vue-CLIvue-init中就有调用了该函数

    image.png

    若我们自己的脚手架中包含了基础工程,则执行命令时拷贝基础工程到我们创建的初始工程中即可。

    image.png

    H. vue-cli 中用到的其他一些库
    user-home:【Get the path to the user home directory — 获取用户主目录的路径】

    image.png

    tildifyConvert an absolute path to a tilde path: /Users/sindresorhus/dev → ~/dev — 将绝对路径转换为波形路径】

    image.png

    ora:【Elegant terminal spinner — 在终端里有显示载入动画】

    image.png

    vue-cli其他js中还引用了其他一些库,这里就先不介绍了,后面再将vue-cli源码 Vue-cli 原理分析好好解读下。下图是vue-cli的一个基本结构。

    image.png

    小结
    搭建一个简易的cli需要:
    ① 检查node 等的版本号,一般校验不能低于某个版本
    ② 日志的打印:chalk
    ③ 搭建过程中的一些提问: inquirer
    ④ 工程包的拷贝fs-extra 或下载 download-git-repo
    pack.json的配置

    来看下这个最简易cli的结构吧:

    image.png

    具体的创建过程,上面已提到过,即基础工程的拷贝过程。这里再一次贴一下相关代码吧

    image.png

    相关package.json也看下吧:

    image.png

    将该cli发布到npm中,具体发布过程见将自己的vue组件发布为npm包 & npm私有仓库搭建

    image.png

    发布成功后,安装cli

    npm install eui2-cli -g
    

    按装成功后,执行命令

    //projectName: 你的工程名
    eui2 create <projectName>
    

    创建工程

    image.png

    打开工程目录查看

    image.png

    没错,就是它了

    最简易的cli 在git上的地址


    三、yeoman自行了解下

    大前端的自动化工厂(1)——Yeoman

    相关文章

      网友评论

          本文标题:【npm】搞个自己的CLI拉取基础工程

          本文链接:https://www.haomeiwen.com/subject/hcufnctx.html