如何搭建一款簡(jiǎn)易腳手架

什么是腳手架?

簡(jiǎn)而言之它就是一個(gè)工具,方便我們新建項(xiàng)目用的,通過這個(gè)工具創(chuàng)建的項(xiàng)目之后我們可以直接開發(fā)了懊昨。

市面常見的腳手架?

  1. vue-cli 提供vue開發(fā)的webpack,pwa等模板
  2. create-react-app React團(tuán)隊(duì)官方出的一個(gè)構(gòu)建React單頁面應(yīng)用的腳手架工具
  3. Yeoman 通用型腳手架春宣,過于通用,不夠?qū)W⒓的悖褂寐闊?/li>

為什么要自己搭建月帝?

  1. 專心快速的完成業(yè)務(wù)
  2. 代碼更加規(guī)范化
  3. 少造輪子,少拷貝代碼幽污,簡(jiǎn)化流程

如何搭建一款簡(jiǎn)易腳手架嚷辅?

一、目錄搭建

  1. 創(chuàng)建一個(gè)文件夾距误,取名為lang-cli
  2. 在該目錄下執(zhí)行 npm init -y簸搞,就會(huì)生成一個(gè)package.json,在packjson中寫入以下依賴并執(zhí)行npm install安裝准潭。
"dependencies": {
    "axios": "^0.24.0",
    "chalk": "^4.1.2",
    "commander": "^8.3.0",
    "download-git-repo": "^3.0.2",
    "fs-extra": "^10.0.0",
    "inquirer": "^8.2.0",
    "ora": "^5.4.1"
  }
  1. 新建一個(gè)bin文件夾趁俊,并在bin目錄下新建一個(gè)無后綴的文件,取名為lang(這個(gè)文件將作為我們整個(gè)腳手架的入口文件, 用node ./bin/lang也可以運(yùn)行)刑然,并寫入以下內(nèi)容寺擂,這個(gè)語句的意思就是為了讓系統(tǒng)看到這一行的時(shí)候,沿著該路徑去查找node并執(zhí)行泼掠。
#! /usr/bin/env node
console.log('hello lang')
  1. 由于一直在本地用node ./bin/lang運(yùn)行起來很麻煩怔软,所以可以掛載到全局。在package.json中加入如下一行择镇,在根目錄下執(zhí)行npm link挡逼,之后每次輸入lang,就可以直接運(yùn)行了腻豌。
{
  "name": "lang",
  "bin": "./bin/lang", // 默認(rèn)取的name的名字
}
{
  "name": "lang",
  "bin": {
      'lang-cli': './bin/lang'
  }
}

二家坎、編寫具體指令(配置可執(zhí)行命令)

commander用來編寫指令和處理命令行的一個(gè)工具嘱能。chalk 是用來修改控制臺(tái)輸出內(nèi)容樣式的,比如顏色啊乘盖,具體用法如下:

const program = require("commander")
const chalk = require("chalk")
// 定義當(dāng)前版本
// 定義使用方法
program
  .version(`lang-cli@${require("../package.json").version}`)
  .usage('<command> [option]')

// 定義指令  create
program
  .command('create <app-name>')
  .description('create a new project')
  .option('-f,--force', 'overwrite target directory if it exsit')
  .action((name, cmd) => {
      console.log(name, cmd)
      //每個(gè)功能放單獨(dú)模塊中寫焰檩,調(diào)用create模塊去創(chuàng)建
      require('../lib/create.js')(name, cmd)
  })
 
//vue config set/get 等指令 這里就不詳述了

// 監(jiān)聽 "--help命令輸入"
program
  .on('--help', function () {
    console.log()
    console.log(`Run ${chalk.cyan('lang <command> --help')} show details`)
    console.log()
  })
// 解析命令行參數(shù)
program.parse(process.argv)

三、編寫創(chuàng)建邏輯

  1. 創(chuàng)建一個(gè)lib文件夾订框,在此文件中創(chuàng)建一個(gè)create.js析苫,具體代碼如下:
const path = require('path')
const fs = require('fs-extra')
const inquirer = require('inquirer')
const Creator = require('./Creator')
module.exports = async function (projectName, option) {
  // 創(chuàng)建項(xiàng)目
  const cwd = process.cwd(); // 獲取當(dāng)前命令執(zhí)行時(shí)的工作目錄
  const targetDir = path.join(cwd, projectName) // 目標(biāo)目錄

  if (fs.existsSync(targetDir)) {
    if (option.force) {//如果強(qiáng)制創(chuàng)建,刪除已有的
      await fs.remove(targetDir)
    } 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;
      if (action === 'overwrite') {
        console.log('removing.....')
        await fs.remove(targetDir)
      }
    }
  }

  // 創(chuàng)建項(xiàng)目(單獨(dú)提出來一個(gè)類去做)
  const creator = new Creator(projectName, targetDir)
  creator.create()
}

Creator.js類中的代碼如下:

const { fetchRepoList, fetchTagList } = require('./request')
const { wrapLoading } = require('./util')
const inquirer = require('inquirer')
const downloadGitRepo = require('download-git-repo') //不支持promise
const util = require('util') //node自帶的
const path = require('path') //node自帶的
class Creator {
  constructor(projectName, targetDir) {
    this.name = projectName;
    this.target = targetDir;
    this.downloadGitRepo = util.promisify(downloadGitRepo) //轉(zhuǎn)換成promise方法
  }
  async fetchRepo() {
    // 失敗要重新拉却┌狻(網(wǎng)慢的話)
    let repos = await wrapLoading(fetchRepoList, 'waiting fetch template')
    if (!repos) return;
    repos = repos.map(i => i.name)
    let { repo } = await inquirer.prompt({
      name: 'repo',
      type: 'list',
      message: 'please choose a template to create project',
      choices: repos,
    })
    return repo;
  }
  async fetchTag(repo) {
    let tags = await wrapLoading(fetchTagList, 'waiting fetch tag', repo)
    if (!tags) return;
    tags = tags.map(i => i.name)
    let { tag } = await inquirer.prompt({
      name: 'tag',
      type: 'list',
      message: 'please choose a tag to create project',
      choices: tags,
    })
    return tag;
  }
  async download(repo, tag) {
    // 1.拼接出下載路徑  https://github.com/wave1994
    let requestUrl = `wave1994/${repo}${tag ? '#' + tag : ''}`
    // 2.把資源下載到某個(gè)路徑上
    // await this.downloadGitRepo(requestUrl, this.target)
    await this.downloadGitRepo(
        requestUrl, 
        path.resolve(process.cwd(), `${repo}@${tag}`)
    )
  }
  async create() {
    //開始創(chuàng)建
    // 1. 采用遠(yuǎn)程拉取的方式
    // 1). 先去拉取當(dāng)前組織下的模板
    let repo = await this.fetchRepo()
    // 2). 再通過模板找到版本號(hào)
    let tag = await this.fetchTag(repo)
    // 3). 下載
    let downloadUrl = await this.download(repo, tag)
  }
}
module.exports = Creator

request.js中代碼:

// 通過axios獲取結(jié)果
const axios = require('axios')
axios.interceptors.response.use(res => res.data)

async function fetchRepoList() {
  return axios.get('https://api.github.com/users/wave1994/repos')
}

async function fetchTagList(repo) {
  return axios.get(`https://api.github.com/repos/wave1994/${repo}/tags`)
}
module.exports = {
  fetchRepoList,
  fetchTagList
}

util.js文件中代碼:

const ora = require('ora')

async function sleep(n) {
  return new Promise((resolve, reject) => {
    setTimeout(() => resolve(), n);
  })
}
// 等待loading函數(shù)
async function wrapLoading(fn, msg, ...args) {
  const spinner = ora(msg)
  spinner.start(); // 開始加載
  try {
    let repos = await fn(...args);
    spinner.succeed() //成功
    return repos;
  } catch (error) {
    spinner.fail('fetch failed, refetch....')
    await sleep(1000)
    return wrapLoading(fn, msg, ...args)
  }

}
module.exports = {
  wrapLoading
}

四衩侥、執(zhí)行命令

lang create app-name

總結(jié)

腳手架是前端工程化領(lǐng)域的基本項(xiàng),個(gè)人認(rèn)為掌握前端腳手架的開發(fā)是十分重要的矛物,本文旨在提供一個(gè)大概思路及樣板茫死,目前只包含了命令行、模板拉取履羞,相對(duì)于成熟的腳手架如vue-cli峦萎、create-react-app等來說,還有很多很多工作要做忆首,包括本地服務(wù)爱榔、打包構(gòu)建、集成部署糙及、周邊其他等都還需要完善详幽,想要在工程化領(lǐng)域有所建樹的同學(xué),不妨在這幾個(gè)方面多下下功夫浸锨。

參考文檔:https://vleedesigntheory.github.io/tech/front/cli20200701.html#%E5%89%8D%E8%A8%80

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末唇聘,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子柱搜,更是在濱河造成了極大的恐慌迟郎,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,311評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件聪蘸,死亡現(xiàn)場(chǎng)離奇詭異谎亩,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)宇姚,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門匈庭,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人浑劳,你說我怎么就攤上這事阱持。” “怎么了魔熏?”我有些...
    開封第一講書人閱讀 152,671評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵衷咽,是天一觀的道長(zhǎng)鸽扁。 經(jīng)常有香客問我,道長(zhǎng)镶骗,這世上最難降的妖魔是什么桶现? 我笑而不...
    開封第一講書人閱讀 55,252評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮鼎姊,結(jié)果婚禮上骡和,老公的妹妹穿的比我還像新娘。我一直安慰自己相寇,他們只是感情好慰于,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,253評(píng)論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著唤衫,像睡著了一般婆赠。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上佳励,一...
    開封第一講書人閱讀 49,031評(píng)論 1 285
  • 那天休里,我揣著相機(jī)與錄音,去河邊找鬼赃承。 笑死份帐,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的楣导。 我是一名探鬼主播,決...
    沈念sama閱讀 38,340評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼畜挨,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼筒繁!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起巴元,我...
    開封第一講書人閱讀 36,973評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤毡咏,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后逮刨,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體呕缭,經(jīng)...
    沈念sama閱讀 43,466評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,937評(píng)論 2 323
  • 正文 我和宋清朗相戀三年修己,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了恢总。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,039評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡睬愤,死狀恐怖片仿,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情尤辱,我是刑警寧澤砂豌,帶...
    沈念sama閱讀 33,701評(píng)論 4 323
  • 正文 年R本政府宣布厢岂,位于F島的核電站,受9級(jí)特大地震影響阳距,放射性物質(zhì)發(fā)生泄漏塔粒。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,254評(píng)論 3 307
  • 文/蒙蒙 一筐摘、第九天 我趴在偏房一處隱蔽的房頂上張望卒茬。 院中可真熱鬧,春花似錦蓄拣、人聲如沸扬虚。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽辜昵。三九已至,卻和暖如春咽斧,著一層夾襖步出監(jiān)牢的瞬間堪置,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評(píng)論 1 262
  • 我被黑心中介騙來泰國(guó)打工张惹, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留舀锨,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,497評(píng)論 2 354
  • 正文 我出身青樓宛逗,卻偏偏與公主長(zhǎng)得像坎匿,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子雷激,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,786評(píng)論 2 345