手摸手教你搭個腳手架
目錄
- 腳手架
- 全域性命令
- 互動式輸入
- 拉取遠端倉庫程式碼
- 優化
- 總結
1、腳手架
今天工地的磚有點燙,我抬頭望了望天,思考了半分鐘,決定為了以後搬磚方便,先搭個腳手架:
不好意思,拿錯了,是這個:
相信很多小夥伴都用過vue-cli、create-react-app或angular-cli之類的腳手架,一個命令列就能快速搭起專案框架,告別刀耕火種的複製貼上,分分鐘解放生產力。
作為一個有追求的切圖仔,這種摸魚神器,必須立馬安排。
好了,那麼問題來了:什麼是腳手架?
從表現形式上來看,腳手架主要有以下幾個特點:
- 一個能全域性執行的命令;
- 能實現互動式輸入,比如輸入專案名稱,選擇配置項等;
- 能自動拉取github或gitlab遠端倉庫的程式碼。
當然高階的腳手架肯定不止這麼點功能,不過我們先從最簡單的實現起。
2、全域性命令
2.1 基本原理
先拆包觀察一下,類似vue-cli
這類的全域性命令是如何實現的。
首先找到vue-cli
的安裝路徑,全域性安裝可以通過npm config get prefix
找到路徑,比如我的windows系統就是
C:\Users\使用者名稱\AppData\Roaming\npm:
可以看到這個目錄下有很多與全域性命令同名的檔案,以及一個node_modules
資料夾。我們全域性安裝的依賴包就在node_modules
裡。找到@vue\cli
,這個就是vue-cli
vue-cli
原始碼包中的package.json
有這樣一個關鍵配置:
"bin": {
"vue": "bin/vue.js"
},
這個bin/vue.js
指令碼必須以#!/usr/bin/env node
開頭,
當npm全域性安裝時,就會根據這個配置,生成對應的同名可執行檔案。
而當我們執行vue
時,系統就會以node程式來執行vue.js。
npm官方文件中提供了npm link
命令,用以讓使用者將自定義的腳手架生成一個全域性命令。
npm link
主要做了兩步操作:
- 一是在npm全域性安裝路徑下的node_modules資料夾裡生成一個連結檔案,這個連結檔案指向執行該命令的資料夾,也就是我們的腳手架原始碼資料夾;
- 二是在npm全域性安裝路徑下生成與配置bin裡同名的可執行檔案。
所以,通過npm link
,我們就可以生成一個node全域性命令。
npm link
官方文件:https://docs.npmjs.com/cli/v6/commands/npm-link。
2.2 全域性命令的實現
話不多說,先來擼一個全域性命令。
1)執行npm init
初始化一個package.json
:
2)在package.json
里加上bin
配置:
"bin": {
"test-cli": "./test.js"
},
3)新增對應的執行指令碼檔案test.js
:
#! /usr/bin/env node
console.log("Hello! My CLI!");
4)在package.json
同級目錄下執行npm link
5)在控制檯執行一下我們定義的全域性命令test-cli
,看到輸出結果:
完成!
3、互動式輸入
通過上述操作,使用者已經可以通過全域性命令執行到我們的test.js
檔案,剩下的功能我們就可以在js裡去自由發揮了。
Node社群有著數量龐大的第三方模組,藉由這些模組我們可以快速開發實現想要的功能。
inquirier
就是其中之一,目前在github上有14.5k的star,它的目標就是“致力於成為一個易於嵌入且美觀的命令列工具”。顧名思義,通過inquirier
模組,我們可以在命令列中實現與使用者的輸入互動。
我們需要知道使用者要建立的專案名稱是什麼,以及使用者想要下載哪個遠端倉庫的程式碼:
const inquirer = require("inquirer");
inquirer
.prompt([
{
type: "input",
name: "project",
message: "專案名稱",
},
{
type: "list",
name: "tpl",
message: "請選擇模板",
choices: ["vue", "react"],
}
])
.then((res) => {
console.log(res);
const { project, tpl } = res;
// project就是使用者輸入的專案名稱
// tpl就是使用者選擇的模板
});
這段程式碼就是為使用者提供了一個input輸入選項,來輸入專案名稱,以及一個list列表選項,來選擇要下載的模板(之後我們再根據這個模板名稱去對應的倉庫地址進行下載)。
inquirer
還有更多的選項功能,感興趣的小夥伴可以去官方文件上自由探索:https://github.com/SBoudrias/Inquirer.js。
4、拉取遠端倉庫程式碼
現在我們已經知道使用者要下載的是哪個模板程式碼,也知道這些模板程式碼對應的下載地址:
const stores = [
{
name: "vue",
url: "https://github.com/vuejs/vue.git"
},
{
name: "react",
url: "https://github.com/facebook/react.git"
}
]
拉取github或者gitlab遠端倉庫程式碼的第三方模組也有好幾個,我選用的是nodegit
,這個專案在github上目前有4.9k的star,用起來也很簡單:
const Git = require("nodegit");
/** 克隆遠端倉庫程式碼 */
// url: 原始碼倉庫地址; path: 要下載的目標路徑; cb: 下載結束後的回撥函式
const gitClone = (url, path, cb)=>{
console.log("正在下載遠端倉庫程式碼...")
console.log(url)
Git.Clone(url, path)
.then(function(res) {
console.log("下載完成")
cb(true)
})
.catch(function(err) { console.log("下載失敗"+err);cb(false) });
}
nodegit官網地址:
https://github.com/nodegit/nodegit
至此三個基本功能都已實現!
5、優化
深諳摸魚之道的我想了想,覺得還能更進一步,比如下載完原始碼,再幫我自動安裝下依賴包啦:
const process = require('child_process');
/** 安裝依賴包 */
const install = (path)=>{ // path是原始碼模板中package.json所在的路徑
console.log("正在安裝依賴包...")
const cmd = 'cd '+path+' && yarn';
process.exec(cmd, function(error, stdout, stderr) {
console.log(error);
console.log(stdout);
console.log(stderr);
console.log("安裝完成")
});
}
之前看到很多腳手架出場都自帶拉風的藝術字,作為一個有追求的切圖仔,這個必須安排!
這個也是用到了一個第三方模組figlet
(https://github.com/patorjk/figlet.js):
const figlet = require('figlet');
figlet('My CLI!', {horizontalLayout:"full"}, function(err, data) {
if (err) {
console.log('Something went wrong...');
console.dir(err);
return;
}
console.log(data)
// do something...
});
很多腳手架還提供了引數配置、幫助資訊等功能,這個大多是通過commander
模組實現的(https://github.com/tj/commander.js,20.7k star),這裡就不詳細展開了,我在demo專案裡也簡單實現了下,確實很強大很好用。
Demo已開源:https://github.com/youzouzou/testcli。
這個demo只是簡單地實現了腳手架的基本功能,探索了一下幾個node模組的用法,還有很多需要優化的點。進一步學習的最好辦法就是去看那些成熟的腳手架原始碼,然後去模仿去實踐,再結合實際情況,摸索出最適合自己團隊的方案。
6、總結
工欲善其事,必先利其器。
實際上腳手架並不拘泥於上面的實現形式,凡是能提高效率的工具,在某種意義上都可以稱之為腳手架。
模仿只是最初級的階段,創新才是真正的開始。