1. 程式人生 > >個人腳手架搭建 -- charmingsong-cli

個人腳手架搭建 -- charmingsong-cli

個人腳手架搭建 -- charmingsong-cli

目的

為了解決多次構建相同功能的專案,在一定程度上需要定製化以及私有化設定

設計

問題

為什麼不用現成的腳手架生成?

  • 如果利用vue或者react的腳手架搭建專案可能建立完專案後還需要一系列的配置以及更改展示樣式,並沒有真正的解決問題。並且各自的腳手架只能生成各自的框架結構,其他專案不支援。例如,用vue-cli生成一個react專案,是不可以的。

為什麼不直接複製相同的專案?

  • 現有的專案中邏輯程式碼關聯性很強,並且專案中可能存在一些基於專案環境的程式碼,直接複製專案更改名稱工程量較大。環境錯誤不易發覺。

整體思路

為完成所需功能,主要需要實現兩個模組

  • 工具——cli
  • 模板——template

cli: 就是和使用者互動部分的工具, 需要知道使用者所需。

template: 專案模板,可以自己隨意定製。

設計目的
  • 模板與工具分開,各自單獨維護
  • cli負責獲取模板列表,下載、渲染模板
    • 模板可以自行增加刪除修改,不需要重新發版新的包
    • 專案邏輯都放到模板中,模板可以設計私有化屬性
cli 工作流程圖

啟程

程式碼是利用typescript實現的,具體程式碼可以檢視專案地址 charmingsong-cli

文章不會用大量的程式碼填充講解,主要會記錄一些功能性模組程式碼

目錄結構

.
├── .DS_Store
├── .eslintrc.json
├── .gitignore
├── .npmignore
├── .npmrc
├── README.md
├── cs-config.json
├── package-lock.json
├── package.json
├── src
│   ├── index.ts
│   ├── libs
│   │   ├── inquirer.ts
│   │   ├── program.ts
│   │   └── tools.ts
│   ├── typing.d.ts
│   └── utils
│       └── logger.ts
├── tsconfig.json
└── yarn.lock
  • tsconfig.json: ts 的配置檔案,可以查詢官網,瞭解的具體設定。

  • package.json

    // ...
    "scripts": {
      "build": "tsc", // 打包
      "dev": "tsc -w", // 開發除錯
      "eslint": "eslint src --ext .ts", // eslint 檢查以 .ts 為字尾的檔案
      // ...
    },
    "bin": {
      "cs": "bin/index.js"
      // 全域性安裝(npm i -g xxx) 全域性執行時執行的檔案 然後全域性就可以使用命令 cs
    },
    "husky": {
      "hooks": {
        "pre-commit": "npm run eslint"
        // 主要是為了在提交commit的時候檢測程式碼是否符合標準,如果感興趣可以瞭解一下husky
      }
    },
    // ...
    
    > [husky](https://github.com/typicode/husky)
  • src/: 程式碼檔案目錄

    • index.ts : 主檔案

      #!/usr/bin/env node
      
      // ...

      全域性安裝執行命令式, 在檔案開頭加上 #!/usr/bin/env node 在直接執行時會用本機中的 node環境來執行檔案。

    • typing.d.tstypescript的描述檔案,個人理解就是用來將一些沒有ts全域性獲取不到的包, 定義個環境提供給typescript 。 (深入學習 ts 後來補充 xxx.d.ts 檔案所代表的意義)

    • utils/:公共方法目錄

      • logger.ts : 提供一個在終端輸出特殊樣式的方法
    • libs/:環境工具目錄

      • program.ts: 終端命令定義工具, 主要就是使用commander包,對其進行私有化設定封裝
      • inquirer.ts:終端互動式介面提示工具,獲取使用者的自定義資訊,利用inquirer來實現,和 終端命令定義工具 搭配使用。

      • tools.ts:主要提供流程需要的函式方法, 如:下載模板,渲染模板等

實現流程

專案地址已經貼出來了, 所以不講解每個檔案的程式碼實現邏輯

因為每個檔案都是各司其職, 這裡只記錄主檔案的實現邏輯

主檔案解讀

建議不懂的包自行去官網看一下,一方面不論我怎麼講解都沒有官網講解的深刻全面,另一方面我可能有的地方也不是很瞭解其原理本質,如果有錯誤的地方,歡迎指出,共同進步

  1. 利用commander包全域性註冊一個 init 的命令

    import program from './libs/program'
    // ./libs/program檔案就是對commander設定了一下基本資訊,然後重新命名、引入
    
    async function commandInit(appName: string) {}
    
    program.command('init <project-name>').action((appName: string) => {
      commandInit(appName)
    })
    
    // commander 的用法,可以設定命令列的引數
    // 當用戶輸入 之前 cs init xxx 的時候
    //   cs ==> package.json檔案的設定的環境變數 我設定名稱是  cs
    //   init ==> 是 cs 後面帶的命令, 這裡設定的是 init
    //   xxx ==> 這裡是指檔名,是使用者輸入的引數,此處用法 commander 包有關, 建議仔細看一下用法
    //  commandInit ==> 如果使用者輸入命令是 init 時所執行的函式。
    
    program.parse(process.argv)
    // 將node的環境變數給 program ,簡單來說就是將終端引數交個 commander 來解析
    
    if (!process.argv.slice(2).length) {
      program.outputHelp()
    }
    // 這裡是對 cs 命令的 提示幫助, 如果使用者在終端只輸入 cs ,而不輸入命令 那麼展示 提示資訊
  2. 實現定義的命令函式 commandInit

    import { join } from 'path'
    import { existsSync, mkdirSync } from 'fs'
    
    import Logger from './utils/logger' // 在終端輸出資訊,可以捕捉錯誤
    
    import {
      downTemplate, // 下載模板方法
      getMetaJson, // 下載需要的初始json檔案方法
      writeTemplate // 渲染檔案方法
    } from './libs/tools'
    // ./libs/tools 是方法庫,具體的程式碼實現可以參考原始碼。
    
    import prompt from './libs/inquirer' // inquirer封裝,終端互動的工具
    
    import { metaName } from '../cs-config.json'
    // 獲取 inquirer 所需要初始化的json資訊,也就是最開始提問的json
    
    async function commandInit(appName: string) {
      const targetDir = join(process.cwd(), appName)
      // 檢視目標目錄是否存在
    
      existsSync(targetDir)
        ? Logger.fatal('The current directory already exists --> %s ', appName)
        : mkdirSync(targetDir)
      // 存在就對使用者,輸出錯誤提示:目錄存在, 如果不存在,就建立使用者所輸入的名稱的資料夾
    
      const baseInfo: MetaInfoMust = await prompt(await getMetaJson())
      // 獲取 模板列表的json 提供給 prompt,來和使用者進行互動
    
      const { template } = baseInfo
      // 獲取使用者選擇的模板資訊
    
      const templatePath = (await downTemplate(template)) as string
      Logger.success('template download success!')
      // 下載目標模板到本地, 並提示成功資訊
    
      const templateMetaPath = join(templatePath, metaName)
    
      const templateMetaInfo = existsSync(templateMetaPath)
        ? await prompt(require(templateMetaPath))
        : {}
      // 如果模板中需要自定義設定, 讀取配置,在和使用者互動
    
      const metaInfo = { ...baseInfo, ...templateMetaInfo }
      // 獲取兩次的互動所得的使用者資訊
    
      await writeTemplate(templatePath, metaInfo, targetDir)
      Logger.success('success ok!')
      // 根據獲取的使用者自定義資訊,向開始建立的目標目錄寫入、渲染模板並提示成功資訊
    
      process.exit(1)
      // 退出命令程式
    }

模板的設計

模板型別是根據github紀錄的模板型別

模板型別以 cs-templates-xxx格式命名,可用模板列表可自行檢視本專案 master_meta分支

模板開發規則

可自行新增模板

目錄
.
├── README.md
├── meta.json
└── template

模板寫入利用 handlebars,

例如:

{
  "name": "{{projectName}}",
  "version": "{{version}}",
  "description": "{{description}}"
}
預設互動

模板名稱--template

專案名稱--projectName

簡介描述--description

版本--version

自定義互動

可以自行配置meta.json,依據inquirer語法配置, 自動解析。

例如:

[
  {
    "name": "testname",
    "type": "input",
    "message": "測試",
    "default": "test"
  }
]

結語

設計這個腳手架大部分是為了鍛鍊自己的程式設計思想,寫這篇文章也是為了能在提升自己的語言組織能力的同時幫助到小夥伴們。 個人網站也是用這個 cli 中的 基於vue-ssr 模板搭建的, 有興趣的可以看一下個人網站