前端腳手架開發入門

爲何須要腳手架

腳手架本質上是一個工具,使用腳手架的目的就是擺脫構建工程時重複性的工做,尤爲是當一個工程具備必定通用性時,工程腳手架的意義就更爲突出。它可讓咱們只須要一行命令,就能夠初始化好一項工程,而不用費心費力的去作配置環境,安裝依賴,解決依賴衝突這樣的支線任務,能夠直奔主線任務,早早下班~~vue

概覽

開發一個的腳手架,一般須要以下npm包:node

  • commander: 強大的node命令行處理工具。能輕鬆的獲取命令行的參數。
  • inquirer: 命令行交互工具,讓你能以「問答」的交互方式來完成一系列的命令行操做。
  • download-git-repo: git倉庫下載工具,一般用來下載模板代碼。
  • ora: 終端loading美化工具。
  • chalk: 命令行輸入/輸出美化工具,想要五彩版本的命令行,選它就對了。
  • handlebars: 模板引擎

實現的功能:react

  • 一條簡單的命令初始化項目
  • 提供友好的交互體驗
  • 可選擇安裝不一樣模板
  • 自動安裝項目依賴

開始幹活

STEP1: 打開一個終端,在你喜歡的地方新建一個空項目git

mkdir meo-cli
cd meo-cli
複製代碼

在項目更目錄下執行:github

npm init -y
複製代碼

你會獲得一個package.jsonvue-cli

{
  "name": "meo-cli",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC"
}
複製代碼

關鍵點來了,在 package.json中加以一個bin字段,在安裝時,npm 會將文件符號連接到 prefix/bin 以進行全局安裝或./node_modules/.bin/本地安裝。這樣,就能夠全局使用了。例如,下面的將meo-cli做爲命令名稱,執行文件是根目錄的index.jsnpm

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

STEP2 : 先寫一個簡單代碼試一試吧~。在根目錄下建立index.js文件,而後狠狠敲入下面的代碼:json

#!/usr/bin/env node

console.log('helo, world!')
複製代碼

而後在命令行執行 meo-cli, 不出意外的話,你應該看不到輸出hello,world。只會有個報錯提示:數組

zsh: command not found: meo-cli
複製代碼

這是爲什呢?由於咱們並無安裝對應的包,固然就不會連接到全局啦。一個辦法是發包,而後安裝到本地,就能夠了。可是這樣太麻煩,難道每次調試都要發包??bash

有沒有更優雅的方法呢?答案是:有 npm早就想好了對策,就是npm link,它能夠把指定的執行文件連接到全局,使用也很是簡單,在項目根目錄下執行:

npm link
複製代碼

就能夠了。若是你執行命令後,顯示相似安裝npm包的提示,就說明連接成功了。在執行一下 meo-cli,就能夠看到使人欣慰的hello world了。這裏要注意一下首行的代碼

#!/usr/bin/env node
複製代碼

這段並非註釋,這段代碼是告訴你的腳本工具(bash/zsh), 下面的內容是要在node環境下運行的代碼。千萬不能省略!!!進行下一步以前,讓咱們下來回顧一下vue-cli腳手架是怎麼使用的。

圖片

能夠看出來,腳手架會提供一個問答交互的方式,讓使用者輸入或選擇參數,而後根據獲取的參數作出相應的動做(action)。

STEP3: 交互式腳手架的另外一個特色是靈活,使用者能夠根據本身的需求,能夠清晰的去選擇讓腳手架作什麼,不作什麼。要實現這樣的功能,就是要用到開頭提到的插件:

  • commander
  • inquirer

commander是能夠用來獲取命令行的參數,並對參數作響應函數,inquirer則能夠爲咱們提供一個’問答’式的交互體驗。咱們修改一下index.js的代碼:

// index.js
#!/usr/bin/env node

const { program } = require('commander');
program.version('1.0.0')

program.parse(process.argv)
複製代碼

而後咱們在命令行輸入:

meo-cli -V  // 1,0,0
複製代碼

就會獲得版本號信息。輸入

meo-cli -h
複製代碼

圖片

就會獲得這樣的幫助信息。

這和咱們使用其餘腳手架的體驗是同樣的。

這樣的體驗要歸功於commander.js

commander用法以下:.command(‘init [name]’, ‘init a project’, opts) 功能:註冊一個命令
第一個參數:設置的命令的名稱,後面能夠跟參數,<> 表示必選參數,[]表示可選參數

第二個參數: 命令的描述,可選,注意,當有第二個參數時,不能顯示的調用action做爲命令的回調,須要使用獨立的可執行文件做爲命令

第三個參數:配置參數,如noHelp,isDefault等 .option(’-n, --name | [name]’, ‘desc’, ‘GK’)

功能:定義命令選項,(相似命令的額外參數, 用於輔助命令)

.description(‘this is a command desc’)
功能:命令的描述, 同時會應用到命令的幫助信息中,使用help命令時會顯示
.action(cb):命令的回調函數

.parse() 命令行參數解析, 一般用於最後 e.g.

圖片

圖片

上面的代碼,定義了一個命令init, 對命令作了描述(description), 並對init命令作了額外參數-t, 表示初始化工程的類型。action是init命令的回調,在回調用打印了參數,即咱們定義一個工程,名字爲demo, 工程類型爲vue。這樣後續工做中就能夠用工程名拼接下載模板到本地的路徑,type=vue 表明咱們會去拉取vue的模板。STEP4: ok, 咱們已經能夠經過自定義命令獲取到參數,接下來就能夠拉取對應模板了。這時咱們就須要使用 download-git-repo 這個包,它能夠用來下載github, gitlab等遠程倉庫的代碼。用法以下:

download(repository, destination, options, callback)
複製代碼

repository: 遠程倉庫的地址。destination:下載到本地的路徑 options: 配置參數 callback: 回調函數 須要注意一下,遠程倉庫地址能夠寫成:

direct: http: //github.com/xxx
複製代碼

即須要完成的倉庫路徑,默認狀況下,會下載master分支,若是要下載其餘分支,在連接後加入分支名,

direct:http://github.com/name/xxx.git#my-branch
複製代碼

小技巧咱們能夠將模板分配到不一樣的分支,而後經過分支來下載不一樣模板。回調函數中會返回下載的結果,根據結果能夠作出不一樣狀態的處理。例如,根據返回狀態判斷是否下載成功,下載成功後提示是否要進行其餘操做(安裝項目依賴,後面會提到哦~)

到此,一個初始化工程的腳手架就完成了。是的,真沒騙人,命令行執行指定命令,獲取參數,拉取模板代碼,就這些~~。

可是, 還能夠作更多。

一般咱們在初始化一個項目時,會提示填寫項目名稱(已經作了),項目描述,做者等信息,這些都是使用者輸入的,也就是命令行的參數,並且會提供一個更友好的「你問我答」 的方式教你作事,不, 求你填寫信息😁😁。這時候就是inquirer上場的時候了,它經過極爲簡單函數方式來提供交互操做。例如,咱們要提示使用者輸入項目描述和做者信息,就能夠這樣寫,在action回調函數中

// ...
inquirer.prompt([
        {
            name: 'description',
            message: '請輸入項目描述'
        },
        {
            name: 'author',
            message: '請輸入項目做者',
            default: 'robot'
        }
    ])
    .then((res) => {})
複製代碼

name表示輸入的鍵名,輸入的值爲value, 即結果會以鍵值對的形式返回。default爲默認值,當直接回車跳過期,會使用默認值。若是但願默認值是空,能夠寫成 default:'' 或省略default。

inquirer.prompt() 的參數是一個對象數組,能夠這樣理解,inquirer是流程化的結構,流程能夠是等待輸入,列表選擇,confirm確認yes or no。例如選擇項目模板是,使用者能夠從提供的模板列表中選擇,而不是本身去輸入,就能夠這樣定義參數:

{
     name:'type',
     type: 'list',
     message: 'choose a type of project to init',
     choices: ['react', 'vue', 'h5'],
     default: 'react',
    }
複製代碼

type表示類型,choices爲列表數組,使用者能夠從react,vue,h5中選擇模板,默認會是react模板。

圖片

而後根據type的值去拼接git倉庫地址,下載對應模板。

更多inquirer的用法,你們能夠參考 github.com/SBoudrias/I… 來使用,這裏再也不贅述。

STEP5: 咱們已經經過交互方式拿到了項目描述,做者等信息,可是咱們的目的是將這些信息寫入到下載的模板中,也就是package.json中對應的description,author以及項目名稱name中。這要怎麼作呢?這就須要handlebars.js的幫助了,handlebars是一個強大的模板引擎,它能夠解析指定模板,而後根據參數渲染模板。由於咱們要將name, description, author寫入到package.json中,所以咱們要稍微修改一下模板文件:

圖片

如圖,將要填寫的字段用{{}} 方式表示,內容就是對應要寫入的變量名字,這和inquirer交互時拿到的字段要保持一致。固然,handlebars不止這麼簡單,更多的用法能夠參考官網 handlebarsjs.com/guide/

這樣,咱們就能夠在模板下載完成後作寫入工做了。

download(url,'./template', { clone: true }, (error) => {
  if(!error) {
    const packagePath = path.join(downloadPath, 'package.json');
    // 判斷是否有package.json, 要把輸入的數據回填到模板中
    if (fs.existsSync(packagePath)) {
        const content = fs.readFileSync(packagePath).toString();
        // handlebars 模板處理引擎
        const template = handlebars.compile(content);
        const result = template(param);
        fs.writeFileSync(packagePath, result);
        
    } else {
        console.log('failed! no package.json');
    }
  }
})
複製代碼

這樣在下載成功後,而且有package.json時纔會去寫入,不然給出錯誤提示。

然而…

咱們發現,到目前爲止,咱們的命令行的輸入一點也很差看,沒有下載中的提示,沒有五彩斑斕醒目的文字… ora 和 chalk這時就起做用了。ora能夠美化命令行的loading,你是轉圈圈,動態的小點點,仍是自定義的gif均可以知足你,chalk就可讓你的命令行文字有了顏色,失敗的紅色告警,成功的綠色提示,都沒問題。

下面是拉取倉庫模板的部分代碼:

const downloadPath = path.join(process.cwd(), name);
const param = {name, ...parameter};
const spinner = ora('正在下載模板, 請稍後...');
spinner.start();
download(
    // 直連下載,默認下載master
    `direct:http://git.code.oa.com/name/xxx.git#${type}-tpl`,
    downloadPath,
    { clone: true },
    (error) => {
        if (!error) {
            // success download
            spinner.succeed();
            const packagePath = path.join(downloadPath, 'package.json');
            // 判斷是否有package.json, 要把輸入的數據回填到模板中
            if (fs.existsSync(packagePath)) {
                const content = fs.readFileSync(packagePath).toString();
                // handlebars 模板處理引擎
                const template = handlebars.compile(content);
                const result = template(param);
                fs.writeFileSync(packagePath, result);
    console.log(chalk.green('success! 項目初始化成功!'));
          console.log(
                  chalk.greenBright('開啓項目') + '\n' +
                  chalk.greenBright('cd ' + name) + '\n' +
                  chalk.greenBright('start to dvelop~~~!')
               )
                
            } else {
                spinner.fail();
                console.log(chalk.red('failed! no package.json'));
                return;
            }
        } else {
            console.log(chalk.red('failed! 拉取模板失敗', error));
            return;
        }
    }
)
複製代碼

到此爲止,一個基礎的工程腳手架就完成了,它能夠經過簡單一條命令,根據輸入的參數,拉取不一樣模板代碼,並將用戶信息回填到模板中,提供了交互式的友好體驗。嗯~,挺香的,收工嘍!!

cd vue-demo
npm run serve
複製代碼

可是… 咱們會發現一個問題,使用vue-cli的時候,在最後會有個運行提示:

它並無提示咱們要npm install, 這說明下載模板的時候項目的依賴也同時安裝。可咱們的沒有這個功能,不行,搞起!!STEP6 : 模板下載好後,咱們要進入模板目錄,而後根據它的package.json安裝依賴,這裏咱們能夠豐富一下,讓使用者在安裝依賴時有選擇:1. 先不安裝依賴,稍後自行安裝, 2. 選擇安裝工具,這時最後的提示要給個npm intall的提示纔算完美。是否安裝依賴。即咱們須要從使用者那裏獲得一個confirm, 根據返回的true或false來決定是否進行下一項安裝。選擇安裝工具。若是使用者選擇安裝,就要提示他選擇安裝工具。這兩個交互一樣可使用inquirer來完成

const continueToInstall  = {
    type: 'confirm',
    name:'next',
    message: 'continue to install the project',
    default: true,
}
const installTool = {
    name: 'tool',
    type: 'list',
    message: 'choose the tool to install',
    choices: ['npm', 'tnpm', 'yarn'],
    default: 'npm',
}
複製代碼

這裏作了簡單的封裝:

const { next } = await inquirer.prompt(continueToInstall);
if (next) {
    const { tool } = await inquirer.prompt(installTool);
    // 安裝項目依賴
    const res = await installFunc({ cwd: downloadPath, command: tool });
    if (res && res.code === 0) {
        processSuccess(name, true, type);
    }
} else {
    processSuccess(name, false, type);
}
複製代碼

至此,一個簡單的工程腳手架就完成了。

小結

  1. 本篇文章按照step by step的方式,在每一步詳細的闡述了開發腳手架過程當中使用的工具和注意事項。
  2. 本篇文章闡述的方法只實現了基礎功能,好的腳手架還能夠作更多,如集成單元測試,第三方庫的選擇安裝,項目打包發佈等。更多符合項目特色的腳手架還須要根據實際項目去開發。

原做者:李澤剛

未經贊成,禁止轉載

相關文章
相關標籤/搜索