手寫腳手架

1 初始化命令

新建工具包初始化自定義命令執(zhí)行 npm link 設(shè)置為全局使用

  1. 新建bin目錄,創(chuàng)建 cli.js 文件
  2. 在 cli.js 文件內(nèi)部設(shè)置 #! /usr/bin/env node 指令
  3. 終端切到當(dāng)前工具包目錄,執(zhí)行 npm link 創(chuàng)建全局鏈接

2 commander使用

使用 commander 處理自定義幫助命令

  1. 執(zhí)行 npm i commander -D 安裝自定義命令
  2. 按語法設(shè)置可選 options 與 自定義命令
const { program } = require('commander')

// 新增自定義的可選屬性
program.option('-f --framework <framework>', 'select your framework')
program.option('-d --dest <dest>', 'a destination folder')

// 使用 commander 格式化 argv
program.version(require('../package.json').version).parse(process.argv)

處理幫助信息

// 處理幫助信息
const examples = {
  create: ['sli create|crt <project>'],
  config: [
    'sli config|cfg set <k> <v>',
    'sli config|cfg get <k>'
  ]
}
program.on('--help', () => {
  console.log('Examples: ')
  Object.keys(examples).forEach(function (actionName) {
    examples[actionName].forEach((item) => {
      console.log(' ' + item)
    })
  })
})

自定義命令

program
  .command('create <project> [others...]')
  .alias('crt')
  .description('創(chuàng)建新項目')
  .action((name, args) => {
    console.log(name + '執(zhí)行了')
    console.log(args)
  })

抽離help函數(shù)

  1. 將 --help 需要做的事情抽離到單獨的文件當(dāng)中
  2. 將對應(yīng)的函數(shù)導(dǎo)出供外部進行調(diào)用

抽離自定義命令


const {
  createActions
} = require('./actions')

const myCommand = function (program) {
  //? 01 創(chuàng)建項目命令
  program
    .command('create <project> [others...]')
    .alias('crt')
    .description('創(chuàng)建新項目')
    .action(createActions)

  //? 02 配置項目命令
  program
    .command('config <set|get> [others...]')
    .alias('cfg')
    .description('配置項目')
    .action((name, args) => {
      console.log(name + '執(zhí)行了')
      console.log(args)
    })
}

module.exports = myCommand

3 其它工具使用

chalk 使用

const chalk = require('chalk')

//? 文字顏色
console.log(chalk.green('綠色'))
console.log(chalk.keyword('red')('前端開發(fā)'))
console.log(chalk.hex('#fff')('前端開發(fā)'))

//? 背景顏色
console.log(chalk.bgGray('帶背景'))

//? 格式化輸出
console.log(chalk.green.bold`
  {red 從前慢}
  沒有前端開發(fā)
`)

/**
 * 使用 chalk 包可以修改命令行終端字體的顏色
 * + 提供的關(guān)鍵字設(shè)置顏色:green red orange 等等
 * + 提供 keyword 方法接收鍵字
 * + 提供 hex 方法接收16進制
 * + 可以設(shè)置背景顏色
 * + 格式化輸出文字內(nèi)容
 */

ora 使用

import ora from 'ora'

const spinner = ora('正在下載......').start()
spinner.color = 'green'
// spinner.text = 'Loading rainbows'

setTimeout(() => {
  // spinner.succeed('下載成功')
  // spinner.fail('下載失敗')
  spinner.info('下載內(nèi)容')
}, 2000)

/**
 * 當(dāng)前版本只支持 es6Module,可以將 package.json 文件中添加 type:module 字段
 * ora 實例化對象然后調(diào)用 start 
 * color設(shè)置文字顏色
 * text 設(shè)置文字內(nèi)容
 * 
 * succeed 成功回調(diào)
 * fail 失敗回調(diào)
 * info
 * ......
 */

inquirer 使用

基本使用

const inquirer = require('inquirer')

//? 定義問題
let quesList = [
  {
    type: 'input',
    name: 'username',
    message: '用戶名',
    validate(an) {
      if (!an) {
        return '當(dāng)前為必填項'
      } else {
        return true
      }
    }
  }
]

//? 獲取結(jié)果
inquirer.prompt(quesList).then((an) => {
  console.log(an.username)
})
/**
 * 基礎(chǔ)字段
 * + type 定義問題類型
 * + name 將來問題的答案會被保存在一個對象當(dāng)中,這里定義的值就是它的鍵名
 * + message 設(shè)置問題的提示信息
 * + default 設(shè)置默認值
 * + validate 函數(shù),接收參數(shù)為當(dāng)前問題的答案,可以添加判斷的條件來決定后續(xù)走向
 */

遞進問題

const inquirer = require('inquirer')

const quesList = [
  {
    type: 'confirm',
    name: 'isLoad',
    message: '是否執(zhí)行下載'
  },
  {
    type: 'list',
    name: 'method',
    message: '選擇下載方式',
    choices: ['npm', 'cnpm', 'yarn'],
    when(preAn) {
      return preAn.isLoad
    }
  }
]

inquirer.prompt(quesList).then((an) => {
  console.log(an)
})

/**
 * confirm 詢問型問題,返回 true 或者 false
 * choices 接收一個數(shù)組,給問題提供選項
 * when 接收一個參數(shù)是一個問題的答案,可用于判斷當(dāng)前問題是否顯示
 */

總結(jié)

const inquirer = require('inquirer')

// 準備問題
const quesList = [
  {
    type: 'checkbox',
    name: 'feature',
    message: '選擇基礎(chǔ)功能',
    pageSize: 2,
    choices: ['webpack', 'webpack-cli', 'eslint', 'jest', 'zoe', 'vueRouter', 'React']
  }
]

// 處理問題
inquirer.prompt(quesList).then((an) => {
  console.log(an.feature)
})

/**
 * 問題屬性:
 *  + type:input list confirm checkbox
 *  + name: 用于做為答案的鏈出現(xiàn)
 *  + message: 用于問題的提示信息
 *  + choices: 出現(xiàn)選項時,設(shè)置為列表選項
 *  + pageSize: 設(shè)置每頁顯示的問題數(shù)量
 * 常見方法:
 *  + validate 校驗
 *  + when 判斷
 */

4 功能實現(xiàn)

資料

獲取組織倉庫列表信息
https://api.github.com/orgs/lagoufed/repos

獲取個人倉庫列表信息
https://api.github.com/users/zcegg/repos

獲取指定倉庫版本號
https://api.github.com/repos/zcegg/create-nm/tags

查詢訪問次數(shù)

curl -i https://api.github.com/users/octocat

headers={"Authorization":"token "+"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"}


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

步驟分析

已完成:命令、交互工具、業(yè)務(wù)參數(shù)
未完成:
查詢遠端模板列表(添加交互)
查詢選中模板下是否存在多個版本
下載指定模板指定版本
將模板緩存在指定位置
渲染數(shù)據(jù)寫入到指定的目錄

請求次數(shù)限制

  //! 發(fā)送請求
  let ret = await axios('https://api.github.com/users/zcegg/repos')
  console.log(ret.data)

解決次數(shù)限制

//! 發(fā)送請求
  const headers = { "Authorization": "token " + "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" }
  let { data } = await axios({
    method: 'get',
    url: 'https://api.github.com/users/zcegg/repos',
    headers: headers
  })
  const repos = data.map(item => item.name)
  console.log(repos)

查詢模板信息

const createActions = async function (project) {
  //? 定義請求頭信息
  const headers = { "Authorization": "token " + "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" }

  var { data } = await axios({
    method: 'get',
    url: 'https://api.github.com/users/zcegg/repos',
    headers: headers
  })
  const repos = data.map(item => item.name)

  //? 01準備問題
  const quesList = [
    {
      type: 'list',
      name: 'tmpRepo',
      message: '選擇目標(biāo)模板',
      choices: repos
    }
  ]
  //? 02 處理問題
  const { tmpRepo } = await inquirer.prompt(quesList)

  //? 03 查詢選中模板信息
  var { data } = await axios({
    method: 'get',
    url: 'https://api.github.com/repos/zcegg/create-nm/tags',
    headers: headers
  })

  const tags = data.map(item => item.name)
  console.log(tags, '<----')

}

提取查詢方法

const fetchInfo = async function (repoName, tmpName) {
  //? 定義token
  const token = "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"
  const url1 = `https://api.github.com/users/${repoName}/repos`
  const url2 = `https://api.github.com/repos/${repoName}/${tmpName}/tags`
  const headers = { "Authorization": "token " + token }
  const url = !tmpName ? url1 : url2
  let { data } = await axios({
    method: 'get',
    url: url,
    headers: headers
  })
  return data.map(item => item.name)
}

添加耗時及柯理化

//! 工具方法之添加耗時
const addLoading = function (fn) {
  return async function (...args) {
    const spinner = ora('正在查詢').start()
    const ret = await fn(...args)
    spinner.succeed('查詢成功')
    return ret
  }
}

// const repos = await addLoading(fetchInfo)('zcegg')

版本邏輯處理

let loadUrl = null
  if (tags.length) {
    // 處理版本
    const quesTag = [
      {
        type: 'list',
        name: 'tmpTag',
        message: '選擇指定版本',
        choices: tags
      }
    ]
    const { tmpTag } = await inquirer.prompt(quesTag)
  } else {
    console.log('直接執(zhí)行下載')
  }

處理緩存路徑

const toUnixPath = require('../utils/toUnixPath')
// console.log(process.env) // 查詢環(huán)境變量
// console.log(process.platform) // 查詢當(dāng)平臺關(guān)鍵字

console.log(process.env['USERPROFILE'])

console.log(toUnixPath(`${process.env[process.platform === 'win32' ? 'USERPROFILE' : 'HOME']}` + '/.tmp'))

初始化下載函數(shù)

//! 工具方法之下載操作
const downLoadRepo = function (repo, tag) {
  //? 定義緩存目錄
  const cacheDir = toUnixPath(`${process.env[process.platform === 'win32' ? 'USERPROFILE' : 'HOME']}` + '/.tmp')
  //? 處理參數(shù)
  let api = `zcegg/${repo}`
  if (tag) api += `#/${tag}`
  console.log(cacheDir)
  console.log(api)
}

實現(xiàn)下載操作

  //* 導(dǎo)出下載函數(shù)
  let downloadFn = require('download-git-repo')
  downloadFn = promisify(downloadFn)

  //? 定義緩存路徑
  const cacheDir = toUnixPath(`${process.env[process.platform === 'win32' ? 'USERPROFILE' : 'HOME']}` + '/.tmp')
  //? 處理參數(shù)
  let api = `zcegg/${repo}`
  if (tag) api += `#/${tag}`
  //? 定義緩存目錄
  const dest = tag ? path.resolve(cacheDir, repo, tag) : path.resolve(cacheDir, repo)
  //? 執(zhí)行下載操作
  const spinner = ora('正在下載......').start()
  await downloadFn(api, dest)
  spinner.succeed('下載成功')

使用緩存

  //? 執(zhí)行下載操作(判斷緩存中是否存)
  if (!fs.existsSync(dest)) {
    //* 緩存目錄中不存在則直接下載
    const spinner = ora('正在下載......').start()
    await downloadFn(api, dest)
    spinner.succeed('下載成功')
  }
  //? 返回地址用于數(shù)據(jù)讀取
  return dest

無數(shù)據(jù)渲染

//? 05 下載完成之后判斷是否需要渲染數(shù)據(jù),從而生成本地的項目
  // 如果需要渲染則在 package.json 中使用 ejs 語法,同時提前通過 qus.js 來準備問題
  if (fs.existsSync(path.join(dest, 'que.js'))) {
    console.log('需要渲染')
  } else {
    // 不需要渲染就直接拷貝
    ncp(dest, project)
  }

metalsmith使用

  //? 05 下載完成之后判斷是否需要渲染數(shù)據(jù),從而生成本地的項目
  // 如果需要渲染則在 package.json 中使用 ejs 語法,同時提前通過 qus.js 來準備問題
  if (fs.existsSync(path.join(dest, 'que.js'))) {
    //! 需要數(shù)據(jù)渲染
    await new Promise((resolve, reject) => {
      MetalSmith(__dirname)
        .source(dest)
        .destination(path.resolve(project))
        .use((files, metal, done) => {
          //* files 是當(dāng)前目錄下所有的文件信息
          //* 
          console.log(files)
          done()
        })
        .build((err) => {
          if (err) {
            reject()
          } else {
            resolve()
          }
        })
    })

  } else {
    // 不需要渲染就直接拷貝
    ncp(dest, project)
  }

設(shè)置問題

if (fs.existsSync(path.join(dest, 'que.js'))) {
    //! 需要數(shù)據(jù)渲染
    await new Promise((resolve, reject) => {
      MetalSmith(__dirname)
        .source(dest)
        .destination(path.resolve(project))
        .use(async (files, metal, done) => {
          const quesList = require(path.join(dest, 'que.js'))
          const answer = await inquirer.prompt(quesList)
          console.log(answer)
          done()
        })
        .build((err) => {
          if (err) {
            reject()
          } else {
            resolve()
          }
        })
    })

  } else {
    // 不需要渲染就直接拷貝
    ncp(dest, project)
  }

問題數(shù)據(jù)傳遞

if (fs.existsSync(path.join(dest, 'que.js'))) {
    //! 需要數(shù)據(jù)渲染
    await new Promise((resolve, reject) => {
      MetalSmith(__dirname)
        .source(dest)
        .destination(path.resolve(project))
        .use(async (files, metal, done) => {
          const quesList = require(path.join(dest, 'que.js'))
          const answer = await inquirer.prompt(quesList)
          //! 當(dāng) answer 的答案我們需要在下一個 use 中進行使用
          //! 利用 metal.metadata() 來保存所有的數(shù)據(jù),交給下一個 use 進行使用即可
          let meta = metal.metadata()
          Object.assign(meta, answer)

          // 這步操作完成之后,que.js 文件就沒有用了,不需要拷貝至項目的目錄
          // delete files['que.js']
          done()
        })
        .use((files, metal, done) => {
          // 獲取上一個 use 中拿到的用戶數(shù)據(jù)
          let data = metal.metadata()
          console.log(data)
          done()
        })
        .build((err) => {
          if (err) {
            reject()
          } else {
            resolve()
          }
        })
    })

  } else {
    // 不需要渲染就直接拷貝
    ncp(dest, project)
  }

數(shù)據(jù)渲染

.use((files, metal, done) => {
  // 獲取上一個 use 中拿到的用戶數(shù)據(jù)
  let data = metal.metadata()
  //? 找到那些需要渲染數(shù)據(jù)的具體文件,找到之后將它們的內(nèi)容轉(zhuǎn)為字符串
  //? 轉(zhuǎn)為字符串之后,接下來就可以針對于字符串進行替換實現(xiàn)渲染
  Reflect.ownKeys(files).forEach(async (file) => {
    if (file.includes('js') || file.includes('json')) {
      let content = files[file].contents.toString()
      if (content.includes("<%")) {
        content = await render(content, data)
        files[file].contents = Buffer.from(content)
      }
    }
  })
  done()

執(zhí)行 npm

const { spawn } = require('child_process')

// 執(zhí)行 npm install 
const commandSpawn = (...args) => {
  return new Promise((resolve, reject) => {
    const childProcess = spawn(...args)
    childProcess.stdout.pipe(process.stdout)
    childProcess.stdout.pipe(process.stderr)
    childProcess.on('close', () => {
      resolve()
    })
  })
}

module.exports = {
  commandSpawn
}

//? 06 執(zhí)行 npm install 
const run_command = process.platform === 'win32' ? 'npm.cmd' : 'npm'
await commandSpawn(run_command, ['install'], { cwd: `./${project}` })

//?07 執(zhí)行 run serve
commandSpawn(run_command, ['run', 'serve'], { cwd: `./${project}` })
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容