從0到1搭建一個自己的cli腳手架

從0到1搭建一個自己的腳手架

在這裏插入圖片描述
源碼地址

什麼是腳手架


顧名思義,腳手架就是幫助我們配置一些環境、工具,能夠讓我們方便的直接開始開發,專注於我們的業務,不用花時間去配置開發環境。例如常見的vue-cli,我們只需要通過npm i vue-cli -g全局安裝腳手架後再進行vue init即可初始化一個自己的項目,真是高大上呢。

接下來我們就要實現一個屬於自己的cli腳手架,雖然可能有點簡陋, 但是怎麼說也是屬於自己的啊

準備工作


我們在這裏簡單介紹一下需要依賴的包,熟悉每個包的作用。

commander

用來編寫指令和處理命令行的:

const program = require("commander");
// 定義指令
program
  .version('0.0.1')
  .command('init', 'Generate a new project from a template')
  .action(() => {
    // 回調函數
  })
// 解析命令行參數
program.parse(process.argv);

類比我們用過的vue init

chalk

修改控制檯輸出內容樣式,在這裏可以發揮一下你的藝術細菌了~

const chalk = require('chalk');
console.log(chalk.green('success'));
console.log(chalk.red('error'));

當然它有很多顏色選擇,還有背景色,不一一列舉了,到github上一看就知道了~

inquirer

一個用來設計交互式命令行的工具,非常強大

const inquirer = require('inquirer');
inquirer
  .prompt([
    // 交互式的問題,例如名字,是否使用ts
  ])
  .then(answers => {
    // 回調函數,answers 就是用戶輸入的內容,是個對象
  });

類比我們在進行完vue init後他是不是會問你用不用ts啊,eslint,CSS預處理器等等,就是它完成的

ora

介個東西,就是爲了美觀,下載的時候會有轉圈特效。

const ora = require('ora')
let spinner = ora('downloading template ...')
spinner.start()

同樣的,它也有很多樣式,自行查閱文檔

download-git-repo

顧名思義,這是用來下載遠程倉庫的,也就是我們的模版

const download = require('download-git-repo')
download(repository, destination, options, callback)

repository 是遠程倉庫地址;destination 是存放下載的文件路徑,也可以寫文件名,默認當前目錄;options 是選項,比如 { clone:boolean } 表示用 http download 還是 git clone 。

目錄搭建


當我們瞭解了上面的知識,就開始我們的工作吧~

  1. 創建一個文件夾,這裏我叫moe-cli
  2. 在該目錄下執行npm init -y,生成package.json文件,在文件裏複製下面的依賴,然後執行npm i
"dependencies": {
    "chalk": "^3.0.0",
    "commander": "^5.0.0",
    "download-git-repo": "^3.0.2",
    "inquirer": "^7.1.0",
    "ora": "^4.0.3"
  }
  1. 新建一個bin文件夾,在bin文件夾下新建一個文件moe.js,這個文件夾就是我們腳手架的入口文件,我們可以嘗試寫幾句代碼執行一下:
#!/usr/bin/env node
console.log('hello')

在這裏插入圖片描述
相信你們已經注意到了開頭的#!/usr/bin/env node,它的作用是當 系統看到這行時,能夠沿着該路徑查找node並執行,主要是爲了兼容mac電腦,確保執行

bin目錄初始化


現在bin目錄下只有這個文件,即入口文件,所以我們先完善這歌文件:

#!/usr/bin/env node
const program = require('commander')

// 定義當前版本
// 定義使用方法
// 定義四個指令
program
  .version(require('../package').version)
  .usage('<command> [options]')
  .command('add', 'add a new template')
  .command('delete', 'delete a template')
  .command('list', 'list all the templates')
  .command('init', 'generate a new project from a template')
  
// 解析命令行參數
program.parse(process.argv)

寫完代碼是不是覺得有點眼熟,對!他就是用來定義指令的,運行一下:
在這裏插入圖片描述
你曾經一定看到過這樣的界面,但是我們每次輸入node ./bin/moe太過麻煩,我們可以在package.json裏定義一個命令:

"bin": {
    "moe": "bin/moe.js"
  },

bin用來指定每個命令所對應的可執行文件地位置
然後在根目錄下執行npm link將命令掛載到全局,這樣我們只要輸入moe,就能直接運行了~
在這裏插入圖片描述
是不是非常的斯國一呢😄,接下來我們完善一下bin目錄,將我們入口文件中定義過的四個指令都對應的建立一個js文件,如圖:
在這裏插入圖片描述
同樣的,我們也得修改一下package.json裏的bin

"bin": {
    "moe": "bin/moe.js",
    "moe-add": "bin/moe-add.js",
    "moe-delete": "bin/moe-delete.js",
    "moe-list": "bin/moe-list.js",
    "moe-init": "bin/moe-init.js"
  },

然後先npm unlink解綁一下,再執行npm link重新綁定命令。最後我們在根目錄下新建一個template.json文件,內容爲{},作爲我們存放模版的倉庫。

編寫命令

這裏提供代碼,寫好了註釋,代碼不是很難,對照註釋一看就能明白,不需要過多講解


moe-add

#!/usr/bin/env node

// 交互式命令行
const inquirer = require('inquirer')
// 修改控制檯字符串的樣式
const chalk = require('chalk')
// node 內置文件模塊
const fs = require('fs')
// 讀取根目錄下的 template.json
const tplObj = require(`${__dirname}/../template`)

// 自定義交互式命令行的問題及簡單的校驗
let question = [
  {
    name: "name",
    type: 'input',
    message: "請輸入模板名稱",
    validate (val) {
      if (val === '') {
        return 'Name is required!'
      } else if (tplObj[val]) {
        return 'Template has already existed!'
      } else {
        return true
      }
    }
  },
  {
    name: "url",
    type: 'input',
    message: "請輸入模板地址",
    validate (val) {
      if (val === '') return 'The url is required!'
      return true
    }
  }
]

inquirer
  .prompt(question).then(answers => {
    // answers 就是用戶輸入的內容,是個對象
    let { name, url } = answers;
    // 過濾 unicode 字符
    tplObj[name] = url.replace(/[\u0000-\u0019]/g, '')
    // 把模板信息寫入 template.json 文件中
    fs.writeFile(`${__dirname}/../template.json`, JSON.stringify(tplObj), 'utf-8', err => {
      if (err) console.log(err)
      console.log('\n')
      console.log(chalk.green('Added successfully!\n'))
      console.log(chalk.grey('The latest template list is: \n'))
      console.log(tplObj)
      console.log('\n')
    })
  })

它的目的是要添加模板並進行存儲,存儲位置就是我們定義過的template.json了。我們執行看一下效果:
在這裏插入圖片描述
注意這裏的模版地址,不需要寫全部鏈接,如圖:
在這裏插入圖片描述
有些人可能會問,我們在入口文件定義program.command('add').action(() => {})的時候沒有寫action這個回調函數,爲什麼能夠執行moe add
其實當我們執行moe add時,commander會嘗試在入口文件的目錄內尋找可執行文件,找到形如program-command的命令來執行(moe-add),下面幾個命令也是同樣的道理

moe-delete

#!/usr/bin/env node

const inquirer = require('inquirer')
const chalk = require('chalk')
const fs = require('fs')
const tplObj = require(`${__dirname}/../template`)

let question = [
  {
    name: "name",
    message: "請輸入要刪除的模板名稱",
    validate (val) {
      if (val === '') {
        return 'Name is required!'
      } else if (!tplObj[val]) {
        return 'Template does not exist!'
      } else  {
        return true
      }
    }
  }
]

inquirer
  .prompt(question).then(answers => {
    let { name } = answers;
    delete tplObj[name]
    // 更新 template.json 文件
    fs.writeFile(`${__dirname}/../template.json`, JSON.stringify(tplObj), 'utf-8', err => {
      if (err) console.log(err)
      console.log('\n')
      console.log(chalk.green('Deleted successfully!\n'))
      console.log(chalk.grey('The latest template list is: \n'))
      console.log(tplObj)
      console.log('\n')
    })
  })

運行看結果:在這裏插入圖片描述

moe-list

這個最簡單了

#!/usr/bin/env node

const tplObj = require(`${__dirname}/../template`)
console.log(tplObj)

運行看結果:
在這裏插入圖片描述

moe-init

這是最重要的一步,我們通過這個命令來初始化一個項目,說的通俗易懂點就是通過它下載我們存儲過的模版。

#!/usr/bin/env node

const program = require('commander')
const chalk = require('chalk')
const ora = require('ora')
const download = require('download-git-repo')
const tplObj = require(`${__dirname}/../template`)

program
    .usage('<template-name> [project-name]')
program.parse(process.argv)
// 當沒有輸入參數的時候給個提示
if (program.args.length < 1) return program.help()

// 好比 vue init webpack project-name 的命令一樣,第一個參數是 webpack,第二個參數是 project-name
let templateName = program.args[0]
let projectName = program.args[1]
// 小小校驗一下參數
if (!tplObj[templateName]) {
    console.log(chalk.red('\n Template does not exit! \n '))
    return
}
if (!projectName) {
    console.log(chalk.red('\n Project should not be empty! \n '))
    return
}

url = tplObj[templateName]

console.log(chalk.white('\n Start generating... \n'))
// 出現加載圖標
const spinner = ora("Downloading...");
spinner.start();
// 執行下載方法並傳入參數
download(
    url,
    projectName,
    err => {
        if (err) {
            spinner.fail();
            console.log(chalk.red(`Generation failed. ${err}`))
            return
        }
        // 結束加載圖標
        spinner.succeed();
        console.log(chalk.cyan('\n Generation completed!'))
        console.log(chalk.cyan('\n To get started'))
        console.log(chalk.cyan(`\n    cd ${projectName} \n`))
    }
)

運行看結果:
在這裏插入圖片描述
我們的根目錄下會多出一個test文件夾
在這裏插入圖片描述
至此,簡單的腳手架就做好了!如果你看到這的話,能否留下一個👍鼓勵一下窩~

番外

我們腳手架搭建好了,是不是可以嘗試着發到npm上?想想就刺激~

  • 刪除 test 文件夾。
  • 在根目錄下新建 README.md 文件,寫上聲明之類的。
  • 在根目錄下新建 .npmignore 文件,並寫入 /node_modules,發佈的時候忽略 node_modules 文件夾。
  • 去 npm 官網註冊個賬號,同時搜索一下 moe-cli 這個名字,npm不允許重名的包出現。

一切準備就緒後,我們在根目錄下執行npm login登錄npm帳號
在這裏插入圖片描述
注意紅框標註的地方!一定是npm源
執行npm publish 一鍵發佈
在這裏插入圖片描述
然後過一會你就可以看到你的包了,我這裏報錯是因爲我爲了寫這篇博客把我發過的包刪掉了,需要等待24小時以後才能重新發布。

接下來你就可以嘗試使用npm安裝你發佈的包,並進行使用,是不是很爽!我這裏沒有辦法做演示,所以你可以自己動手操作一下。

最後


上面的操作,如果你熟悉了,再去看看vue-cli的源碼,可能就不會那麼閉塞了,當然它的功能比我們要完善的多~

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章