(如下閱讀將花費10分鐘)javascript
平常開發中,咱們都只專一在業務上的開發,拿起一套開箱即用的模板項目就直接開搞了,不知道你們有沒有思考過,平時咱們使用的腳手架裏面到底作了什麼,而且若是是本身來搭一套腳手架,應該怎麼去搭呢? css
在本博客中,將記錄做者搭建腳手架的過程,總體將分爲兩個部分,第一部分是cli的搭建,第二部分是模板項目的搭建;java
在搭建腳手架cli以前,咱們首先思考一下,一個cli,須要什麼能力呢?答案是初始化能力,那麼初始化的功能須要怎樣去實現呢,這裏,咱們先梳理一下思路。node
咱們在使用其餘cli時,會發現,它們有問詢的功能,好比詢問項目名稱,項目描述等;而後還能夠選擇模板項目將要使用什麼css預處理器等的問題;因此,cli首先就要具有問詢功能,用以獲取定製化信息;
問詢結束後,咱們的cli會得到即將建立的項目的基本信息,接下來咱們就須要以一個模板項目做爲模板去建立,模板項目將會在第二部分講解,這裏一筆帶過~;OK,既然有了模板項目,cli就須要下載這個項目,而後複製這個項目,同時將前面問詢所得的定製化信息寫入項目配置中,因此cli還須要有下載和複製,寫入模板功能;
到此,一個模板項目基本已經建立成功了,那後面咱們還但願腳手架能夠幫忙進行git初始化以及安裝依賴的功能,因此最終,還須要添加git初始化和安裝依賴的功能;
總結一下,實現一個cli的初始化功能咱們須要有以下能力:
問詢 ==> 下載模板 ==> 複製,寫入模板 ==> git初始化 ==> 安裝依賴git
好了,爲了實現上述思路,cli將會引入以下依賴去實現對應的功能:github
const program = require('commander'); // commander負責讀取命令 const inquirer = require('inquirer'); // inquirer負責問詢 const download = require('download-git-repo'); // download-git-repo負責下載對應模板項目的git倉庫 const fse = require('fs-extra'); // fs-extra負責文件的複製 const memFs = require('mem-fs'); const editor = require('mem-fs-editor'); // mem-fs-editor負責模板的複製以及嵌入模板字符串,它須要依賴mem-fs const { exec } = require('child_process'); // child_process負責執行命令行
固然除了上述必須的依賴外,爲了更好的交互體驗,還引入了以下依賴:shell
const chalk = require('chalk'); // 改變命令行輸出樣式 const ora = require('ora'); // 一個優雅地命令行交互spinner
至此,準備的工做已經完畢,下面,就開始一步步來編寫cli了npm
萬事開頭難,有了思路後,實現思路纔是真正的開始,下面將介紹如何組織cli項目json
首先看一下,工程的組織數組
├── bin | ├── zero | ├── zero-init ├── src | ├── constants.js | ├── project.js | └── utils.js ├── .gitignore ├── .npmrc ├── README.md └── package.json
在bin/zero中引入commander,而且聲明init命令,commander會在同級目錄中尋找zero-init文件
const program = require('commander'); program .usage('<command> [options]') .command('init [name]', 'init a project') .parse(process.argv);
有了第一步的解析後,咱們能夠在zero-init文件中編寫建立邏輯,這裏,咱們再抽象project文件到src目錄底下,以便更好地分離職責,bin文件只負責解析命令就好啦~
下面就看看project文件中,如何實現問詢
inquirer.prompt([{ type: 'input', name: 'projectName', message: '請輸入項目名:', validate(input) { if (!input) { return '項目名不能爲空'; } if (fse.existsSync(input)) { return '當前目錄已存在同名項目,請更換項目名'; } return true; } }]);
inquirer提供prompt函數來實現問詢,其參數爲數組,問詢順序將按照數組的順序來進行;
在問詢結束後,咱們基本知道須要建立的項目的名稱、描述等信息了,下一步,就是下載倉庫了,下面咱們來看看核心代碼:
const downloadPath = path.join(projectPath, '__download__'); download(TEMPLATE_GIT_REPO, downloadPath, { clone: true }, (err) => { // 拷貝 // 此處省略若干代碼 // 拷貝完成後刪除臨時文件 fse.remove(downloadPath); }
這裏須要注意的是,模板工程所在的倉庫,即TEMPLATE_GIT_REPO,最好是public的
下載倉庫只是把模板工程存放在一個臨時文件夾內,真正的工程文件須要等信息寫入模板後再生成;
這裏須要注意的是,模板寫入時遵循ejs規範
const memFs = require('mem-fs'); const editor = require('mem-fs-editor'); // 這裏須要mem-fs進行內存優化 const store = memFs.create(); this.memFsEditor = editor.create(store); // 這裏source表示源文件,dest表示目標文件,data表示須要寫入的數據 this.memFsEditor.copyTpl( source, dest, data );
在調用copyTpl時,data字段中的key-value將被寫入到模板中,舉個栗子:
// 模板文件中,字段是這樣的 { name: "<%= projectName %>" } // data就要這樣去寫 { projectName: 'this is your project name' }
這兩步的核心都是使用nodejs提供的child_process中exec方法去執行命令來實現,exec能幫助咱們在命令行中執行shell命令,exec函數有對應的回調方法來讓咱們判斷命令執行是否成功,核心代碼以下:
exec('npm install', (error, stdout, stderr) => { if (error) { // 出錯了 } else { // 成功了 } })
是否是很簡單呢,想了解更多的操做能夠搜索官方文檔查看哈,這裏就不詳解了。
到這裏,咱們的cli已經實現的差很少了,這裏咱們總結一下如何測試和發佈吧
npm link // 本地調試 npm publish // 發佈
經過npm link命令,便可以在命令行工具測試你的cli了,注意在package.json的bin字段中定義好入口命令和文件
// package.json { "bin": { "zero": "bin/zero" } }
OK,文章主要總結了編寫一個腳手架中init功能的思路以及一些實現的方法,具體一些細節仍有優化的空間,歡迎你們討論!
文章的具體代碼能夠參考個人倉庫zero-cli