你可能用到過很多前端腳手架工具,有沒有試想過到底如何寫一個屬於你的腳手架呢?
腳手架依賴工具
commander.js 命令列工具
download-git-repo git倉庫程式碼下載
chalk 命令列輸出樣式美化
Inquirer.js 命令列互動
ora 命令列載入中效果
專案搭建
初始化專案
建立專案目錄後執行npm init
按照提示完成初始化專案。
安裝依賴
安裝上面我們提到過的這幾個腳手架依賴工具,執行npm install chalk commander download-git-repo inquirer ora --save
完成
安裝。
專案結構
專案初始化完成後,建立bin
檔案和commands
檔案。bin檔案為可執行命令入口目錄,commands則負責編寫一些命令互動。
bin 目錄下 react-cli 檔案
#!/usr/bin/env node
process.env.NODE_PATH = __dirname + `/../node_modules/`
const { resolve } = require(`path`)
const res = command => resolve(__dirname, `../commands/`, command)
const program = require(`commander`)
program.version(require(`../package`).version )
program.usage(`<command>`)
program.command(`init`)
.option(`-f, --foo`, `enable some foo`)
.description(`Generate a new project`)
.alias(`i`)
.action(() => {
require(res(`init`))
})
if(!program.args.length){
program.help()
}
複製程式碼
建立該react-cli.js
為可執行命令入口檔案,並且定義了一個`init`命令,執行命令後會去commands 目錄下尋找對應的init.js
檔案
commands 目錄下 init 檔案
const {prompt} = require(`inquirer`)
const program = require(`commander`)
const chalk = require(`chalk`)
const download = require(`download-git-repo`)
const ora = require(`ora`)
const fs = require(`fs`)
const path = require(`path`)
const option = program.parse(process.argv).args[0]
const defaultName = typeof option === `string` ? option : `react-project`
const tplList = require(`${__dirname}/../templates`)
const tplLists = Object.keys(tplList) || [];
const question = [
{
type: `input`,
name: `name`,
message: `Project name`,
default: defaultName,
filter(val) {
return val.trim()
},
validate(val) {
const validate = (val.trim().split(" ")).length === 1
return validate || `Project name is not allowed to have spaces `;
},
transformer(val) {
return val;
}
}, {
type: `list`,
name: `template`,
message: `Project template`,
choices: tplLists,
default: tplLists[0],
validate(val) {
return true;
},
transformer(val) {
return val;
}
}, {
type: `input`,
name: `description`,
message: `Project description`,
default: `React project`,
validate (val) {
return true;
},
transformer(val) {
return val;
}
}, {
type: `input`,
name: `author`,
message: `Author`,
default: `project author`,
validate (val) {
return true;
},
transformer(val) {
return val;
}
}
]
module.exports = prompt(question).then(({name, template, description, author}) => {
const projectName = name;
const templateName = template;
const gitPlace = tplList[templateName][`place`];
const gitBranch = tplList[templateName][`branch`];
const spinner = ora(`Downloading please wait...`);
spinner.start();
download(`${gitPlace}${gitBranch}`, `./${projectName}`, (err) => {
if (err) {
console.log(chalk.red(err))
process.exit()
}
fs.readFile(`./${projectName}/package.json`, `utf8`, function (err, data) {
if(err) {
spinner.stop();
console.error(err);
return;
}
const packageJson = JSON.parse(data);
packageJson.name = name;
packageJson.description = description;
packageJson.author = author;
var updatePackageJson = JSON.stringify(packageJson, null, 2);
fs.writeFile(`./${projectName}/package.json`, updatePackageJson, `utf8`, function (err) {
if(err) {
spinner.stop();
console.error(err);
return;
} else {
spinner.stop();
console.log(chalk.green(`project init successfully!`))
console.log(`
${chalk.bgWhite.black(` Run Application `)}
${chalk.yellow(`cd ${name}`)}
${chalk.yellow(`npm install`)}
${chalk.yellow(`npm start`)}
`);
}
});
});
})
})
複製程式碼
1.program.parse(process.argv)
可以解析執行init 時候傳入的引數, 我們可以拿到這個引數做為專案建立的目錄名,如果沒有傳入該引數則為其設定一個預設目錄名稱。
2. 命令列互動問答
question
陣列為互動命令配置,陣列中每一個物件都對應一個執行命令時候的一個問題type
為該提問的型別,name
為該問題的名字,可以在後面通過name拿到該問題的使用者輸入答案message
為問題的提示default
則為使用者沒輸入時的預設為其提供一個答案validate
方法可以校驗使用者輸入的內容,返回true時校驗通過,若不正確可以返回對應的字串提示文案transformer
為使用者輸入問題答案後將對應的答案展示到問題位置,需要有返回值,返回到字串為展示內容
具體使用文件
3. 問答結束的回撥
- prompt方法中then裡的引數是一個物件,可以由此拿到問題由name定義的使用者輸入內容。
- 根據使用者輸入的內容,可以對應為其生成下載模版,這裡使用
download-git-repo
工具來下載git倉庫程式碼 - download方法第一個引數為要下載程式碼倉庫位置,如果為GitHub程式碼倉庫只需要寫使用者名稱和專案名稱即可,如
`Hzy0913/react-template`
即為下載該倉庫master的程式碼,如果需要切換對應分支則在倉庫地址後面加入對應分支名,如`Hzy0913/react-template#complete`
。 - download方法第二個引數為生成下載檔案的檔名,我將他儲存在命令執行目錄下,檔名使用使用者輸入的名字,如引數為
`./projectName`
,即可在當前執行命令目錄下生成對應的檔名。 ora
模組可以為我們生成下載時候的旋轉圖示,ora方法傳入的第一個引數為等待時候的提示文案並生成例項,在例項物件上呼叫start()方法開始出現旋轉動畫和提示,stop()方法停止。- 模版下載好以後需要為
package.json
檔案生成使用者自定義輸入的內容,node的fs
模組的readFile方法可以幫助我們獲取生成檔案的內容,writeFile則可以寫入內容 - 最後完成後可以在命令列皮膚上使用console方法給出一些提示內容,
chalk
模組可以幫助我們美化輸出內容。
文章中腳手架示例程式碼可以見 build-react-cli
工具測試及釋出
- 在寫的過程中免不了在我們本地進行測試,因為本身專案為node的工具,我們可以在專案目錄下執行
node bin/react-cli
執行。 - 當然在釋出以後肯定不能使用該命令,此時在釋出前,新增
package.json
中的bin
物件,key為指令碼執行的名字,value為執行目錄,如"bin": {"build-react": "bin/react-cli"}
,即可在輸入build-react的時候等同於執行node bin/react-cli
命令,在我們全域性安裝腳手架的時候,bin物件裡面的內容即可變成全域性可執行命令。 - 釋出npm包,npm包釋出非常簡單,註冊npm賬號後本地登入即可,在專案目錄下執行
npm publish
即可釋出,注意包名不能與現有的npm裡的相同、每次釋出新版本的包時需要修改package.json
裡的版本號,釋出的包只有在24小時內可以刪除。