從 0 構建本身的腳手架/CLI知識體系(萬字) 🛠

搭建腳手架的目的就是快速的搭建項目的基本結構並提供項目規範和約定。目前平常工做中經常使用的腳手架有 vue-cli、create-react-app、angular-cli 等等,都是經過簡單的初始化命令,完成內容的快速構建。javascript

腳手架是咱們常用的工具,也是團隊提效的重要手段。因此係統性的掌握腳手架相關知識,對前端開發者來講是很是重要的,即便不少人從此不必定都會參與到各自部門或者公司的基建工做,可是系統性掌握好這個技能也能夠方便咱們後期的源碼閱讀。下面就一塊兒來了解一下吧 😉css

1、腳手架的簡單雛形 🐣

腳手架就是在啓動的時候詢問一些簡單的問題,而且經過用戶回答的結果去渲染對應的模板文件,基本工做流程以下:html

  1. 經過命令行交互詢問用戶問題
  2. 根據用戶回答的結果生成文件

例如咱們在使用 vue-cli 建立一個 vue 項目時的時候 👇前端

step1:運行建立命令vue

$ vue create hello-world
複製代碼

step2:詢問用戶問題java

cli-new-project.png

cli-select-features.png

step3:生成符合用戶需求的項目文件node

# 忽略部分文件夾
vue-project
├─ index.html
├─ src
│  ├─ App.vue
│  ├─ assets
│  │  └─ logo.png
│  ├─ components
│  │  └─ HelloWorld.vue
│  ├─ main.js
│  └─ router
│     └─ index.js
└─ package.json
複製代碼

參考上面的流程咱們能夠本身來 搭建一個簡單的腳手架雛形react

1. 在命令行啓動 cli

目標: 實如今命令行執行 my-node-cli 來啓動咱們的腳手架ios

1.1 新建項目目錄 my-node-cli

$ mkdir my-node-cli 
$ cd my-node-cli 
$ npm init # 生成 package.json 文件
複製代碼

1.2 新建程序入口文件 cli.js

$ touch cli.js # 新建 cli.js 文件
複製代碼

在 package.json 文件中指定入口文件爲 cli.js 👇git

{
  "name": "my-node-cli",
  "version": "1.0.0",
  "description": "",
  "main": "cli.js",
  "bin": "cli.js", // 手動添加入口文件爲 cli.js
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC"
}
複製代碼

此時項目目錄結構:

my-node-cli      
├─ cli.js        
└─ package.json     
複製代碼

打開 cli.js 進行編輯

#! /usr/bin/env node

// #! 符號的名稱叫 Shebang,用於指定腳本的解釋程序
// Node CLI 應用入口文件必需要有這樣的文件頭
// 若是是Linux 或者 macOS 系統下還須要修改此文件的讀寫權限爲 755
// 具體就是經過 chmod 755 cli.js 實現修改

// 用於檢查入口文件是否正常執行
console.log('my-node-cli working~')
複製代碼

1.3 npm link 連接到全局

$ npm link # or yarn link
複製代碼

執行完成 ✅

image.png

咱們就能夠來測試了,在命令行中輸入 my-node-cli 執行一下

$ my-node-cli
複製代碼

這裏咱們就看到命令行中打印了

my-node-cli working~
複製代碼

完成 ✔,接下來

2. 詢問用戶信息

實現與詢問用戶信息的功能須要引入 inquirer.js 👉 文檔看這裏

$ npm install inquirer --dev # yarn add inquirer --dev
複製代碼

接着咱們在 cli.js 來設置咱們的問題

#! /usr/bin/env node

const inquirer = require('inquirer')

inquirer.prompt([
  {
    type: 'input', //type: input, number, confirm, list, checkbox ... 
    name: 'name', // key 名
    message: 'Your name', // 提示信息
    default: 'my-node-cli' // 默認值
  }
]).then(answers => {
  // 打印互用輸入結果
  console.log(answers)
})

複製代碼

在命令行輸入 my-node-cli 看一下執行結果

image.png

這裏咱們就拿到了用戶輸入的項目名稱 { name: 'my-app' }, 👌

3. 生成對應的文件

3.1 新建模版文件夾

$ mkdir templates # 建立模版文件夾 
複製代碼

3.2 新建 index.html 和 common.css 兩個簡單的示例文件

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>
    <!-- ejs 語法 -->
    <%= name %>
  </title>
</head>
<body>
  <h1><%= name %></h1>
</body>

</html>
複製代碼
/* common.css */
body {
  margin: 20px auto;
  background-color: azure;
}
複製代碼

此時的目錄結構

my-node-cli           
├─ templates          
│  ├─ common.css      
│  └─ index.html      
├─ cli.js             
├─ package-lock.json  
└─ package.json       
複製代碼

3.3 接着完善文件生成邏輯

這裏藉助 ejs 模版引擎將用戶輸入的數據渲染到模版文件上

npm install ejs --save # yarn add ejs --save
複製代碼

完善後到 cli.js 👇

#! /usr/bin/env node

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

inquirer.prompt([
  {
    type: 'input', //type:input,confirm,list,rawlist,checkbox,password...
    name: 'name', // key 名
    message: 'Your name', // 提示信息
    default: 'my-node-cli' // 默認值
  }
]).then(answers => {
  // 模版文件目錄
  const destUrl = path.join(__dirname, 'templates'); 
  // 生成文件目錄
  // process.cwd() 對應控制檯所在目錄
  const cwdUrl = process.cwd();
  // 從模版目錄中讀取文件
  fs.readdir(destUrl, (err, files) => {
    if (err) throw err;
    files.forEach((file) => {
      // 使用 ejs 渲染對應的模版文件
      // renderFile(模版文件地址,傳入渲染數據)
      ejs.renderFile(path.join(destUrl, file), answers).then(data => {
        // 生成 ejs 處理後的模版文件
        fs.writeFileSync(path.join(cwdUrl, file) , data)
      })
    })
  })
})
複製代碼

一樣,在控制檯執行一下 my-node-cli ,此時 index.htmlcommon.css 已經成功建立 ✔

咱們打印一下當前的目錄結構 👇

my-node-cli           
├─ templates          
│  ├─ common.css      
│  └─ index.html      
├─ cli.js             
├─ common.css .................... 生成對應的 common.css 文件        
├─ index.html .................... 生成對應的 index.html 文件        
├─ package-lock.json  
└─ package.json       
複製代碼

打開生成的 index.html 文件看一下

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <!-- ejs 語法 -->
  <title>
    my-app
  </title>
</head>

<body>
  <h1>my-app</h1>
</body>

</html>
複製代碼

用戶輸入的 { name: 'my-app' } 已經添加到了生成的文件中了 ✌️

點此打開 👉 my-node-cli 源碼地址

2、熱門腳手架工具庫 🔧

實際生產中搭建一個腳手架或者閱讀其餘腳手架源碼的時候須要瞭解下面這些工具庫 👇

名稱 簡介
commander 命令行自定義指令
inquirer 命令行詢問用戶問題,記錄回答結果
chalk 控制檯輸出內容樣式美化
ora 控制檯 loading 樣式
figlet 控制檯打印 logo
easy-table 控制檯輸出表格
download-git-repo 下載遠程模版
fs-extra 系統fs模塊的擴展,提供了更多便利的 API,並繼承了fs模塊的 API
cross-spawn 支持跨平臺調用系統上的命令

重點介紹下面這些,其餘工具能夠查看說明文檔

1. commander 自定義命令行指令

更多用法 👉 中文文檔

簡單案例 👇

1.1 新建一個簡單的 Node Cli 項目

// package.json
{
  "name": "my-vue",
  "version": "1.0.0",
  "description": "",
  "bin": "./bin/cli.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "T-Roc",
  "license": "ISC",
  "devDependencies": {
    "commander": "^7.2.0"
  }
}
複製代碼

目錄結構:

npms-demo             
├─ bin                
│  └─ cli.js          
├─ package-lock.json  
└─ package.json              
複製代碼

1.3 引入 commander 編寫代碼

# 安裝依賴
npm install commander # yarn add commander 
複製代碼

完善 bin.js 代碼

#! /usr/bin/env node

const program = require('commander')

program
.version('0.1.0')
.command('create <name>')
.description('create a new project')
.action(name => { 
    // 打印命令行輸入的值
    console.log("project name is " + name)
})

program.parse()
複製代碼

1.3 npm link 連接到全局

  • 執行 npm link 將應用 my-vue 連接到全局
  • 完成以後,在命令行中執行 my-vue

看一下,命令行中的輸出內容 👇

~/Desktop/cli/npms-demo ->my-vue

Usage: my-vue [options] [command]

Options:
  -V, --version   output the version number
  -h, --help      display help for command

Commands:
  create <name>   create a new project
  help [command]  display help for command
複製代碼

這個時候就有了 my-vue 命令使用的說明信息,在 Commands 下面出現了咱們剛剛建立的 create 命令 create <name>,咱們在命令行中運行一下

~/Desktop/cli/npms-demo ->my-vue create my-app
project name is my-app
複製代碼

這個時候控制檯就打印出來 create 命令後面的 <name>my-app 👏

2. chalk 命令行美化工具

chalk(粉筆)能夠美化咱們在命令行中輸出內容的樣式,例如對重點信息添加顏色

2.1 安裝依賴

npm install chalk # yarn add chalk
複製代碼

2.2 基本使用

在 npms-demo 項目中打開 bin/cli.js

#! /usr/bin/env node

const program = require('commander')
const chalk = require('chalk')

program
.version('0.1.0')
.command('create <name>')
.description('create a new project')
.action(name => { 
    // 打印命令行輸入的值

    // 文本樣式
    console.log("project name is " + chalk.bold(name))

    // 顏色
    console.log("project name is " + chalk.cyan(name))
    console.log("project name is " + chalk.green(name))

    // 背景色
    console.log("project name is " + chalk.bgRed(name))

    // 使用RGB顏色輸出
    console.log("project name is " + chalk.rgb(4, 156, 219).underline(name));
    console.log("project name is " + chalk.hex('#049CDB').bold(name));
    console.log("project name is " + chalk.bgHex('#049CDB').bold(name))
})

program.parse()
複製代碼

在命令行中運行項目 my-vue create my-app 看一下效果

image.png

具體的樣式對照表以下 👇

image.png

3. inquirer 命令行交互工具

更多用法 👉 文檔地址

inquirer 在腳手架工具中的使用頻率是很是高的,其實在上文腳手架的簡單雛形中,咱們已經使用到了,這裏就不過多介紹了。

4. ora 命令行 loading 動效

更多用法 👉 文檔地址

// 自定義文本信息
const message = 'Loading unicorns'
// 初始化
const spinner = ora(message);
// 開始加載動畫
spinner.start();

setTimeout(() => {
    // 修改動畫樣式

    // Type: string
    // Default: 'cyan'
    // Values: 'black' | 'red' | 'green' | 'yellow' | 'blue' | 'magenta' | 'cyan' | 'white' | 'gray'
    spinner.color = 'red';    
    spinner.text = 'Loading rainbows';

    setTimeout(() => {
        // 加載狀態修改
        spinner.stop() // 中止
        spinner.succeed('Loading succeed'); // 成功 ✔
        // spinner.fail(text?); 失敗 ✖
        // spinner.warn(text?); 提示 ⚠
        // spinner.info(text?); 信息 ℹ
    }, 2000);
}, 2000);
複製代碼

命令行是輸出效果以下

QQ20210516-173914-HD.gif

5. cross-spawn 跨平臺 shell 工具

更多用法 👉 文檔地址

在腳手架裏面,能夠用來自動執行 shell 命令,例如:

#! /usr/bin/env node 

const spawn = require('cross-spawn');
const chalk = require('chalk')

// 定義須要按照的依賴
const dependencies = ['vue', 'vuex', 'vue-router'];

// 執行安裝
const child = spawn('npm', ['install', '-D'].concat(dependencies), { 
    stdio: 'inherit' 
});

// 監聽執行結果
child.on('close', function(code) {
    // 執行失敗
    if(code !== 0) {
        console.log(chalk.red('Error occurred while installing dependencies!'));
        process.exit(1);
    }
    // 執行成功
    else {
        console.log(chalk.cyan('Install finished'))   
    }
})
複製代碼

一樣的在命令行執行一下 my-vue 看一下執行結果

image.png

成功安裝 👍

3、搭建本身的腳手架 🏗

先給咱們的腳手架起個名字吧,正好祝融號登錄了火星,不如就叫:zhurong-cli 😆

.-') _ ('-. .-.             _  .-') .-') _             
  (  OO) )( OO )  /            ( \( -O )                  ( OO ) )            
,(_)----. ,--. ,--. ,--. ,--.   ,------.  .-'),-----. ,--./ ,--,'  ,----.     
|       | |  | |  | |  | |  |   |   /`. '( OO'  .-.  '| \ | |\ '  .-./-') '--.   /  |   .|  | |  | | .-') | / | |/ | | | || \| | )| |_( O- ) (_/ / | | | |_|( OO )| |_.' |\_) |  |\|  ||  .     |/ |  | .--, \ 
 /   /___ |  .-.  | |  | | `-' /| . '.' \ | | | || |\ | (| | '. (_/ 
|        ||  | |  |(' '-'(_.-' |  |\  \    `' '-' '|  | \   |  |  '--'  |  
`--------'`--' `--' `-----'    `--' '--' `-----' `--' `--'   `------' 複製代碼

須要實現哪些基本功能:

  1. 經過 zr create <name> 命令啓動項目
  2. 詢問用戶須要選擇須要下載的模板
  3. 遠程拉取模板文件

搭建步驟拆解:

  1. 建立項目
  2. 建立腳手架啓動命令(使用 commander)
  3. 詢問用戶問題獲取建立所需信息(使用 inquirer)
  4. 下載遠程模板(使用 download-git-repo)
  5. 發佈項目

1. 建立項目

參照前面的例子,先建立一個簡單的 Node-Cli 結構

zhurong-cli           
├─ bin                
│  └─ cli.js  # 啓動文件 
├─ README.md          
└─ package.json       
複製代碼

配置腳手架啓動文件

{
  "name": "zhurong-cli",
  "version": "1.0.0",
  "description": "simple vue cli",
  "main": "index.js",
  "bin": {
    "zr": "./bin/cli.js" // 配置啓動文件路徑,zr 爲別名
  },
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": {
    "name": "T-Roc",
    "email": "lxp_work@163.com"
  },
  "license": "MIT"
}
複製代碼

簡單編輯一下咱們的 cli.js

#! /usr/bin/env node

console.log('zhurong-cli working ~')
複製代碼

爲了方便開發調試,使用 npm link 連接到全局

~/Desktop/cli/zhurong-cli ->npm link
npm WARN zhurong-cli@1.0.0 No repository field.

up to date in 1.327s
found 0 vulnerabilities

/usr/local/bin/zr -> /usr/local/lib/node_modules/zhurong-cli/bin/cli.js
/usr/local/lib/node_modules/zhurong-cli -> /Users/Desktop/cli/zhurong-cli
複製代碼

完成以後,接着測試一下

~/Desktop/cli/zhurong-cli ->zr
zhurong-cli working ~ # 打印內容
複製代碼

OK,獲得了咱們想要的打印內容,接下來

2. 建立腳手架啓動命令

簡單分析一下咱們要怎麼作?

  1. 首先咱們要藉助 commander 依賴去實現這個需求
  2. 參照 vue-cli 經常使用的命令有 create、config 等等,在最新版本中可使用 vue ui 進行可視化建立
  3. 若是建立的存在,須要提示是否覆蓋

如今開始吧 😉

2.1 安裝依賴

$ npm install commander --save
複製代碼

安裝完成以後 👇

2.2 建立命令

打開 cli.js 進行編輯

#! /usr/bin/env node

const program = require('commander')

program
  // 定義命令和參數
  .command('create <app-name>')
  .description('create a new project')
  // -f or --force 爲強制建立,若是建立的目錄存在則直接覆蓋
  .option('-f, --force', 'overwrite target directory if it exist')
  .action((name, options) => {
    // 打印執行結果
    console.log('name:',name,'options:',options)
  })
  
program
   // 配置版本號信息
  .version(`v${require('../package.json').version}`)
  .usage('<command> [option]')
  
// 解析用戶執行命令傳入參數
program.parse(process.argv);
複製代碼

在命令行輸入 zr,檢查一下命令是否建立成功

~/Desktop/cli/zhurong-cli ->zr
Usage: zr <command> [option]

Options:
  -V, --version                output the version number
  -h, --help                   display help for command

Commands:
  create [options] <app-name>  create a new project
  help [command]               display help for command
複製代碼

咱們能夠看到 Commands 下面已經有了 create [options] <app-name>,接着執行一下這個命令

~/Desktop/cli/zhurong-cli ->zr create
error: missing required argument 'app-name'

~/Desktop/cli/zhurong-cli ->zr create my-project
執行結果 >>> name: my-project options: {}

~/Desktop/cli/zhurong-cli ->zr create my-project -f
執行結果 >>> name: my-project options: { force: true }

~/Desktop/cli/zhurong-cli ->zr create my-project --force
執行結果 >>> name: my-project options: { force: true }
複製代碼

成功拿到命令行輸入信息 👍

2.3 執行命令

建立 lib 文件夾並在文件夾下建立 create.js

// lib/create.js

module.exports = async function (name, options) {
  // 驗證是否正常取到值
  console.log('>>> create.js', name, options)
}
複製代碼

在 cli.js 中使用 create.js

// bin/cli.js

......
program
  .command('create <app-name>')
  .description('create a new project')
  .option('-f, --force', 'overwrite target directory if it exist') // 是否強制建立,當文件夾已經存在
  .action((name, options) => {
    // 在 create.js 中執行建立任務
    require('../lib/create.js')(name, options)
  })
......
複製代碼

執行一下 zr create my-project,此時在 create.js 正常打印了咱們出入的信息

~/Desktop/cli/zhurong-cli ->zr create my-project
>>> create.js
my-project {}
複製代碼

在建立目錄的時候,須要思考一個問題:目錄是否已經存在?

  1. 若是存在
    • { force: true } 時,直接移除原來的目錄,直接建立
    • { force: false } 時 詢問用戶是否須要覆蓋
  2. 若是不存在,直接建立

這裏用到了 fs 的擴展工具 fs-extra,先來安裝一下

# fs-extra 是對 fs 模塊的擴展,支持 promise 
$ npm install fs-extra --save
複製代碼

咱們接着完善一下 create.js 內部的實現邏輯

// lib/create.js

const path = require('path')
const fs = require('fs-extra')

module.exports = async function (name, options) {
  // 執行建立命令

  // 當前命令行選擇的目錄
  const cwd  = process.cwd();
  // 須要建立的目錄地址
  const targetAir  = path.join(cwd, name)

  // 目錄是否已經存在?
  if (fs.existsSync(targetAir)) {

    // 是否爲強制建立?
    if (options.force) {
      await fs.remove(targetAir)
    } else {
      // TODO:詢問用戶是否肯定要覆蓋
    }
  }
}
複製代碼

詢問部分的邏輯,咱們將在下文繼續完善

2.3 建立更多命令

若是想添加其餘命令也是一樣的處理方式,這裏就不擴展說明了,示例以下 👇

// bin/cli.js

// 配置 config 命令
program
  .command('config [value]')
  .description('inspect and modify the config')
  .option('-g, --get <path>', 'get value from option')
  .option('-s, --set <path> <value>')
  .option('-d, --delete <path>', 'delete option from config')
  .action((value, options) => {
    console.log(value, options)
  })

// 配置 ui 命令
program
  .command('ui')
  .description('start add open roc-cli ui')
  .option('-p, --port <port>', 'Port used for the UI Server')
  .action((option) => {
    console.log(option)
  })
複製代碼

2.4 完善幫助信息

咱們先看一下 vue-cli 執行 --help 打印的信息

image.png

對比 zr --help 打印的結果,結尾處少了一條說明信息,這裏咱們作補充,重點須要注意說明信息是帶有顏色的,這裏就須要用到咱們工具庫裏面的 chalk 來處理

// bin/cli.js

program
  // 監聽 --help 執行
  .on('--help', () => {
    // 新增說明信息
    console.log(`\r\nRun ${chalk.cyan(`zr <command> --help`)} for detailed usage of given command\r\n`)
  })

複製代碼

2.5 打印個 Logo

若是此時咱們想給腳手架整個 Logo,工具庫裏的 figlet 就是幹這個的 😎

// bin/cli.js

program
  .on('--help', () => {
    // 使用 figlet 繪製 Logo
    console.log('\r\n' + figlet.textSync('zhurong', {
      font: 'Ghost',
      horizontalLayout: 'default',
      verticalLayout: 'default',
      width: 80,
      whitespaceBreak: true
    }));
    // 新增說明信息
    console.log(`\r\nRun ${chalk.cyan(`roc <command> --help`)} show details\r\n`)
  })

複製代碼

咱們再看看此時的 zr --help 打印出來的是個什麼樣子

WX20210519-224306@2x.png

看起來仍是挺不錯的,哈哈 😄

3. 詢問用戶問題獲取建立所需信息

這裏召喚咱們的老朋友 inquirer,讓他來幫咱們解決命令行交互的問題

接下來咱們要作的:

  1. 上一步遺留:詢問用戶是否覆蓋已存在的目錄
  2. 用戶選擇模板
  3. 用戶選擇版本
  4. 獲取下載模板的連接

3.1 詢問是否覆蓋已存在的目錄

這裏解決上一步遺留的問題:

  1. 若是目錄已存在
    • { force: false } 時 詢問用戶是否須要覆蓋

邏輯實際上已經完成,這裏補充一下詢問的內容

首選來安裝一下 inquirer

$ npm install inquirer --save
複製代碼

而後詢問用戶是否進行 Overwrite

// lib/create.js

const path = require('path')

// fs-extra 是對 fs 模塊的擴展,支持 promise 語法
const fs = require('fs-extra')
const inquirer = require('inquirer')

module.exports = async function (name, options) {
  // 執行建立命令

  // 當前命令行選擇的目錄
  const cwd  = process.cwd();
  // 須要建立的目錄地址
  const targetAir  = path.join(cwd, name)

  // 目錄是否已經存在?
  if (fs.existsSync(targetAir)) {

    // 是否爲強制建立?
    if (options.force) {
      await fs.remove(targetAir)
    } else {

      // 詢問用戶是否肯定要覆蓋
      let { action } = await inquirer.prompt([
        {
          name: 'action',
          type: 'list',
          message: 'Target directory already exists Pick an action:',
          choices: [
            {
              name: 'Overwrite',
              value: 'overwrite'
            },{
              name: 'Cancel',
              value: false
            }
          ]
        }
      ])

      if (!action) {
        return;
      } else if (action === 'overwrite') {
        // 移除已存在的目錄
        console.log(`\r\nRemoving...`)
        await fs.remove(targetAir)
      }
    }
  }
}
複製代碼

咱們來測試一下:

  1. 在當前目錄,即命令行中顯示的目錄下手動建立2個目錄,這裏隨便取名爲 my-project 和 my-project2
  2. 執行 zr create my-project,效果以下

image.png

  1. 執行 zr create my-project2 --f,能夠直接看到 my-project2 被移除

⚠️注意:爲何這裏只作移除? 由於後面獲取到模板地址後,下載的時候會直接建立項目目錄

3.2 如何獲取模版信息

模板我已經上傳到遠程倉庫:github.com/zhurong-cli

WX20210520-221040.png

vue3.0-template 版本信息 👇

WX20210520-220540.png

vue-template 版本信息 👇

WX20210520-221400.png

github 提供了

咱們在 lib 目錄下建立一個 http.js 專門處理模板和版本信息的獲取

// lib/http.js

// 經過 axios 處理請求
const axios = require('axios')

axios.interceptors.response.use(res => {
  return res.data;
})


/** * 獲取模板列表 * @returns Promise */
async function getRepoList() {
  return axios.get('https://api.github.com/orgs/zhurong-cli/repos')
}

/** * 獲取版本信息 * @param {string} repo 模板名稱 * @returns Promise */
async function getTagList(repo) {
  return axios.get(`https://api.github.com/repos/zhurong-cli/${repo}/tags`)
}

module.exports = {
  getRepoList,
  getTagList
}
複製代碼

3.3 用戶選擇模板

咱們專門新建一個 Generator.js 來處理項目建立邏輯

// lib/Generator.js

class Generator {
  constructor (name, targetDir){
    // 目錄名稱
    this.name = name;
    // 建立位置
    this.targetDir = targetDir;
  }

  // 核心建立邏輯
  create(){

  }
}

module.exports = Generator;
複製代碼

在 create.js 中引入 Generator 類

// lib/create.js

...
const Generator = require('./Generator')

module.exports = async function (name, options) {
  // 執行建立命令

  // 當前命令行選擇的目錄
  const cwd  = process.cwd();
  // 須要建立的目錄地址
  const targetAir  = path.join(cwd, name)

  // 目錄是否已經存在?
  if (fs.existsSync(targetAir)) {
    ...
  }

  // 建立項目
  const generator = new Generator(name, targetAir);

  // 開始建立項目
  generator.create()
}

複製代碼

接着來寫詢問用戶選擇模版都邏輯

// lib/Generator.js

const { getRepoList } = require('./http')
const ora = require('ora')
const inquirer = require('inquirer')

// 添加加載動畫
async function wrapLoading(fn, message, ...args) {
  // 使用 ora 初始化,傳入提示信息 message
  const spinner = ora(message);
  // 開始加載動畫
  spinner.start();

  try {
    // 執行傳入方法 fn
    const result = await fn(...args);
    // 狀態爲修改成成功
    spinner.succeed();
    return result; 
  } catch (error) {
    // 狀態爲修改成失敗
    spinner.fail('Request failed, refetch ...')
  } 
}

class Generator {
  constructor (name, targetDir){
    // 目錄名稱
    this.name = name;
    // 建立位置
    this.targetDir = targetDir;
  }

  // 獲取用戶選擇的模板
  // 1)從遠程拉取模板數據
  // 2)用戶選擇本身新下載的模板名稱
  // 3)return 用戶選擇的名稱

  async getRepo() {
    // 1)從遠程拉取模板數據
    const repoList = await wrapLoading(getRepoList, 'waiting fetch template');
    if (!repoList) return;

    // 過濾咱們須要的模板名稱
    const repos = repoList.map(item => item.name);

    // 2)用戶選擇本身新下載的模板名稱
    const { repo } = await inquirer.prompt({
      name: 'repo',
      type: 'list',
      choices: repos,
      message: 'Please choose a template to create project'
    })

    // 3)return 用戶選擇的名稱
    return repo;
  }

  // 核心建立邏輯
  // 1)獲取模板名稱
  // 2)獲取 tag 名稱
  // 3)下載模板到模板目錄
  async create(){

    // 1)獲取模板名稱
    const repo = await this.getRepo()
    
    console.log('用戶選擇了,repo=' + repo)
  }
}

module.exports = Generator;
複製代碼

測試一下,看看如今是個什麼樣子

image.png

我選擇了默認的 vue-template,此時

image.png

成功拿到模板名稱 repo 的結果 ✌️

3.4 用戶選擇版本

過程和 3.3 同樣

// lib/generator.js

const { getRepoList, getTagList } = require('./http')
...

// 添加加載動畫
async function wrapLoading(fn, message, ...args) {
  ...
}

class Generator {
  constructor (name, targetDir){
    // 目錄名稱
    this.name = name;
    // 建立位置
    this.targetDir = targetDir;
  }

  // 獲取用戶選擇的模板
  // 1)從遠程拉取模板數據
  // 2)用戶選擇本身新下載的模板名稱
  // 3)return 用戶選擇的名稱

  async getRepo() {
    ...
  }

  // 獲取用戶選擇的版本
  // 1)基於 repo 結果,遠程拉取對應的 tag 列表
  // 2)用戶選擇本身須要下載的 tag
  // 3)return 用戶選擇的 tag

  async getTag(repo) {
    // 1)基於 repo 結果,遠程拉取對應的 tag 列表
    const tags = await wrapLoading(getTagList, 'waiting fetch tag', repo);
    if (!tags) return;
    
    // 過濾咱們須要的 tag 名稱
    const tagsList = tags.map(item => item.name);

    // 2)用戶選擇本身須要下載的 tag
    const { tag } = await inquirer.prompt({
      name: 'tag',
      type: 'list',
      choices: tagsList,
      message: 'Place choose a tag to create project'
    })

    // 3)return 用戶選擇的 tag
    return tag
  }

  // 核心建立邏輯
  // 1)獲取模板名稱
  // 2)獲取 tag 名稱
  // 3)下載模板到模板目錄
  async create(){

    // 1)獲取模板名稱
    const repo = await this.getRepo()

    // 2) 獲取 tag 名稱
    const tag = await this.getTag(repo)
     
    console.log('用戶選擇了,repo=' + repo + ',tag='+ tag)
  }
}

module.exports = Generator;
複製代碼

測試一下,執行 zr create my-project

image.png

選擇好了以後,看看打印結果

image.png

到此詢問的工做就結束了,能夠進行模板下載了

4. 下載遠程模板

下載遠程模版須要使用 download-git-repo 工具包,實際上它也在咱們上面列的工具菜單上,可是在使用它的時候,須要注意一個問題,就是它是不支持 promise的,因此咱們這裏須要使用 使用 util 模塊中的 promisify 方法對其進行 promise 化

4.1 安裝依賴與 promise 化

$ npm install download-git-repo --save
複製代碼

進行 promise 化處理

// lib/Generator.js

...
const util = require('util')
const downloadGitRepo = require('download-git-repo') // 不支持 Promise

class Generator {
  constructor (name, targetDir){
    ...

    // 對 download-git-repo 進行 promise 化改造
    this.downloadGitRepo = util.promisify(downloadGitRepo);
  }
  
  ...
}
複製代碼

4.2 核心下載功能

接着,就是模板下載部分的邏輯了

// lib/Generator.js

...
const util = require('util')
const path = require('path')
const downloadGitRepo = require('download-git-repo') // 不支持 Promise

// 添加加載動畫
async function wrapLoading(fn, message, ...args) {
  ...
}

class Generator {
  constructor (name, targetDir){
    ...

    // 對 download-git-repo 進行 promise 化改造
    this.downloadGitRepo = util.promisify(downloadGitRepo);
  }
  ...
  
  // 下載遠程模板
  // 1)拼接下載地址
  // 2)調用下載方法
  async download(repo, tag){

    // 1)拼接下載地址
    const requestUrl = `zhurong-cli/${repo}${tag?'#'+tag:''}`;

    // 2)調用下載方法
    await wrapLoading(
      this.downloadGitRepo, // 遠程下載方法
      'waiting download template', // 加載提示信息
      requestUrl, // 參數1: 下載地址
      path.resolve(process.cwd(), this.targetDir)) // 參數2: 建立位置
  }

  // 核心建立邏輯
  // 1)獲取模板名稱
  // 2)獲取 tag 名稱
  // 3)下載模板到模板目錄
  // 4)模板使用提示
  async create(){

    // 1)獲取模板名稱
    const repo = await this.getRepo()

    // 2) 獲取 tag 名稱
    const tag = await this.getTag(repo)

    // 3)下載模板到模板目錄
    await this.download(repo, tag)
    
    // 4)模板使用提示
    console.log(`\r\nSuccessfully created project ${chalk.cyan(this.name)}`)
    console.log(`\r\n cd ${chalk.cyan(this.name)}`)
    console.log(' npm run dev\r\n')
  }
}

module.exports = Generator;
複製代碼

完成這塊,一個簡單的腳手架就完成了 ✅

來試一下效果如何,執行 zr create my-project

image.png

這個時候,咱們就能夠看到模板就已經建立好了 👏👏👏

zhurong-cli                 
├─ bin                      
│  └─ cli.js                
├─ lib                      
│  ├─ Generator.js          
│  ├─ create.js             
│  └─ http.js               
├─ my-project .............. 咱們建立的項目             
│  ├─ public                
│  │  ├─ favicon.ico        
│  │  └─ index.html         
│  ├─ src                   
│  │  ├─ assets             
│  │  │  └─ logo.png        
│  │  ├─ components         
│  │  │  └─ HelloWorld.vue  
│  │  ├─ App.vue            
│  │  └─ main.js            
│  ├─ README.md             
│  ├─ babel.config.js       
│  └─ package.json          
├─ README.md                
├─ package-lock.json        
└─ package.json             

複製代碼

5. 發佈項目

上面都是在本地測試,實際在使用的時候,可能就須要發佈到 npm 倉庫,經過 npm 全局安裝以後,直接到目標目錄下面去建立項目,如何發佈呢?

  1. 第一步,在 git 上建好倉庫
  2. 第二步,完善 package.json 中的配置
{
  "name": "zhurong-cli",
  "version": "1.0.4",
  "description": "",
  "main": "index.js",
  "bin": {
    "zr": "./bin/cli.js"
  },
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "files": [
    "bin",
    "lib"
  ],
  "author": {
    "name": "T-Roc",
    "email": "lxp_work@163.com"
  },
  "keywords": [
    "zhurong-cli",
    "zr",
    "腳手架"
  ],
  "license": "MIT",
  "dependencies": {
    "axios": "^0.21.1",
    "chalk": "^4.1.1",
    "commander": "^7.2.0",
    "download-git-repo": "^3.0.2",
    "figlet": "^1.5.0",
    "fs-extra": "^10.0.0",
    "inquirer": "^8.0.0",
    "ora": "^5.4.0"
  }
}

複製代碼
  1. 第三步,使用 npm publish 進行發佈,更新到時候,注意修改版本號

image.png

這樣就發佈成功了,咱們打開 npm 網站搜索一下 🔍

WX20210522-220454@2x.png

已經能夠找到它了,這樣咱們就能夠經過 npm 或者 yarn 全局安裝使用了

點此打開 👉 zhurong-cli 源碼地址

4、Yeoman:一個通用的腳手架系統

yeoman.png

Yeoman 最初發佈於 2012 年,它是一款高效、開源的 Web 應用腳手架(scaffolding)軟件,意在精簡軟件的開發過程。腳手架軟件用於實現項目中多種不一樣的工具和接口的協同使用,優化項目的生成過程。容許建立任何類型的應用程序(Web,Java,Python,C#等)。

Yeoman 其實是三個工具的總和:

  • yo --- 腳手架,自動生成工具
  • grunt、gulp --- 構建工具
  • bower、npm --- 包管理工具

使用 Yeoman 搭建腳手架很是簡單,Yeoman 提供了 yeoman-generator 讓咱們快速生成一個腳手架模板,咱們能夠經過各種 Generator 實現任何類型的項目搭建,下面咱們來試一下 🤓

1. Yeoman 基礎使用

Yeoman 是一套構建系統,在這裏咱們搭建腳手架須要使用的就是 yo 👇

1.1 全局範圍安裝 yo

$ npm install yo --global # or yarn global add yo
複製代碼

1.2 安裝對應的 generator

yo 搭配不一樣 generator-xxx 能夠建立對應的項目,例如 generator-webappgenerator-nodegenerator-vue 等等,這裏咱們使用 generator-node 來演示操做。

$ npm install generator-node --global # or yarn global add generator-node
複製代碼

1.3 經過 yo 運行 generator

$ mkdir yo-project
$ cd yo-project
$ yo node
複製代碼

這樣咱們就經過 yo + generator-node 快捷搭建一個 node 項目,目錄結構以下 👇

yo-project
├─ .editorconfig
├─ .eslintignore
├─ .travis.yml
├─ .yo-rc.json
├─ LICENSE
├─ README.md
├─ lib
│  ├─ __tests__
│  │  └─ testCli.test.js
│  └─ index.js
├─ package-lock.json
└─ package.json          

複製代碼

如何查找本身須要的 generator 呢?咱們能夠去官網 generators 列表搜索 👉 點此進入

image.png

這種使用方式真的很是的簡單方便,可是它的問題也很明顯--不夠靈活,畢竟不一樣的團隊在使用的技術棧上都有所差別,若是咱們想搭建本身想要的項目結構要怎麼處理呢? 接着往下看 👇

2. 自定義 Generator

自定義 Generator 實際上就是建立一個特定結構的 npm 包,這個特定的結構是這樣的 👇

generator-xxx ............ 自定義項目目錄  
├─ generators ............ 生成器目錄   
│  └─ app ................ 默認生成器目錄      
│     └─ index.js ........ 默認生成器實現
└─ package.json .......... 模塊包配置文件
複製代碼

或者這樣的 👇

generator-xxx   
├─ app           
│  └─ index.js     
├─ router        
│  └─ index.js   
└─ package.json  
複製代碼

這裏咱們須要注意的是,項目的名稱必須是 generator-<name> 格式,才能夠正常被 yo 識別出來,例如上面舉例使用的 generator-node。

2.1 建立項目

$ mkdir generator-simple # 建立項目
$ cd generator-simple    # 進入項目目錄
複製代碼

2.2 初始化 npm

$ npm init # or yarn init
複製代碼

一路 enter 以後咱們就生成好了 package.json,不過咱們還須要額外檢查一下:

  • name 屬性值須是 "generator-<name>"
  • keyword 中必須包含 yeoman-generator
  • files 屬性要指向項目的模板目錄。

完成上面的工做以後咱們看一下 package.json 是個什麼樣子

{
  "name": "generator-simple",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [ 
    "yeoman-generator" 
  ],
  "files": [
    "generators"
  ],
  "author": "ITEM",
  "license": "MIT"
}
複製代碼

⚠️注意:這裏若是使用的是第二種目錄結構,那麼 package.json 中須要作點修改 🔧

{
  "files": [
    "app",
    "router"
  ]
}
複製代碼

2.3 安裝 yeoman-generator

yeoman-generator 是 Yeoman 提供的一個 Generator 基類,讓咱們在建立自定義 Generator 的時候更加便捷。

$ npm install yeoman-generator --save # or yarn add yeoman-generator 
複製代碼

2.4 Generator 基類的使用說明

在介紹 Generator 基類以前,咱們先來實現一個簡單的 🌰

首先打開核心入口文件,編輯內容以下 👇

// ~/generators/app/index.js

// 此文件做爲 Generator 的核心入口
// 須要導出一個繼承自 Yeoman Generator 的類型
// Yeoman Generator 在工做時會自動調用咱們在此類型中定義的一些生命週期方法
// 咱們在這些方法中能夠經過調用父類提供的一些工具方法實現一些功能,例如文件寫入

const Generator = require('yeoman-generator');

module.exports = class extends Generator {
  // add your own methods
  method1() {
    console.log('I am a custom method');
  }
  method2() {
    console.log('I am a custom method2');
  }
};

複製代碼

完成以後,咱們經過 npm link 的方式把項目連接到全局

$ npm link # or yarn link
複製代碼

這樣咱們就能夠在全局去訪問到 generator-simple 項目了,咱們來試一下

$ yo simple
複製代碼

看一下控制檯的輸出

I am a custom method1
I am a custom method2
複製代碼

OK,是咱們想要的結果 😎

⚠️ 注意,若是運行yo simple 出現下面的錯誤

This generator (simple:app) 
requires yeoman-environment at least 3.0.0, current version is 2.10.3,
try reinstalling latest version of 'yo' or use '--ignore-version-check' option
複製代碼

能夠這樣處理:

方案一

# 卸載當前版本
npm uninstall yeoman-generator

# 安裝低版本的包
npm i yeoman-generator@4.13.0

# 執行
yo simple
複製代碼

方案二

# 全局安裝模塊
npm i -g yeoman-environment

# 新的執行方式(yoe沒有打錯)
yoe run simple
複製代碼

從上面的小 🌰 咱們能夠看到咱們自定義方法是自動順序執行,Generator 基類也提供了一些順序執行的方法,相似於生命週期同樣,咱們看一下有哪些 👇

  1. initializing -- 初始化方法(檢查狀態、獲取配置等)
  2. prompting -- 獲取用戶交互數據(this.prompt())
  3. configuring -- 編輯和配置項目的配置文件
  4. default -- 若是 Generator 內部還有不符合任意一個任務隊列任務名的方法,將會被放在 default 這個任務下進行運行
  5. writing -- 填充預置模板
  6. conflicts -- 處理衝突(僅限內部使用)
  7. install -- 進行依賴的安裝(eg:npm,bower)
  8. end -- 最後調用,作一些 clean 工做

2.5 開始咱們的自定義 Generator

咱們藉助 Generator 提供的方法,咱們對入口文件改造一下

// ~/generators/app/index.js

const Generator = require('yeoman-generator');

module.exports = class extends Generator {
  // yo 會自動調用該方法
  writing () {
    // 咱們使用 Generator 提供的 fs 模塊嘗試往目錄中寫入文件
    this.fs.write(
      // destinationPath() 基於項目地址
      this.destinationPath('temp.txt'), // 寫入地址
      Math.random().toString() // 寫入內容
    )
  }
};
複製代碼

運行一下看看

$ yo simple
複製代碼

這個時候,控制檯輸出出 create temp.txt,咱們打印一下目錄結構

generator-simple      
├─ generators         
│  └─ app             
│     └─ index.js     
├─ package-lock.json  
├─ package.json       
└─ temp.txt .............. writing 中建立的文件        
複製代碼

打開新建立的 temp.txt 瞅瞅

0.8115477932475306
複製代碼

能夠看到文件中寫入了一串隨機數。

在實際使用的時候,咱們須要經過模板去建立多個文件,這個時候咱們就須要這樣處理 👇

首先,建立模板文件目錄 ./generators/app/templates/,並在文件夾中新增一個模板文件 temp.html

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <!-- yo 支持 ejs 語法 -->
  <title><%= title %></title>
</head>
<body>
  <% if (success) { %>
    <h1>這裏是模版文件<%= title %></h1>
  <% } %>
</body>
</html>
複製代碼

而後,修改一下入口文件 👇

// ~/generators/app/index.js

const Generator = require('yeoman-generator');

module.exports = class extends Generator {
  // yo 會自動調用該方法
  writing () {
    // 咱們使用 Generator 提供的 fs 模塊嘗試往目錄中寫入文件
    // this.fs.write(
    // this.destinationPath('temp.txt'),
    // Math.random().toString()
    // )

    // 模版文件路徑,默認指向 templates
    const tempPath = this.templatePath('temp.html')
    // 輸出目標路徑
    const output = this.destinationPath('index.html')
    // 模板數據上下文
    const context = { title: 'Hello ITEM ~', success: true}

    this.fs.copyTpl(tempPath, output, context)
  }
};
複製代碼

完成以後yo simple 運行一下,這樣咱們就在根目錄下獲得了 index.html,打開看看 🤓

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <!-- 支持 ejs 語法 -->
  <title>Hello ITEM ~</title>
</head>
<body>
  
    <h1>這裏是模版文件Hello ITEM ~</h1>

</body>
</html>
複製代碼

ejs 寫入的變量,都已經被數據成功替換了 ✌️

接下來,咱們要如何經過命令行交互獲取用戶自定義的一些數據,例如:項目名稱、版本號等等。

這個就須要藉助 Generator 提供的 Promting 來處理命令行的一些交互

// ~/generators/app/index.js

const Generator = require('yeoman-generator');

module.exports = class extends Generator {
  // 在此方法中能夠調用父類的 prompt() 方法與用戶進行命令行詢問
  prompting(){
    return this.prompt([
      {
        type: 'input', // 交互類型
        name: 'name', 
        message: 'Your project name', // 詢問信息
        default: this.appname // 項目目錄名稱,這裏是 generator-simple
      }
    ])
    .then(answers => {
      console.log(answers) // 打印輸入內容
      this.answers = answers // 存入結果,能夠在後面使用
    })
  }
  // yo 會自動調用該方法
  writing () {
    ......
  }
};
複製代碼

保存以後,再運行 yo simple

image.png

咱們看到命令行詢問了 Your Project name ?,在用戶輸入完成以後,咱們拿到了 anwsers,這樣咱們就能夠在接下來的流程裏面去使用這個結果。

// ~/generators/app/index.js
...
// 模板數據上下文
 writing () {
    ...
    // 模板數據上下文
    const context = { title: this.answers.name, success: true}

    this.fs.copyTpl(tempPath, output, context)
  }
...
複製代碼

再運行一下 yo simple,查看輸出的 index.html

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <!-- 支持 ejs 語法 -->
  <title>my-project</title>
</head>
<body>
  
    <h1>這裏是模版文件my-project</h1>
  
</body>
</html>
複製代碼

咱們能夠看到用戶輸入的內容 { name: 'my-project' } 已經顯示在咱們的 index.html 裏面了 👌

點此打開 👉 generator-simple 源碼地址

yeoman 就介紹到這裏,接下來咱們來看另一款腳手架工具 -- plop 👇

5、plop:一款小而美的腳手架工具

plop 小在體積輕量,美在簡單易用

更多使用方法 👉 plop 使用文檔

咱們能夠將其直接集成到項目中,解決一下重複性的活着須要標準化的建立工做,下面咱們就來作個小案例,好比

咱們已經約定好了組件的建立規範

  • 組件名稱使用大駝峯
  • 樣式須要單獨擰出來寫
  • 須要搭配說明文檔

plop 的使用過程大體能夠拆解爲

  1. 安裝 plop,新增配置文件 plopfile.js
  2. 編輯 plop 配置文件
  3. 建立模板文件
  4. 執行建立任務

下面進入 coding 環節

1. 安裝 plop

首先用咱們的 zhurong-cli 初始化一個 vue 項目

# 全局安裝
$ npm install zhurong-cli -g 
# 建立 vue 項目
$ zr create plop-demo
複製代碼

咱們這裏爲了團隊統一使用,plop 直接就集成到項目之中

$ npm install plop --save-dev
複製代碼

項目目錄下面建立 plop 的配置文件 plopfile.js

2. 編輯 plop 配置文件

// ./plopfile.js

module.exports = plop => {
  plop.setGenerator('component', {
    // 描述
    description: 'create a component',
    // 詢問組件的名稱
    prompts: [
      {
        type: 'input',
        name: 'name',
        message: 'Your component name',
        default: 'MyComponent'
      }
    ],
    // 獲取到回答內容後續的動做
    actions: [
      //每個對象都是一個動做
      {
        type: 'add', // 表明添加文件
        // 被建立文件的路徑及名稱
        // name 爲用戶輸入的結果,使用 {{}} 使用變量
        // properCase: plop 自帶方法,將 name 轉換爲大駝峯
        path: 'src/components/{{ properCase name }}/index.vue',
        // 模板文件地址
        templateFile: 'plop-templates/component.vue.hbs'
      },
      {
        type: 'add',
        path: 'src/components/{{ properCase name }}/index.scss',
        templateFile: 'plop-templates/component.scss.hbs'
      },
      {
        type: 'add',
        path: 'src/components/{{ properCase name }}/README.md',
        templateFile: 'plop-templates/README.md.hbs'
      }
    ]
  })
}

複製代碼

上面用到 properCase 方法將 name 轉化爲大駝峯,其餘格式還包括 👇

  • camelCase: changeFormatToThis
  • snakeCase: change_format_to_this
  • dashCase/kebabCase: change-format-to-this
  • dotCase: change.format.to.this
  • pathCase: change/format/to/this
  • properCase/pascalCase: ChangeFormatToThis
  • lowerCase: change format to this
  • sentenceCase: Change format to this,
  • constantCase: CHANGE_FORMAT_TO_THIS
  • titleCase: Change Format To This

咱們看到上面已經引用了模板文件,實際上咱們還沒建立,接着建立一下

3. 建立模板文件

項目文件夾下面建立 plop-templates 文件夾,裏面建立對應的模板文件

plop-templates         
├─ README.md.hbs ............... 說明文檔模板     
├─ component.scss.hbs .......... 組件樣式模板
└─ component.vue.hbs ........... 組件模板
複製代碼

模板引擎咱們用到是 Handlebars ,更多語法說明 👉 Handlebars 中文網

編輯 component.scss.hbs

{{!-- ./plop-templates/component.scss.hbs --}} {{!-- dashCase/kebabCase: change-format-to-this --}} {{!-- name: 輸入模板名稱 --}} .{{ dashCase name }} { } 複製代碼

編輯 component.vue.hbs

{{!-- ./plop-templates/component.vue.hbs --}} <template> <div class="{{ dashCase name }}">{{ name }}</div> </template> <script> export default { name: '{{ properCase name }}', } </script> <style lang="scss"> @import "./index.scss"; </style> 複製代碼

編輯 README.md.hbs

{{!-- ./plop-templates/README.md.hbs --}} 這裏是組件 {{ name }} 的使用說明 複製代碼

補充說明:

  • 這裏模板都是最簡單實現,實際生產中能夠根據需求豐富模板內容
  • 模板中的 dashCase、properCase 爲變動 name 命令的顯示規則,上文已經列表過
    • dashCase:變爲橫線連接 aa-bb-cc
    • properCase:變爲大駝峯 AaBbCc
    • ...
  • Handlebars 中使用變量,用 {{}} 包裹

4. 執行建立任務

打開 package.json

// scripts 中 增長一條命令
...
"scripts": {
    ...
    "plop": "plop"
  },
...  
複製代碼

此時咱們就可使用 npm run plop 來建立組件了

image.png

image.png

很快組件就建立完成了 ✅

此時看一下 components 文件夾下面

components         
├─ MyApp           
│  ├─ README.md    
│  ├─ index.scss   
│  └─ index.vue    
└─ HelloWorld.vue  
複製代碼

已經建立了 MyApp 的組件了,裏面的文件咱們也打開看看

打開 MyApp/index.vue

<template>
  <div class="my-app">my-app</div>
</template>

<script> export default { name: 'MyApp', } </script>

<style lang="scss"> @import "./index.scss"; </style>
複製代碼

打開 MyApp/index.scss

.my-app {

}
複製代碼

打開 MyApp/README.md

這裏是組件 my-app 的使用說明
複製代碼

點此打開 👉 plop-demo 源碼地址

6、寫在最後

不知道你們看完這篇文章,學廢了嗎 😂

本篇文章整理了好久,但願對你們的學習有所幫助 😁

另外也但願你們能夠 點贊 評論 關注 支持一下,您的支持就是寫做的動力 😘

預告一下,下一篇將帶來 👉 打包與構建工具相關的知識體系


參考文章:

github.com/CodeLittleP…
cli.vuejs.org/zh/guide/cr…
yeoman.io/authoring/i…
www.jianshu.com/p/93211004c…

相關文章
相關標籤/搜索