Node腳手架編寫初學者教程

瘋狂的小蘑菇發表於2017-06-26

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-generatoryoeman-generator本質上就是一個具備完整檔案結構的模板,使用者需要手動地把這些模板下載到本地,然後yoeman就會根據這些模板自動生成各種不同的專案。

vue-cli提供了相當豐富的選項和設定功能,但是其本質也是從遠端倉庫把不同的模版拉取到本地,而並非是什麼“本地生成”的黑科技。

這樣看來,思路也就有了——首先建立不同的模板,然後腳手架根據使用者的指令引用模板生成實際專案。

模板既可以內建在腳手架當中,也可以部署在遠端倉庫。

  • 內建在腳手架中,使用node file操作來把模板克隆到本地。

    優點是不用新建倉庫儲存模板。尤其是有多專案模板時候,比如init pcinit 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可以把本地目錄連結到全域性模組下。

    對於開發模組者而言,這算是最有價值的命令了。比如我們開發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連結。程式碼使用了函數語言程式設計,需要有點函式式基礎。

相關文章