天天看點

nodeJs實作一個自定義腳手架工具

什麼是Nodejs Cli應用?

簡單來說就是在指令行可以使用nodejs來執行的應用,例如:vue-cli、creat-react-app、webpack-cli等;在前端開發過程中我們會用到很多的工具,這些工具在安裝過後可以直接使用指令行執行;注意在全局安裝和在項目安裝不同。

// 全局安裝,直接執行指令
> npm install webpack webpack-cli -g
> webpack

// 項目安裝,需要借助npx執行
> npm install webpack webpack-cli --save-dev
> npx webpack
           

Nodejs Cli應用的工作流程!

1、啟動過程:

指令行執行指令 => 根據package.json中bin查詢入口 => 執行入口js檔案cli.js

2、執行過程:

指令行執行js檔案功能啟動=> 指令行詢問使用者問題 => 結合問題答案+模闆等檔案 => 生成結構檔案

Nodejs Cli應用的入口檔案:cli.js

1、入口檔案路徑 ,首先在package.json 中添加bin字段

{
  "name": "ncl",
  "main": "index.js",
  "bin": {
    "ncl": "./cli.js"   //入口檔案,ncl和name保持一緻
  },
  ...
}
           

2、入口檔案特定的檔案頭 ,在cli.js頂部輸入

3、入口檔案權限

// 如果是 Linux 或者 macOS 系統下還需要修改此檔案的讀寫權限為 755

// 具體就是通過 chmod 755 cli.js 實作修改

4、簡單測試子產品

npm link 可以将子產品連結到全局,也可以連結到使用該子產品的項目node_modules中;這樣在開發子產品的過程中,不用釋出到npm也可以使用子產品進行測試。

> npm link // 在自定義子產品項目的目錄執行,将子產品連接配接到全局
> ncl // 直接執行子產品,使用子產品名
           

注意:在 Window PowerShell 腳本中直接執行子產品名不會成功,而在 Windows指令腳本(cmd)中直接執行子產品名就可以成功,具體原因尚不清楚。

Nodejs Cli應用的示例

該示例的功能實作一個自定義的腳手架:在指令行詢問使用者一些簡單的問題作為參數,然後自動生成一些項目檔案。其中的檔案可以通過模闆生成,也可以傳遞資料到模闆。

nodeJs實作一個自定義腳手架工具

1、安裝一些依賴子產品

> npm install inquirer --save //nodejs環境下,實作指令行的使用者互動插件 
> npm install ejs --save //模闆引擎
           

2、cli.js中定義指令行詢問使用者問題

  • inquire.prompt進行指令行的使用者詢問操作
  • inquirer.prompt傳回值為一個promise對象
  • inquirer.prompt的參數為一個數組
const inquirer = require('inquirer')

inquirer.prompt([
    {
        type: 'input',
        name: 'name',
        message: '請輸入項目名稱?'
    }
])
.then(anwsers => {
    // anwsers: { name: "xxx" } //anwsers傳回一個結果對象
})
           

3、擷取模闆目錄和目标生成目錄

const path = require('path')


// 模闆目錄
// __dirname 擷取目前執行代碼檔案的絕對路徑
// tmplDir 為templates的絕對路徑
const tmplDir = path.join(__dirname, 'templates')

// 目标目錄
// process.cwd()傳回 Node.js 程序的目前工作目錄。
// process參考api文檔:http://nodejs.cn/api/process.html
const destDir = process.cwd()
           

4、模闆引擎渲染模闆

const ejs = require('ejs')
const path = require('path')

// 通過模闆引擎渲染檔案
// 參數1:fileDir為檔案的絕對路徑
// 參數2:渲染模闆所需變量,存在anwsers對象裡面
// 參數3:回調函數,result為新檔案
ejs.renderFile(fileDir, anwsers, (err, result) => {
  if (err) throw err

  // 将結果寫入目标檔案路徑
  fs.writeFileSync(fileDestDir, result)
})
// 一個package.json作為模闆的示例:
{
"name": "<%= name %>",
  "version": "<%= version %>",
  "description": "<%= description %>",
  "author": "<%= author %>",
  "bin": "cli.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "license": "ISC"
}
           

5、讀取目錄下的檔案

// 将模闆下的檔案全部轉換到目标目錄
  // 參數1:path
  // 參數2:回調函數,參數files為檔案相對路徑組成的數組
  fs.readdir(tmplDir, (err, files) => {
    if (err) throw err
    files.forEach(file => {
      // 通過模闆引擎渲染檔案
      // 處理 file 
      })
    })
  })
           

6、将檔案寫入路徑

// 将結果寫入目标檔案路徑
// 參數1:檔案的絕對路徑
// 參數2:檔案内容
fs.writeFileSync(fileDestDir, result)
           

模闆檔案相關

這裡是根據自己的需求将需要自動生成的檔案放到模闆目錄下,沒有變化的或者統一的檔案就不需要使用模闆引擎。

1、模闆路徑:templates;

2、通常将整理好的項目結構整體拷貝到templates下,例如:vue的示例源檔案、lint檔案、package.json等等;

nodeJs實作一個自定義腳手架工具

測試子產品執行

本地開發可以使用npm link關聯子產品目錄和依賴此子產品的項目node_modules目錄;也可以釋出到npm源上後直接安裝使用子產品。

1、關聯子產品:

> cd nodejs-cli-sample //Nodejs Cli應用的目錄
> npm link // 将子產品連接配接到全局
           

2、執行子產品:

> cd nodejs-cli-demo // 在項目目錄執行子產品
> ncl // 直接執行子產品,使用子產品名(ncl 是項目nodejs-cli-sample的名稱)
           

釋出Nodejs Cli應用

1、可以直接使用npm publish釋出到源上

> npm publish --registry=https://registry.xxxx
           

2、要考慮到npm源是否有寫權限,可以釋出到自己公司的npm源上或者yarn源上

// 淘寶鏡像源是隻讀的,publish不上去
// 釋出到yarn的鏡像源之後,使用淘寶鏡像源時可以手動同步加快子產品下載下傳速度
yarn publish --registry https://registry.yarnpkg.com/
           

完整示例代碼

1、NodeJs Cli應用cli.js 入口檔案

#!/usr/bin/env node


// Node CLI 應用入口檔案必須要有這樣的檔案頭
// 如果是 Linux 或者 macOS 系統下還需要修改此檔案的讀寫權限為 755
// 具體就是通過 chmod 755 cli.js 實作修改

const fs = require('fs')    // 檔案讀寫
const path = require('path')    // 路徑擷取
const inquirer = require('inquirer')    //指令行使用者互動
const ejs = require('ejs')  // 模闆引擎


// 腳手架的工作過程:啟動 => 指令行詢問使用者問題 => 結合問題答案+模闆 => 生成結構檔案

inquirer.prompt([
    {
        type: 'input',
        name: 'name',
        message: '請輸入項目名稱(\'\')'
    },
    {
        type: 'input',
        name: 'version',
        message: '請輸入項目版本号(1.0.0)'
    },
    {
        type: 'input',
        name: 'description',
        message: '請輸入項目備注(\'\')'
    },
    {
        type: 'input',
        name: 'author',
        message: '請輸入作者名稱(\'\')'
    }
])
.then(anwsers => {
    // anwsers: { name: "xxx" } //anwsers傳回一個結果對象

    // 模闆目錄絕對路徑
    const tmplDir = path.join(__dirname, 'templates')
    // 目标目錄
    const destDir = process.cwd()

    // 讀取目錄下所有檔案
    let readFiles = (dir) => {
        return new Promise((resolve, reject)=>{
            // 參數1:目錄路徑
            // 參數2:回調函數(錯誤對象,files為檔案相對路徑組成的數組)
            fs.readdir(dir, (err, files) => {
                if (err) reject(err)
                resolve(files)
            })
        })
    }

    // 處理模闆檔案
    let ejsRender = (file) => {
        return new Promise((resolve, reject)=>{
            // 模闆檔案絕對路徑
            let dir = path.join(tmplDir, file)
            // 參數1:檔案路徑
            // 參數2:資料對象
            // 參數3:回調函數(錯誤對象,渲染後的新檔案)
            ejs.renderFile(dir, anwsers, (err, result) => {
                if (err) reject(err)
                resolve(result)
            })
        })
    }


    // 1、先讀取目錄下所有檔案
    // 2、使用ejs渲染所有模闆
    // 3、再将新檔案寫到目标路徑
    readFiles(tmplDir).then((files)=>{
        files.forEach(file => {
            ejsRender(file).then((result)=>{
                // 目标檔案絕對路徑,file其實是檔案相對路徑
                let fileDestDir = path.join(destDir, file)
                // 将結果寫入目标檔案路徑
                // 參數1:檔案絕對路徑
                // 參數2:渲染後新檔案
                fs.writeFileSync(fileDestDir, result)
            },throwError)
        })

    },throwError)
})

/**
 * 錯誤處理函數
 * @param {*錯誤對象} error 
 */
function throwError(error){
    throw error
}
           

2、package.json 示例模闆檔案,使用的ejs模闆引擎

{
"name": "<%= name %>",
  "version": "<%= version %>",
  "description": "<%= description %>",
  "author": "<%= author %>",
  "bin": "cli.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "license": "ISC"
}
           

轉載自:https://segmentfault.com/a/1190000040246466

繼續閱讀