用Node.js開發一個Command Line Interface (CLI),node.jscli

Node.js用途很廣,常用來開發服務、桌面應用等被開發者熟知,Node.js還有另外一個非常實用的場景 - 命令行應用(后文統稱CLI)。愛掏網 - it200.com本文將介紹CLI的開發流程、常用的功能,并以meet-cli為例實戰演練,從零開始一步步開發一個可以在生產中使用(read world)的CLI工具。愛掏網 - it200.commeet-cli現已開源,讀者也可以直接下載查看。愛掏網 - it200.com

Why CLI?

在深入開發之前,我們先了解幾個問題。愛掏網 - it200.com

CLI是什么?

1

Command Line Interface,顧名思義是一種通過命令行來交互的工具或者說應用。愛掏網 - it200.comSPA應用中常用的如vue-cli,?angular-cli, node.js開發搭建express-generator,orm框架sequelize-cli,還有我們最常用的webpack,npm等。愛掏網 - it200.com他們是web開發者的輔助工具,旨在減少低級重復勞動,專注業務提高開發效率,規范develop workflow。愛掏網 - it200.com

舉比較典型的angular-cli 為例,讀者可以查看它的npm說明文檔,它可以讓angular開發者快速創建最佳實踐的angular應用,快速啟動,快速創建component、directive、pipe、service、module等,用過的都說很好用,現在各個框架都有配套CLI。愛掏網 - it200.com

CLI的根據不同業務場景有不同的功能,但萬變不離其宗,本質都是通過命令行交互的方式在本地電腦運行代碼,執行一些任務。愛掏網 - it200.com

CLI有什么好處?

我們可以從工作中總結繁雜、有規律可循、或者簡單重復勞動的工作用CLI來完成,只需一些命令,快速完成簡單基礎勞動。愛掏網 - it200.com以下是我對現有工作中的可以用CLI工具實現的總結舉例:

  1. 快速生成應用模板,如vue-cli等根據與開發者的一些交互式問答生成應用框架
  2. 創建module模板文件,如angular-cli,創建component,module;sequelize-cli 創建與mysql表映射的model等
  3. 服務啟動,如ng serve
  4. eslint,代碼校驗,如vue,angular,基本都具備此功能
  5. 自動化測試 如vue,angular,基本都具備此功能
  6. 編譯build,如vue,angular,基本都具備此功能
  7. *編譯分析,利用webpack插件進行分析
  8. *git操作
  9. *生成的代碼上傳CDN
  10. *還可以是小工具用途的功能,如http請求api、圖片壓縮、生成雪碧圖等等,只要你想做的都能做

總體而言就是一些快捷的操作替代人工重復勞動,提升開發效率。愛掏網 - it200.com

與npm scripts的對比

npm scripts也可以實現開發工作流,通過在package.json 中的scripts對象上配置相關npm 命令,執行相關js來達到相同的目的;

但是cli工具與npm scripts相比有什么優勢呢?

  1. npm scripts是某個具體項目的,只能在該項目內使用,cli可以是全局安裝的,多個項目使用;
  2. 使用npm scripts 在業務工程里面嵌入工作流,耦合太高;使用cli 可以讓業務代碼工作流相關代碼剝離,業務代碼專注業務
  3. cli工具可以不斷迭代開發,演進,沉淀。愛掏網 - it200.com

meet-cli?針對項目實際需求,貼合工作實際需求應運而生。愛掏網 - it200.com接下來看看meet-cli的一些功能;

MEET-CLI

1

本文基于

美柚的web開發的工作主要是產品內hybrid應用的 h5部分,以及廣告、營銷互動類的h5、往往互相獨立,工作中發現有以下一些問題:

  • 每個h5的創建一些列目錄和文件,每個h5都有公共的基礎代碼
  • 每次新功能都需要配置相關的npm watch和build命令,我們需要一個創建模板的功能
  • 各個工程之間都有一套自己的build代碼,上傳CDN的代碼,各不相同,開發人員垮項目開發上手慢
  • 每次創建新工程build的代碼都需要重復做一次(或者通過復制粘貼的辦法),我們需要一個公共的上傳功能

基于工作中的問題,額外我再加了點小功能,meet-cli誕生了,下面展示下他的一些功能;

1、meet -help查看功能列表

1

一個cli工具都具有查看幫助的功能,圖中可以看出meet-cli具備創建module、編譯、發布(git提交與資源上傳cdn)、單獨指定文件上傳cdn功能、分析生成文件功能

接下來快速演示上述幾個主要功能

2、meet init

meet init 會在工程根目錄下生成meet.config.js 文件,用以配置meet工具的使用

const path = require('path');

module.exports = {

  // module 生成的目標目錄
  modulePath: path.resolve('public'),

  // module template 目錄
  moduleTemplatePath: path.resolve('meet/templates/module'),

  // project git url
  gitUrl:'http://gitlab.meiyou.com/advertise/ad-activity.git',

  // module build npm command
  npmBuildCommand:'npm run release:',

  // upload assets config
  upload:{

    // CDN Server
    server:'alioss',// 阿里OSS - 'alioss', 七牛云 - 'qn'

    // alioss server config
    config:{
      accessKeyId: "LTAIovxxxx0",
      accessKeySecret: "5xkXYxxxxf6wlzD8",
      bucket: "adstatic",
      region: "oss-cn-beijing",
      srcDir: path.resolve('public/assets'),// 要上傳的dist文件夾
      ignoreDir: false,
      deduplication: true,
      prefix: "ad-activity.meiyou.com",
    }
  },

  // is publish after build?
  autoPublish: true,

  // 測試提交文字
};

3、meet new [module]

快速創建h5模塊目錄和基礎文件,基礎css,html,js,必要依賴,(還可以進行相關express路由配置,指定模塊編譯配置)

1

4、meet build [module]

build模塊,生成代碼,用webpack-bundle-analyzer 進行分析,可視化顯示資源占比,可以一目了然的查看代碼體積上是否存在問題,對于性能優化是一個好處

1

5、meet publish (git操作+upload CDN)

1

還有meet analysis 、meet upload 等功能都是上述功能的局部。愛掏網 - it200.commeet upload 可以指定上傳某個路徑下的資源,作為上傳工具單獨而存在。愛掏網 - it200.com

目前實用功能比較少,后面還會增加一些功能

上面這一波操作很酷, 那是怎么實現呢,我們的核心內容現在才登場,如何從零開始開發一個CLI呢?

從零開發CLI

我們將從零開始開發meet-cli來實戰演示一個完整的cli的開發過程;(注:為了不影響我電腦的meet-cli,我將后文的cli demo命名為mei-cli ,大家見諒!)

基礎模塊

1、創建npm模塊

執行命令,創建npm模塊

npm init -y

得到

1

上面這步驟大家都很熟悉;

2、bin 入口文件

在package.json 文件增加bin的對象

{
  "name": "cli-demo",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "bin":{
    "mei": "./bin/index.js"
  },
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC"
}

js 中首行設置如下代碼:

#!/usr/bin/env node

上面這句話是用來告訴操作系統用node來運行這個文件

可以在js中console.log('hello mei')

3、全局安裝

執行npm install -g ,將mei-cli安裝到全局。愛掏網 - it200.comso,最簡單的cli誕生了,任意找個位置輸入mei 命令,就執行了你的 ./bin/index.js文件,console.log了一句話,‘hello mei’。愛掏網 - it200.com可以將mei-cli模塊發布到npm上,這樣就可以給社區使用了。愛掏網 - it200.com如何發布npm模塊,這里有一篇我的博客可以查看。愛掏網 - it200.com

node.js 知識

node.js 具有filesystem模塊,可以讓開發者對文件進行讀寫、創建、刪除等操作;

process、child_process、path、以及commonjs模塊化知識等

基礎掌握了,我們來認識一些常用組件

  • commander?CLI常用開發框架
  • chalk?終端文字加顏色js組件
  • clui?spinners、sparklines、progress bars圖樣顯示組件
  • shelljs?node.js運行shell命令組件
  • blessed-contrib?命令行可視化組件
  • lnquirer?命令行交互信息收集組件
  • figlet?FIGlet is a program that creates large characters out of ordinary screen characters

此外,還有游大神開發的meet-ali-oss上傳模塊

上述這些組件足以讓你開發酷炫的cli,如果不夠,這里還有50個組件 任你挑選;

我們要完成的cli主體結構圖

1

文件結構要劃分合理,index.js是主入口文件, commands專門放主要的命令功能邏輯,根據命令模塊劃分,比較細的功能實現可以抽成組件放在lib文件夾中,剩余的配置,以及模板等放meet文件夾中

主入口文件

#!/usr/bin/env node
const path = require('path');
const fs = require('fs');
const program = require('commander');
const gmodule = require('../packages/commands/module');
// const serve = require('../packages/commands/serve');
const question = require('../packages/commands/question');
const build = require('../packages/commands/build');
const publish = require('../packages/commands/publish');
const upload = require('../packages/commands/upload');
const analysis = require('../packages/lib/analysis');
const initial = require('../packages/commands/initial');

let config = {};
// 配置文件如果存在則讀取
if(fs.existsSync(path.resolve('meet.config.js'))){
    config = require(path.resolve('meet.config.js'));
}

program
    .version('1.0.0','-v, --version')
    .command('init')
    .description('initialize your meet config')
    .action(initial);

program
    .command('new [module]')
    .description('generator a new module')
    .action(function(module){
        gmodule(config,module)
    });

program
    .command('build [module]')
    .description('git build specify module and assets upload to CDN!')
    .action(function(module){
        build(config,module)
    });

program
    .command('publish')
    .description('upload assets to CDN and git commit && push')
    .action(function(){
        publish(config)
    });

program
    .command('upload')
    .description('upload your build dist files to CDN server')
    .action(function () {
        upload(config.upload);
    });

program
    .command('analysis')
    .description('analysis dist files size and percent')
    .action(function () {
        analysis(config.upload.config.srcDir);
    });

program
    .command('question')
    .description('analysis dist files size and percent')
    .action(function(){
        question()
    });

program.parse(process.argv);

主入口文件利用commander監測終端輸入命令時,觸發相應的模塊運行。愛掏網 - it200.comcommander會自動生成mei -help的命令,該命令用來顯示支持的命令。愛掏網 - it200.com命令命名盡可能短、見名知意,不支持的命令有相關提示,運行錯誤有正確的提示和響應,是cli的最佳實踐。愛掏網 - it200.com

這里在主入口文件中讀取了meet.config.js,把相應的的配置信息傳遞給對應模塊。愛掏網 - it200.com如把CDN上傳的配置信息傳給上傳模塊,把

用了commander發現這cli也沒有什么技術含量。愛掏網 - it200.com

meet new [module] 觸發運行的js

const path = require('path');
const fs = require('fs');
const chalk = require('chalk');
const inquirer = require('inquirer');

// 要拷貝的目標所在路徑
let templatePath;
// 目標文件夾根路徑
let targetRootPath;

function deleteFolderRecursive (path) {
    if (fs.existsSync(path)) {
        fs.readdirSync(path).forEach(function(file, index){
            var curPath = path + "/" + file;
            if (fs.lstatSync(curPath).isDirectory()) {
                // recurse
                deleteFolderRecursive(curPath);
            } else { // delete file
                fs.unlinkSync(curPath);
            }
        });
        fs.rmdirSync(path);
    }
};

function copyTemplates(name){
    function readAndCopyFile(parentPath,tempPath){
        let files = fs.readdirSync(parentPath);

        files.forEach((file)=>{
            let curPath = `${parentPath}/${file}`;
            let stat = fs.statSync(curPath);
            let filePath = `${targetRootPath}/${tempPath}/${file}`;
            if(stat.isDirectory()){
                fs.mkdirSync(filePath);
                readAndCopyFile(`${parentPath}/${file}`,`${tempPath}/${file}`);
            }
            else{
                const contents = fs.readFileSync(curPath,'utf8');
                fs.writeFileSync(filePath,contents, 'utf8');
            }

        });
    }

    readAndCopyFile(templatePath,name);
}

function generateModule(meetConfig,name){
    templatePath = typeof meetConfig.moduleTemplatePath !== 'undefined'? path.resolve(meetConfig.moduleTemplatePath):path.join(__dirname,'..','meet/module');
    targetRootPath = meetConfig.modulePath;
    let targetDir = path.join(targetRootPath,name);

    if(fs.existsSync(targetDir)){

        // 如果已存在改模塊,提問開發者是否覆蓋該模塊
        inquirer.prompt([
            {
                name:'module-overwrite',
                type:'confirm',
                message:`Module named ${name} is already existed, are you sure to overwrite?`,
                validate: function(input){
                    if(input.lowerCase !== 'y' && input.lowerCase !== 'n' ){
                        return 'Please input y/n !'
                    }
                    else{
                        return true;
                    }
                }
            }
        ])
            .then(answers=>{
                console.log('answers',answers);

                // 如果確定覆蓋
                if(answers['module-overwrite']){
                    // 刪除文件夾
                    deleteFolderRecursive(targetDir);
                    console.log(chalk.yellow(`Module already existed , removing!`));

                    //創建新模塊文件夾
                    fs.mkdirSync(targetDir);
                    copyTemplates(name);
                    console.log(chalk.green(`Generate new module "${name}" finished!`));
                }
            })
            .catch(err=>{
                console.log(chalk.red(err));
            })
    }
    else{
        //創建新模塊文件夾
        fs.mkdirSync(targetDir);
        copyTemplates(name);
        console.log(chalk.green(`Generate new module "${name}" finished!`));
    }

}

module.exports = generateModule;

主要邏輯是根據用戶配置的templatePath 與targetRootPath,遍歷templatePath路徑下的所有文件夾與文件,copy文件到targetRootPath,如果已經存在則提示是否覆蓋。愛掏網 - it200.com

上面說明templatePath 是一個靈活的路徑,模板可以在mei-cli中,也可以在任何一個位置,只要指定了正確的路徑,就能實現相同的結果。愛掏網 - it200.com此功能可以使用任何web框架,任何web框架都可以準備他的module模板,它的作用就是把模板文件copy到指定位置,也就是一鍵生成模板。愛掏網 - it200.com

看到這里,發現確實沒什么技術含量。愛掏網 - it200.com

meet publish

const chalk = require('chalk');
const inquirer = require('inquirer');
const shellHelper = require('../lib/shellHelper');
const upload = require('./upload');

let config = {
    autoPublish: false
};

function gitCommit(){
    // 發布,提示輸入commit 信息
    inquirer.prompt([
        {
            name:'message',
            type:'input',
            message:`Enter your publish message \n `
        }
    ])
        .then(answers=>{
            let message = answers.message;
            shellHelper.series([
                'git pull',
                'git add .',
                `git commit -m "${message}"`,
                'git push',
            ], function(err){
                if(err){
                    console.log(chalk.red(err));
                    process.exit(0);
                }
                console.log(chalk.green('Git push finished!'));
                process.exit(0);
            });
        })
        .catch(err=>{
            console.log(chalk.red(err));
        });
}

function publish(meetConfig){
    Object.assign(config,meetConfig);
    upload(config.upload)
        .then(res=>{
            console.log(chalk.green('Upload To CDN finished!'));

            if(config.autoPublish === true){
                gitCommit();
            }
        })
        .catch(err=>{
            console.log(chalk.red(err));
        })
}

module.exports = publish;

meet publish 原理是利用node.js child_process順序執行多個git命令進行git提交,利用meet-ali-oss進行資源文件上傳。愛掏網 - it200.com

剩下的還有build、initial、upload,analysis等功能,都是類似的,不再貼代碼進行一一闡述了,讀者可以下載meet-cli進行查看

git clone https://github.com/linweiwei123/meet-cli.git

除此之外,還可以放飛眼界,在你的cli中加入更多功能,比如進行與服務器進行通信(用axios http模塊請求)、實時通信、分享CLI命令界面等等(有些很雞肋),只要是符合實際需要的,大膽設想,大膽實現,讓你的CLI無比強大。愛掏網 - it200.com

功能邏輯見仁見智,開發者可以發揮個人的智慧開發適合自己的CLI。愛掏網 - it200.com

CLI開發中還有一些地方需要開發者注意。愛掏網 - it200.com

注意事項

  1. .gitignore,.npmignore 跟npm模塊一樣CLI也需要注意提交文件內容限制
  2. package.json 中注意dependencies與devDependencies的區別
  3. 良好的文檔描述

到此meet-cli就開發完成了,還可以發布到npm上給社區使用(適合的話)。愛掏網 - it200.com

未來計劃

todolist

  • 增加圖片處理命令 meet comp [path],用于壓縮,生成webp
  • 生成gitignore文件命令
  • 生成eslint配置
  • 與multipages-generator合并,形成完整的h5開發工作流
  • 類似vue-cli 通過網頁操作替代cli交互

總結

本文主要介紹了CLI的入門,常見的組件以及用法,常見的CLI功能。愛掏網 - it200.comCLI是web開發的輔助工具,旨在提高web工作效率,希望本文能給大家的工作帶來一些啟迪與幫助!

原文發布時間:2024-07-03

本文來源掘金如需轉載請緊急聯系作者

聲明:所有內容來自互聯網搜索結果,不保證100%準確性,僅供參考。如若本站內容侵犯了原著者的合法權益,可聯系我們進行處理。
發表評論
更多 網友評論0 條評論)
暫無評論

返回頂部

主站蜘蛛池模板: 国外免费直播性xxxx18| 日本色图在线观看| 国产成人免费手机在线观看视频 | 99精品视频在线观看| 波多野结衣系列痴女| 国产精品久久国产精品99| 久久精品国产一区二区三区| 色一情一乱一伦一视频免费看 | 午夜宅男在线永久免费观看网| japanese色国产在线看免费| 欧美日韩亚洲国产千人斩| 国产成人影院在线观看| 中文字幕无码不卡在线| 理论片手机在线观看免费视频| 国产精品四虎在线观看免费| 久久毛片免费看一区二区三区| 精品日韩在线视频| 国内精品一区二区三区app | 人人妻人人澡人人爽欧美一区| 2022国产精品手机在线观看| 日本道在线播放| 免费国产午夜高清在线视频| 香蕉视频黄色在线观看| 日日躁夜夜躁狠狠躁超碰97| 人妻一本久道久久综合久久鬼色| 中文字幕天天干| 成年人黄色一级片| 亚洲熟妇少妇任你躁在线观看无码 | 国产麻豆视频免费观看| 久久精品国产屋| 精品人妻系列无码人妻免费视频 | 成年无码av片在线| 亚洲精品短视频| 韩国三级女电影完整版| 好吊妞998视频免费观看在线| 亚洲人成777在线播放| 美女无遮挡拍拍拍免费视频| 国产视频2021| 中文字幕无码视频专区| 欧美激情xxxx性bbbb| 国产999在线观看|