Node腳手架編寫初學者教程
編寫你的第一個腳手架
前言
隨著NodeJs的崛起,現代前端工程已經變得越來越複雜。前端框架如雨後春筍。從Backbone,Ember,Knockout,Spine,Batman框架的崛起到Angular,React,Vue平分天下,我們經歷太多程式碼風格的變革。
不可避免學習新的框架,複雜的環境又讓初學者很難入手。就拿React來說,我們要使用Babel,Webpack等模組輔助編譯,我們還需要Redux,Immutable,Mocha,Jest,Antd或者其他第三方包(如eslint,lodash,moment,debug,uuid,request,co,koa等)輔助業務需求。
目錄結構越來越複雜,為了讓每個專案使用的統一的技術和目錄結構,各大模組紛紛推出了自己的腳手架工具,比如redux-cli,create-react-app,vue-cli等。現在就讓我們以any-cli為例,編寫自己的腳手架工具吧。
核心原理
yoeman
搭建專案需要提供yoeman-generator
。yoeman-generator
本質上就是一個具備完整檔案結構的模板,使用者需要手動地把這些模板下載到本地,然後yoeman
就會根據這些模板自動生成各種不同的專案。
vue-cli提供了相當豐富的選項和設定功能,但是其本質也是從遠端倉庫把不同的模版拉取到本地,而並非是什麼“本地生成”的黑科技。
這樣看來,思路也就有了——首先建立不同的模板,然後腳手架根據使用者的指令引用模板生成實際專案。
模板既可以內建在腳手架當中,也可以部署在遠端倉庫。
內建在腳手架中,使用
node file
操作來把模板克隆到本地。優點是不用新建倉庫儲存模板。尤其是有多專案模板時候,比如
init pc
與init mobile
分別生成兩個不同專案,我們只需要一個倉庫儲存腳手架即可。第二個優點:無論腳手架還是模板變更,只需要提交一次。
部署在遠端倉庫,使用
git clone
把專案克隆到本地。優點是每次模板有程式碼變更時,無需讓使用者本地升級腳手架。
如果模板內建在腳手架裡,每次模板變更,因為是在同一倉庫,所以使用者需要升級腳手架。而部署在遠端倉庫則不同,我們只需要git clone即可獲取最新模板。
整體流程
按照標準慣例,先看下整體流程(github不支援流程圖flow型別展示,好搓,害的我又把流程程式碼改成這種挫樣式)
新增模板->輸入模板名->是否有重名模板?-新增成功:給出提示
刪除模板->輸入模板名->是否有模板?-刪除成功:給出提示
模板列表->列出所有模板
初始化模板-輸入模板名-是否有模板?-輸入模組名-克隆遠端倉庫到本地模組-切換分支:給出提示複製程式碼
技術要點
process.cwd()與__dirname
命令都位於腳手架中,而執行命令的地方常常在模板專案中。
比如腳手架路徑是/usr/local/lib/node_modules/any-cli/lib/
,執行全域性腳手架命令路徑是/Users/admin/test/
。
想要腳手架程式碼想讀腳手架目錄下的a.js
檔案寫入模板專案(執行全域性腳手架命令位置)b.js
中,那麼readFile的路徑為path.resolve(__dirname,'a.js')
,writeFile路徑為path.resolve(process.cwd(),'b.js')
。就這麼簡單。
bin
許多npm模組有可執行檔案希望被安裝到全域性系統路徑。
需要在package.json中提供一個bin欄位,它是一個命令名稱到檔案路徑的對映。如下:
"bin": {
"any": "./bin/any"
},複製程式碼
這樣會把any命令和本地可執行檔案./bin/any建立對映。也就是說,當你在命令列執行any命令時,會執行./bin/any可執行檔案。
全域性安裝,npm將會使用符號連結把這些檔案連結到/usr/local/bin目錄下,系統的二進位制命令全部在這裡。
如果是本地安裝,會連結到./node_modules/.bin目錄下。只有當前目錄執行any命令時,才會生效。
如果你只有一個可執行檔案,那麼它的名字應該和包名相同,此時只需要提供這個檔案路徑(字串),比如:
{
"name": "any-cli",
"version": "1.2.5",
"bin": "./bin/any"
}複製程式碼
等同於:
{
"name": "any-cli",
"version": "1.2.5",
"bin": {
"any-cli": "./bin/any"
}
}複製程式碼
npm link
本地目錄連結到全域性模組
npm link可以把本地目錄連結到全域性模組下。
對於開發模組者而言,這算是最有價值的命令了。比如我們開發
any-cli
模組時,需要在命令列中使用any
來測試我們的程式碼(開發中沒有釋出,也就無法全域性安裝模組)。不要擔心,使用npm link一切變得非常容易。比如我們
any-cli
專案package.json裡,有一條命令如下:"bin": { "any": "./bin/any" },複製程式碼
命令列中使用npm link
$ npm link複製程式碼
得到以下結果
/usr/local/bin/any -> /usr/local/lib/node_modules/any-cli/bin/any /usr/local/lib/node_modules/any-cli -> /Users/lihongji/work/any-cli複製程式碼
分別進入/usr/local/bin與/usr/local/lib/node_modules目錄下檢視。我們發現裡面分別多了
any
可執行檔案與any-cli
目錄。
這樣,每次本地倉庫有改動時,全域性命令也隨之更新。我們就可以邊開發邊測試了。本地目錄引用全域性模組
如果你還有其他模組
any-cli-test
依賴於any-cli
模組,你可以使用如下命令把全域性any
連結到當前模組下。$ cd ~/work/any-cli-test $ npm link any-cli # 把全域性模式的模組連結到本地複製程式碼
npm link test 命令會去
/usr/local/lib/node_modules
目錄下查詢 any-cli的模組,找到這個模組後把/usr/local/lib/node_modules/any-cli
的目錄連結到當前any-cli-test
下的./node_modules/any-cli
目錄上。現在任何 test 模組上的改動都會直接對映到 test-example 上來。
其他欄位:engine與engineStrict
node7.6.0開始支援async,如何保證使用者本地安裝node7.6.0以上版本呢複製程式碼
engine
你可以在本地安裝node特定版本:"engines": { "install-node": "7.6.0" }複製程式碼
安裝後,在本地node_modules/.bin目錄下會多一個node可執行檔案。本地的任何node命令都會使用這個版本的node可執行檔案。
你可以指定工作的node的版本:
{ "engines" : { "node" : ">=0.10.3 <0.12" }="" }<="" code="">0.12">複製程式碼
並且,像
dependensies
一樣,如果你不指定版本或者指定“*”作為版本,那麼所有版本的node都可以。如果指定一個
engines
欄位,那麼npm
會需要node
在裡面,如果“engines”被省略,npm會假定它在node上工作。你也可以用
engines
欄位來指定哪一個npm
版本能更好地初始化你的程式,如:{ "engines" : { "npm" : "~1.0.20" } }複製程式碼
除非使用者設定
engine-strict
標記,這個欄位只是建議值。engineStrict
如果你確定你的模組一定不會執行在你指定版本之外的node
或者npm
上,你可以在package.json
檔案中設定engineStrict:true
。它會重寫使用者的engine-strict
設定。
第三方包:pre-commit/node-config/commander/chalk
這幾個包分別用來處理提交執行指令碼,全域性配置檔案管理與命令列處理。會在程式碼中一一講解。
程式碼檔案
建立any-cli
專案
work$mkdir any-cli
work$cd any-cli
any-cli$git init && npm init複製程式碼
package.json內容
{
"name": "any-cli",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"pub": "npm version patch && npm publish",
"pre-commit": "eslint src"
},
"author": "antgod",
"devDependencies": {
"eslint": "^3.16.1",
"eslint-config-airbnb": "^12.0.0",
"eslint-plugin-babel": "^3.0.0",
"eslint-plugin-import": "^1.6.1",
"eslint-plugin-jsx-a11y": "^2.0.1",
"eslint-plugin-markdown": "*",
"eslint-plugin-react": "^6.3.0",
"eslint-tinker": "^0.3.2",
"pre-commit": "^1.2.2"
},
"dependencies": {
"chalk": "^1.1.3",
"child_process": "^1.0.2",
"commander": "^2.9.0",
"prompt": "^1.0.0"
},
"engines": {
"install-node": "7.6.0"
},
"pre-commit": [
"pre-commit"
],
"bin": {
"any": "./bin/any"
},
"license": "ISC"
}複製程式碼
- 欄位bin下面配置被當做命令列可執行命令。指向/bin下面的any檔案。
- 欄位engines用來當前目錄安裝7.6.0版本node,可直接使用async函式而無需要再使用co模組。
pre-commit用來做提交前程式碼檢查,執行pre-commit指令碼,也就是eslint。
在根目錄下建立/bin 資料夾,建立any檔案(無字尾名)。這個 /bin/any是整個腳手架的入口檔案,所以我們首先對它進行編寫。
# !/usr/bin/env node
const add = require('../src/command/add')
const list = require('../src/command/list')
const init = require('../src/command/init')
const del = require('../src/command/del')
const program = require('commander')
const { version } = require('../package')
// 定義當前版本
program
.version(version)
program.parse(process.argv)
if (!program.args.length) {
program.help()
}複製程式碼
執行npm link,把當前專案連結到全域性。這樣就可以直接在命令列使用any命令測試/bin/any下面的程式碼
如果沒有許可權,請自行百度,使用chmod 777為usr/local目錄新增許可權複製程式碼
any-cli@npm link
any-cli@any複製程式碼
我們繼續在/bin/any中新增程式碼
// 定義使用方法
program
.command('add')
.description('add template')
.alias('a')
.action(add)
program
.command('del')
.description('Delete a template')
.alias('d')
.action(del)
program
.command('list')
.description('List all the templates')
.alias('l')
.action(list)
program
.command('init')
.description('Generate a new project')
.alias('i')
.action(init)複製程式碼
command用來配置any命令的引數,alias配置縮寫,action配置執行什麼函式。其他就不用多說了吧,程式設計師你懂的。
commander 的具體使用方法在這裡就不展開了,可以直接到 [官網][2] 去看詳細的文件。複製程式碼
使用any命令,看到輸出如下,證明入口檔案已經編寫完成了。
Usage: any [options] [command]
Commands:
add|a add template
del|d Delete a template
list|l List all the templates
init|i Generate a new project
Options:
-h, --help output usage information
-V, --version output the version number複製程式碼
接著,我們建立src/command目錄,下面分別建立剛才的4個引數所對應的檔案。檔案內容是具體業務程式碼(分別對應增刪查初始化),在此不做介紹。請參考github連結。程式碼使用了函數語言程式設計,需要有點函式式基礎。