從零打造你的前端開發(fā)腳手架

在實(shí)際開發(fā)過(guò)程中徘跪,我們經(jīng)常都會(huì)用到腳手架來(lái)構(gòu)建前端工程項(xiàng)目讳推,常見(jiàn)的主流框架都有自己的腳手架,vue-cli葵腹、create-react-app高每、angular-cli。在大型公司都會(huì)有內(nèi)部定制化的腳手架開發(fā)工具践宴,使用腳手架可以大幅提升項(xiàng)目的構(gòu)建速度鲸匿,通過(guò)命令行的交互,選擇你所需要的配置與集成阻肩,可快速完成初始項(xiàng)目的創(chuàng)建带欢。

既然使用了這么多腳手架創(chuàng)建項(xiàng)目,為何不自己實(shí)現(xiàn)一套屬于自己開發(fā)習(xí)慣的腳手架呢烤惊,本文將從0開始搭建一套腳手架開發(fā)工具乔煞。

什么是腳手架

腳手架是一類快速形成工程化目錄的工具(command-line-interface, 縮寫:CLI),簡(jiǎn)單來(lái)說(shuō)柒室,腳手架就是幫你減少「為重復(fù)性工作而做的重復(fù)性工作」的工具渡贾。

基于我們?nèi)粘J褂眠^(guò)的腳手架得知一個(gè)腳手架至少需要實(shí)現(xiàn)以下的功能點(diǎn):

  • 有不同的命令執(zhí)行操作,比如:
Commands:
  create [options] <app-name>
  add [options] <plugin> [pluginOptions]
  invoke [options] <plugin> [pluginOptions]
  inspect [options] [paths...]
  serve
  build
  ui [options]
  init [options] <template> <app-name>
  config [options] [value]
  outdated [options]
  upgrade [options] [plugin-name]
  migrate [options] [plugin-name]
  info
  help [command]
  • 可以通過(guò)命令行交互執(zhí)行問(wèn)答列表
? Please pick a preset:
? Default ([Vue 3] babel, eslint)
  Default ([Vue 2] babel, eslint)
  Manually select features
  • 最終根據(jù)用戶的問(wèn)答結(jié)果生成對(duì)應(yīng)的文件

必備的知識(shí)庫(kù)

在完善構(gòu)建腳手架前雄右,需要引入一些腳手架構(gòu)建中必須用到的工具庫(kù)空骚。

  • commander 可以自定義一些命令行指令纺讲,在輸入自定義的命令行的時(shí)候,會(huì)去執(zhí)行相應(yīng)的操作

  • inquirer 可以在命令行詢問(wèn)用戶問(wèn)題府怯,并且可以記錄用戶回答選擇的結(jié)果

  • fs-extra 是fs的一個(gè)擴(kuò)展刻诊,提供了非常多的便利API,并且繼承了fs所有方法和為fs方法添加了promise的支持牺丙。

  • chalk 可以美化終端的輸出

  • figlet 可以在終端輸出logo

  • ora 控制臺(tái)的loading動(dòng)畫

  • download-git-repo 下載遠(yuǎn)程模板

實(shí)現(xiàn)過(guò)程

創(chuàng)建腳手架

新建一個(gè)文件夾则涯,執(zhí)行 npm init 生成package.json文件。

npm init -y

創(chuàng)建bin文件夾冲簿,bin文件夾存放主要的命令程序入口文件粟判,并在文件夾下面新建index.js文件,目錄結(jié)構(gòu)如下:

fe   
├─ bin
│  ├─ index.js          
└─ package.json  

在package.json指定命令的入口文件為剛剛新建的index.js峦剔,bin下面的fe為程序的快速啟動(dòng)命令档礁。

{
  "name": "fe",
  "version": "1.0.0",
  "main": "index.js",
  "bin": {
    "fe": "./bin/index.js"
  },
  ...
}

修改入口文件index.js為如下內(nèi)容,文件以#!開頭代表這個(gè)文件被當(dāng)做一個(gè)執(zhí)行文件來(lái)執(zhí)行吝沫,可以當(dāng)做腳本運(yùn)行呻澜。后面的/usr/bin/env node代表這個(gè)文件用node執(zhí)行,node基于用戶安裝根目錄下的環(huán)境變量中查找:

#! /usr/bin/env node

console.log('hello fe cli')

最后將當(dāng)前命令鏈接到全局惨险,即可測(cè)試是否正常

npm link

此時(shí)在命令行中輸入剛剛的快速啟動(dòng)命令fe羹幸,輸出了打印的日志。說(shuō)明執(zhí)行了index.js中的代碼辫愉,接下來(lái)就開始正式搭建腳手架的功能栅受。

[圖片上傳失敗...(image-d9919-1678670772961)]

這里我們可以額外擴(kuò)展一下輸出的樣式,當(dāng)然這個(gè)不是必要的恭朗,平時(shí)安裝依賴包的時(shí)候經(jīng)常會(huì)看到不同顏色的輸出和LOGO屏镊,主要是用了chalkfiglet,示例代碼如下:

program
  .on('--help', () => {
    console.log('\r\n' + chalk.white.bgBlueBright.bold(figlet.textSync('nanChengFE', {
      font: 'Standard',
      horizontalLayout: 'default',
      verticalLayout: 'default',
      width: 80,
      whitespaceBreak: true
    })));
    console.log(`\r\nRun ${chalk.cyan(`fe <command> --help`)} for detailed usage of given command\r\n`)
  })

最終輸出的樣式如下痰腮,具體的其他LOGO樣式和字體顏色可以看官方文檔而芥。
[圖片上傳失敗...(image-a79c7c-1678670772961)]

腳手架功能完善

基于commander執(zhí)行自定義命令指令,具體的使用可以看相關(guān)文檔膀值,以下實(shí)現(xiàn)一個(gè)簡(jiǎn)單的create命令蔚出,傳入?yún)?shù)并打印輸出日志。

#! /usr/bin/env node
const { Command } = require('commander');
const program = new Command();

program
  .name('fe cli')
  .description('這里是描述文案')
  .version('1.0.0');

program.command('create <name>')
  .description('創(chuàng)建一個(gè)新工程')
  .action((name) => {
    console.log('[ 工程名稱 ] >', name)
  });

program.parse(process.argv);

通過(guò)command定義create命令虫腋,并要求必傳參數(shù)name骄酗,再通過(guò)action回調(diào)獲取到傳入的參數(shù),后續(xù)即可通過(guò)用戶輸入的名稱創(chuàng)建項(xiàng)目悦冀。
[圖片上傳失敗...(image-e7f45-1678670772961)]
接下來(lái)進(jìn)入創(chuàng)建文件的過(guò)程趋翻,在創(chuàng)建文件的時(shí)候需要校驗(yàn)當(dāng)前目錄下是否已經(jīng)存在,如果存在是否需要執(zhí)行覆蓋的動(dòng)作盒蟆。

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

// 當(dāng)前命令行執(zhí)行的目錄
const cwd  = process.cwd();
// 需要?jiǎng)?chuàng)建的目錄
const targetPath  = path.join(cwd, name)

// 目錄是否存在
if (fs.existsSync(targetPath)) {
  // 強(qiáng)制創(chuàng)建
  if (options.force) {
  } else {
    // 詢問(wèn)用戶是否需要強(qiáng)制創(chuàng)建
  }
} else {
  // 目錄不存在正常創(chuàng)建
}

以上代碼可以看出options.force命令參數(shù)可以直接進(jìn)入到強(qiáng)制創(chuàng)建的過(guò)程踏烙。如果還有其他的想法想通過(guò)不同的指令做不同的處理师骗,可以參考強(qiáng)制創(chuàng)建的邏輯進(jìn)行其他的自定義。

上面提到了詢問(wèn)用戶是否需要強(qiáng)制創(chuàng)建讨惩,這需要通過(guò)命令行與用戶進(jìn)行交互辟癌,獲取到用戶選擇的信息,inquirer這個(gè)包可以在命令行詢問(wèn)用戶問(wèn)題并拿到結(jié)果進(jìn)行后續(xù)的邏輯交互荐捻。

let { action } = await inquirer.prompt([{
  name: 'action',
  type: 'list',
  message: '目錄已存在黍少,請(qǐng)選擇:',
  choices: [{
    name: '覆蓋',
    value: 'overwrite'
  }, {
    name: '取消',
    value: false
  }]
}])

if (!action) {
  return;
} else if (action === 'overwrite') {
  // 移除已存在的目錄
  await fs.remove(targetPath)
}

[圖片上傳失敗...(image-9e376c-1678670772961)]

接下來(lái)繼續(xù)詢問(wèn)要選擇哪個(gè)模版,這個(gè)github有提供相關(guān)的接口处面,也可以自己維護(hù)模版配置的相關(guān)接口厂置,核心就是讓用戶選擇需要的模版,比如模版里有vue魂角,react昵济,小程序等不同場(chǎng)景或框架模版,用戶選擇模版后獲取到對(duì)應(yīng)的遠(yuǎn)端模版地址進(jìn)行下載野揪,這里會(huì)用到兩個(gè)新的npm包访忿,ora 可以在控制臺(tái)輸出loading動(dòng)畫,download-git-repo 執(zhí)行下載遠(yuǎn)程模板斯稳。示例代碼如下:

const spinner = ora('遠(yuǎn)端倉(cāng)庫(kù)開始下載...');
spinner.start();

// 本地存放地址
const tmp = path.join(
  process.cwd(),
  'templates',
  '工程目錄名稱',
);

// 已存在路徑執(zhí)行刪除
if (fs.existsSync(tmp)) {
  await fs.remove(tmp)
}

// 下載遠(yuǎn)端模版
download(
  '遠(yuǎn)端倉(cāng)庫(kù)地址',
  tmp,
  {
    clone: true,
  },
  (err) => {
    if (err) {
      spinner.fail('[ 遠(yuǎn)端倉(cāng)庫(kù)下載失敗 ]')
      logger.console.error();
    }
    spinner.succeed('[ 遠(yuǎn)端倉(cāng)庫(kù)下載成功 ]')
  }
)

操作過(guò)程如下:
[圖片上傳失敗...(image-1cbf1-1678670772961)]

如果只是做一個(gè)簡(jiǎn)單版本的腳手架下載模版到這里基本就差不多了醉顽,選擇對(duì)應(yīng)的框架就下載對(duì)應(yīng)的模版。但是實(shí)際開發(fā)過(guò)程中往往還不夠平挑,不同的框架中還會(huì)不同的配置,比如使用vue框架時(shí)系草,是否需要自動(dòng)加入vuex通熄,vue-router等等。所以在不同的模版中我們還要進(jìn)行下一步的用戶問(wèn)詢配置找都,基于用戶回答的結(jié)果創(chuàng)建更符合的工程文件唇辨。

這一段可以參考Vue-cli的源碼,在不同的模板項(xiàng)目中增加相關(guān)的問(wèn)詢配置能耻,比如在Vue模版中新增了以下prompts配置:

  "name": {
    "type": "string",
    "required": true,
    "message": "填寫項(xiàng)目名稱"
  },
   "description": {
    "type": "string",
    "required": false,
    "message": "填寫項(xiàng)目描述",
    "default": "ZZZ前端vue項(xiàng)目"
  },
  ...

在執(zhí)行結(jié)束下載文件后繼續(xù)執(zhí)行項(xiàng)目問(wèn)詢配置列表赏枚,效果如下:
[圖片上傳失敗...(image-28c51-1678670772961)]

最后根據(jù)獲取到的用戶回答結(jié)果進(jìn)行工程文件的生成,比如將獲取到的工程名稱直接填充到模版內(nèi)容中晓猛,根據(jù)用戶的選擇是否需要增加vuex的使用等饿幅。這塊主要會(huì)用到以下幾個(gè)依賴。

  • Metalsmith 靜態(tài)網(wǎng)站(博客戒职,項(xiàng)目)的生成器
  • handlerbars 模板編譯器栗恩,通過(guò)template和json,輸出一個(gè)html
  • consolidate 模板引擎整合庫(kù)

在vue_cli源碼中注冊(cè)了2個(gè)渲染器洪燥,類似于vue中的 v-if v-else的條件渲染磕秤,這個(gè)我們可以根據(jù)實(shí)際需要擴(kuò)展其他的條件渲染乳乌。

// register handlebars helper
Handlebars.registerHelper('if_eq', function (a, b, opts) {
  return a === b
    ? opts.fn(this)
    : opts.inverse(this)
})

Handlebars.registerHelper('unless_eq', function (a, b, opts) {
  return a === b
    ? opts.inverse(this)
    : opts.fn(this)
})

在模版文件中使用對(duì)應(yīng)的標(biāo)識(shí),結(jié)合模板編譯器即可動(dòng)態(tài)生成內(nèi)容市咆,具體的構(gòu)建細(xì)節(jié)有興趣的大家可以看看源碼或者其他文章詳細(xì)的解析汉操。

{{#if_eq userSelectVuex 'vuex'}}
import Vuex from 'vuex'
Vue.use(Vuex)
{{/if_eq}}

列舉這些是想說(shuō)明如果要改造一個(gè)腳手架生成的模版內(nèi)容我們直接可以找到對(duì)應(yīng)的配置進(jìn)行增加修改,在用戶問(wèn)詢處增加需要的場(chǎng)景字段蒙兰,獲取到對(duì)應(yīng)的值進(jìn)行模版內(nèi)容的動(dòng)態(tài)處理磷瘤。

總結(jié)

到這里我們實(shí)現(xiàn)了一個(gè)簡(jiǎn)單的開發(fā)腳手架,有自定義的快捷命令癞己,可以進(jìn)行模板選擇膀斋,可以根據(jù)不同的模板進(jìn)行下一步的配置選擇,最終生成工程文件痹雅。這只是一個(gè)簡(jiǎn)單的實(shí)現(xiàn)仰担,看vue腳手架的命令就可以看出還有很多功能點(diǎn)可以進(jìn)一步完善,有興趣的同學(xué)可以深入研究绩社。

到此本文就結(jié)束了摔蓝,看完本文如果覺(jué)得有用,記得點(diǎn)個(gè)贊支持愉耙,收藏起來(lái)說(shuō)不定哪天就用上啦~

專注前端開發(fā)贮尉,分享前端相關(guān)技術(shù)干貨,公眾號(hào):南城大前端(ID: nanchengfe)

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末朴沿,一起剝皮案震驚了整個(gè)濱河市猜谚,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌赌渣,老刑警劉巖魏铅,帶你破解...
    沈念sama閱讀 216,402評(píng)論 6 499
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異坚芜,居然都是意外死亡览芳,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,377評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門鸿竖,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)沧竟,“玉大人,你說(shuō)我怎么就攤上這事缚忧∥虮茫” “怎么了?”我有些...
    開封第一講書人閱讀 162,483評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵闪水,是天一觀的道長(zhǎng)魁袜。 經(jīng)常有香客問(wèn)我,道長(zhǎng),這世上最難降的妖魔是什么峰弹? 我笑而不...
    開封第一講書人閱讀 58,165評(píng)論 1 292
  • 正文 為了忘掉前任店量,我火速辦了婚禮,結(jié)果婚禮上鞠呈,老公的妹妹穿的比我還像新娘融师。我一直安慰自己,他們只是感情好蚁吝,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,176評(píng)論 6 388
  • 文/花漫 我一把揭開白布旱爆。 她就那樣靜靜地躺著,像睡著了一般窘茁。 火紅的嫁衣襯著肌膚如雪怀伦。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,146評(píng)論 1 297
  • 那天山林,我揣著相機(jī)與錄音房待,去河邊找鬼。 笑死驼抹,一個(gè)胖子當(dāng)著我的面吹牛桑孩,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播框冀,決...
    沈念sama閱讀 40,032評(píng)論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼流椒,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了明也?” 一聲冷哼從身側(cè)響起宣虾,我...
    開封第一講書人閱讀 38,896評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎温数,沒(méi)想到半個(gè)月后绣硝,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,311評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡帆吻,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,536評(píng)論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了咙边。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片猜煮。...
    茶點(diǎn)故事閱讀 39,696評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖败许,靈堂內(nèi)的尸體忽然破棺而出王带,到底是詐尸還是另有隱情,我是刑警寧澤市殷,帶...
    沈念sama閱讀 35,413評(píng)論 5 343
  • 正文 年R本政府宣布愕撰,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏搞挣。R本人自食惡果不足惜带迟,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,008評(píng)論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望囱桨。 院中可真熱鬧仓犬,春花似錦、人聲如沸舍肠。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)翠语。三九已至叽躯,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間肌括,已是汗流浹背点骑。 一陣腳步聲響...
    開封第一講書人閱讀 32,815評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留们童,地道東北人畔况。 一個(gè)月前我還...
    沈念sama閱讀 47,698評(píng)論 2 368
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像慧库,于是被迫代替她去往敵國(guó)和親跷跪。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,592評(píng)論 2 353

推薦閱讀更多精彩內(nèi)容