前端工程化: 手把手node寫一個cli+物料庫

慕容奇妙發表於2019-04-04

零、前言

上一篇文章寫了一下joao-cli的基本思路,部分同學反應看不懂,這次直接帶大家手寫一個自己的cli工具,可以實現

  • 模板程式碼拉取到本地
  • 安裝物料元件到指定目錄
  • 安裝modules並替換對應內容
  • 拉取物料庫指定資料夾

一、準備工作

步驟會給出對應gif圖,圖不動的話建議右鍵新連線開啟.

1.初始化npm並安裝對應依賴.

    // 新建一個資料夾用以儲存cli工具, 當前目錄開啟命令列
    npm init 
    
    package name 輸入你的cli name
    
    可以一路回車跳過剩下內容...
複製程式碼

前端工程化: 手把手node寫一個cli+物料庫

2.註冊node.js

    在當前目錄新建command.js 寫上
    
        #! /usr/bin/env node
        console.log('執行了command指令碼')
    
    // 登記以上內容, 使command.js可以被node執行
    
    // 然後在packge.json裡新增 "bin": { "joao": "./command.js" }
    
        {
          "name": yourcli",
          "version": "0.0.1",
          "description": "快速搭建前端整站, 工程化自動開發",
          "main": "command.js",
          "dependencies": {},
          "devDependencies": {},
          "bin": {
            "joaocli": "./command.js"
          }
        }
    
    //  當前路徑開啟命令列工具 鍵入npm link, 宣告環境變數
        npm link
    //  成功後, 鍵入joaocli 會提示指令碼
        joaocli
        ...
        執行了command指令碼
        

複製程式碼

3.安裝要用到的依賴

安裝依賴 安裝目的
commander 命令列工具
fs 用來處理檔案
git-clone 用來從git託管地址下載你的檔案
inquirer 命令列中可以使用對話或者選項了
path 用來處理路徑
shelljs 用來執行shell
tracer 用來花式打log

安裝方法: pageage.json所在目錄, npm install ${對應的依賴名稱}

npm install commander
npm install fs
...
複製程式碼

二、功能演示及程式碼實現

做好準備工作後(安裝完所有依賴!), 你已經擁有了可執行的指令碼, 現在我們要一一完善各項功能


  • 利用inquirer+commander完成對話
    //修改command.js
    #! /usr/bin/env node
        console.log('執行了command指令碼')
        
        const commander = require('commander');
        const inquirer = require('inquirer');
        
        const questions = [
            {
             type: 'input',
             name: 'projectName',
             message: '請為專案命名',
             filter: function(val) {
                return val;
              }
            },
            {
             type: 'list',
             name: 'type',
             message: '請選擇使用的模板',
             choices: ['empty-vue-template', '模板2', '模板3'],
             filter: function(val) {
                return val.toLowerCase();
              }
            }
        ]
        
        // 新建專案 拉取模板
        commander
        .command('init')
          .description('用來初始化專案, 拉取模板')
          .action(() => {
          console.log('正在構建...')
          inquirer.prompt(questions)
          .then(answers => {
              console.log('answers', answers)
            })  
        })
        
        // 此行內容落下會使命令列監聽失效
        commander.parse(process.argv)
    複製程式碼

現在鍵入 joaocli init 會輸出對話, 並獲取使用者輸入的內容和選項

  • 新建空專案, 拉取模板到本地

引入git clone, 用來進行git 引入shelljs, 用來刪除專案的.git檔案 引入tracer, 用來彩色console

    const clone = require('git-clone');
    const shell = require('shelljs');
    const log = require('tracer').colorConsole();
複製程式碼

新增必要常量

    // 專案地址
    const projectUrl = {
      'empty-vue-template': 'https://github.com/murongqimiao/joao-template', // 空白庫
    } 
複製程式碼

在拿到使用者輸入內容的回撥後

      const { projectName, type } = answers
      console.log(projectName)
      console.log(type)
      clone(`${projectUrl[type]}`, `./${projectName}`,null, function() {
        log.info('專案構建完成')
        shell.rm('-rf', `./${projectName}/.git`);
        log.info(`清除掉${projectName}的git, 記得進入專案npm install`)
      })
複製程式碼

現在在你的目錄下執行 joaocli init吧! 你將拉下一個模板專案並清除.git

前端工程化: 手把手node寫一個cli+物料庫


  • 拉取倉儲裡的物料到本地. 新增依賴
    const path = require('path')
複製程式碼

在packgejson的同級目錄下新增物料資料夾

    mkdir material
    cd material
複製程式碼

新增新的commander實現物料下載

    // 更新物料
    commander
      .command('update')
      .description('更新物料庫')
      .action(() => {
        let pwd = shell.pwd()
        shell.cd(__dirname)
        shell.exec('rm -rf .git')
        const updateFiles = () => {
          shell.cd(path.join(__dirname, '/material'))
          shell.rm('-rf', '*')
          shell.cd(__dirname)
          shell.exec('git pull origin master')
        }
        shell.exec('git init')
        shell.exec('git remote add origin git@github.com:murongqimiao/joao-website.git') // 物料倉庫
        shell.exec('git config core.sparseCheckout true')
        shell.exec("echo 'material' >> .git/info/sparse-checkout")
        updateFiles()
      })
      
      
    // 非常重要
    commander.parse(process.argv)
複製程式碼

執行joaocli update 即可更新物料到 /material 下

關於物料的管理有很多辦法, 這裡筆者採用較為簡單的git倉庫建立專有資料夾託管物料, 利用git的 core.sparseCheckout來下載部分資料夾並不是最好的辦法,因為就git的工作機制而言, sparseCheckout依然是追蹤所有的歷史變更,再刪去不必要內容,如果專案在github託管,還可以把github的倉儲轉為svn再下載,具體操作可以查閱svn如何下載github的部分內容,因為我們廠的程式碼放在gitlab上,因故採用了core.sparseCheckout來下載物料檔案.

同時因為引用下載整個專案, 執行update的時候會特別慢,請耐心等待.

update成功後material檔案為下會增加元件資料夾generate_components與module資料夾generate_modules


  • 新增元件到你的模板裡 涉及檔案讀取與書寫需要使用node的fs系列
    const fs = require('fs')
複製程式碼

command.js中繼續新增內容

    /**
     * 引入一些可以抽離的內容: commander快捷鍵 | getComponentsFiles獲取目錄 |_getFileName 獲取是檔案還是資料夾 | generateComponent 在指定目錄放置目標檔案
     **/
    commander.version('1.0.0')
      .option('-c, --component', 'Add component')
      .option('-p, --page', 'Add page')

    const getComponentsFiles = () => {
      return _getFileName('../material/generate_components')
    }

    const _getFileName = (url) => {
      let _url = path.join(__dirname, url)
      let _back = fs.readdirSync(_url)
      return _back
    }
    
    const generateComponent = (componentType, fileName, pwd) => {
        fs.readFile(path.join(__dirname, `./material/generate_components/${componentType}${fileName}`), 'utf-8', (error, data) => {
            if (error) throw error
            // if (componentName) {
            //   data = data.replace(/__className__/g, componentName)
            //   componentType = componentName
            // }
            
            fs.writeFile(path.join(`${pwd}/src/components/${componentType}${fileName}`), data, (err) => {
              if(err) {
                console.log('err:', err);
              } else {
                console.log(`${componentType}${fileName}寫入成功!...`)
              }
            })
          })
      }
    
    // 新增物料
    commander
      .command('add [args...]')
      .description('增加物料 -c 元件  -p 頁面')
      .action((args) => {
        // 缺少引數
        if (!commander.component && !commander.page) {
          log.warn('缺少引數-c或者-p, 以區分物料種類, 具體見--help')
        }
        // 增加元件
        if (commander.component) {
          let pwd = shell.pwd()
          args.map(componentType => {
            _componentType = getComponentsFiles().indexOf(componentType) > -1 ? componentType : getComponentsFiles().indexOf(componentType + '.vue') > -1 ? componentType + '.vue' : void 0
            if (!_componentType) {
              log.warn(`:${componentType}  --> 元件不存在, 請檢查拼寫`)
            } else {
              let _filePath = path.join(__dirname, `./material/generate_components/${_componentType}`)
              let _isFile = fs.statSync(_filePath).isFile()
              if (_isFile) {
                generateComponent(_componentType, '', pwd)
              } else {
                let _aimFiles = fs.readdirSync(_filePath)
                // 建資料夾
                fs.mkdir(`${pwd}/src/components/${_componentType}`, 0777, (err) => {
                  if (err) {
                    log.info(`${componentType}目錄已經建立`)
                  }
                })
                _aimFiles.map(item => {
                  generateComponent(_componentType, `/${item}`, pwd)
                })
              }
            }
          })
        }
        // 增加頁面
        if (commander.page) {
          /**
          *     這裡是新增modules的邏輯,接下來補充
          **/
        }
      })
      
    ...上面的內容同樣新增到這句話前面
    // 非常重要
    commander.parse(process.argv)

複製程式碼

現在在你init出來的空專案中, 根目錄執行 joaocli add -c empty 或者 joaocli add -c Layout 來測試能否正常新增物料吧!

前端工程化: 手把手node寫一個cli+物料庫


  • 在專案中新增modules

能新增components後, 新增modules就沒有那麼困難了, 和元件相比只是多了一個自定義路徑

首先新增新的問題

    const modulesFileContent = ['data.js', 'index.js', 'vx.js', '__className__.vue']

    // 引入page構建問題
    const pageQuestions = [
      {
       type: 'input',
       name: 'modelType',
       message: '請輸入想要使用的頁面種類',
       filter: function(val) {
          return val;
        }
      }
    ]
複製程式碼

新增增加moduls的邏輯

    /** 
     * 在目標檔案中增加對應的page(單個新增)
     */
    const generateModule = (modelType, pageName, fileName, pwd) => {
        let aimFileName = fileName
        let _directory = ''
            // 考慮一級目錄
        if (pageName.split('/').length > 2) {
          console.log('目前最多支援2級目錄,比如dashboard/example')
        }
        if (pageName.split('/').length === 2) {
          _directory = pageName.split('/')[0] + '/'
          pageName = pageName.split('/')[1]
        }
        
        if (new RegExp(/.vue/).test(fileName)) {
          aimFileName = pageName + '.vue'
        }
        fs.readFile(path.join(__dirname, `./material/generate_modules/${modelType}/${fileName}`), 'utf-8', (error, data) => {
            if (error) throw error
            data = data.replace(/__className__/g, pageName)
            fs.writeFile(path.join(`${pwd}/src/views/${_directory}${pageName}/${aimFileName}`), data, (err) => {
              if(err) {
                console.log('err:', err);
              } else {
                console.log(`${fileName}寫入成功!...`)
              }
            })
          })
      }


    // 增加頁面
        if (commander.page) {
          /**
          *     這裡是新增modules的邏輯補充
          **/
         inquirer.prompt(pageQuestions)
         .then(answers => {
           const  { modelType } = answers
           let pwd = shell.pwd()
           console.log(`commander.page`, args)
           args.map(pageName => {
             console.log('pageName', pageName)
             // 建立對應目錄的資料夾
             if (pageName.split('/').length === 2) {
               fs.mkdir(`${pwd}/src/views/${pageName.split('/')[0]}`, 0755, (err) => {
                 log.info(`${pageName.split('/')[0]}已存在`)
               })
             }
             fs.mkdir(`${pwd}/src/views/${pageName}`, 0755, (err) => {
               log.info(`${pageName}已存在`)
             })
             /* 讀取./generate_modules下面的檔案 */
             modulesFileContent.map(fileName => {
               generateModule(modelType, pageName, fileName, pwd)
             })
           })
         })
        }
複製程式碼

在模板專案的根目錄執行

joaocli add -p yourlogin
複製程式碼

再輸入對應module型別, 比如login

就完成了一鍵安裝modules的過程.

原始碼檢視

npm install -g joao-cli
複製程式碼

進入joao-cli的目錄,直接看程式碼即可, 完全一致.

官網

目前物料不多, 有時間會維護, 歡迎各路大佬塞物料來, 建議新增README.md , 放置物料使用方法和收款碼, 有同學用的爽可以打賞給物料開發者.

相關文章