首頁 > 軟體

手把手教你從0搭建前端腳手架詳解

2023-04-02 06:01:49

本篇文章用來為大家提供一個搭建簡易前端腳手架的思路。

先來看一眼實現的效果。

從圖上來看這個腳手架的功能非常的簡單隻有一個建立的命令,其他都是幫助和顯示版本號的。

也就是上圖這句,建立一個新專案,只需要輸入create 專案名便可使用,在建立時執行了一系列的操作,這一塊的思路很簡單,就是將git倉庫中的專案模板拷貝下來再依據使用者的不同操作對複製下來的模板的部分檔案進行修改就可以了,大致思路便介紹到這裡,接下來我們便來詳細的講講如何實現,以及會用到的依賴。

腳手架目錄結構

瞭解搭建的腳手架

腳手架就是在啟動的時候詢問一些簡單的問題,並且通過使用者回答的結果去渲染對應的模板檔案,我們接下來的流程亦是如此

腳手架的初始化

由於它是一個npm的包,因此我們需要使用npm的初始化命令,隨意新建一個資料夾開啟命令列,輸入npm init,會出現以下情況。

名稱意思預設值
package name包的名稱建立資料夾時的名稱
version版本號1.0.0
description包的描述建立檔案時的名稱
entry point入口檔案index.js
test command測試命令
git repositorygit倉庫地址
git倉庫地址關鍵詞,上傳到npm官網時在頁面中展示的關鍵詞
author作者資訊,物件的形式,裡面儲存一些郵箱、作者名、url
license執照MIT

這就是輸入初始化命令時會詢問的東西,回答完這些後就會生成一個 package.json 的檔案,這個檔案就是記錄包的資訊。

如果想要了解更多,可檢視如下地址:
package.json詳解

腳手架依賴安裝

用到如下依賴請安裝。

npm i path
npm i chalk@4.1.0
npm i fs-extra
npm i inquirer@8.2.4
npm i commander
npm i axios
npm i download-git-repo

詢問使用者問題

建立入口檔案

在詢問問題前我們需要先建立一個入口檔案,建立完成後在package.json中新增bin項,並且將入口檔案路徑寫進去

填寫完入口檔案路徑後在入口檔案內隨便輸出一句, 但必須在入口檔案頂層宣告檔案執行方式為node。

宣告程式碼:

#! /usr/bin/env node

寫完後我們需要測試一下我們是否可以正常的存取的我們的腳手架,在本資料夾開啟命令列,輸入 npm link ,該命令會建立一個全域性存取的包的快捷方式,這個是臨時的就是本地測試的時候用的,這個在命令列輸入你的腳手架的名稱可以看到入口檔案輸出的內容。

最基本的互動命令

在完成上一步後我們就要開始與使用者進行互動了,這個時候我們就需要用到一個用於自定義命令列指令的依賴 commander。

引入依賴:

const program = require('commander')

簡單介紹一下commander依賴常用的方法

command

命令。.command()的第一個引數為命令名稱。命令引數可以跟在名稱後面,也可以用.argument()單獨指定。

引數可為必選的(尖括號表示)、可選的(方括號表示)或變長引數(點號表示,如果使用,只能是最後一個引數)。

例如:

// 建立一個create命令
.command('create <app-name>')

parse

解析。.parse()的第一個引數是要解析的字串陣列,也可以省略引數而使用process.argv,這裡我們也是用process.argv用來解析node的引數。

例如:

// 解析使用者執行命令傳入引數
program.parse(process.argv);

option

選項。option()可以附加選項的簡介。第一個引數可以定義一個短選項名稱(-後面接單個字元)和一個長選項名稱(–後面接一個或多個單詞),使用逗號、空格或|分隔。第二個引數為該選項的簡介。

例如:

.option('-f, --force', '如果存在的話強行覆蓋')

action

處理常式。用command建立的自定義命令的處理常式,action攜帶的實參順序就是命令上的引數的順序。

例如:

program.command('create <app-name>')
// 這個name 就代表第一個必填引數 options就代表其餘, 如果有第二個就在寫一個,最後一個永遠是剩餘引數
.action((name, options) => {
    console.log(name)
    // 列印執行結果
    // require("../lib/create")(name, options)
})

編寫互動命令 create

入口檔案

#! /usr/bin/env node
const program = require('commander');
const chalk = require('chalk');

// 定義命令和引數
// create命令
program
.command('create <app-name>')
.description('create a new project')
// -f or --force 為強制建立,如果建立的目錄存在則直接覆蓋
.option('-f, --force', 'overwrite target directory if it exist')
.action((name, options) => {
    // 列印執行結果
    console.log('專案名稱', name)
})

// 解析使用者執行命令傳入引數
program.parse(process.argv);

這裡我們建立了一個叫 create 的自定義指令,這個命令有著必填的專案名、可以選擇的強制覆蓋的選項 -f,有著處理常式action

我們在action中接收並列印了使用者輸入的專案名稱。

接下來我們再次執行一下自己的腳手架並帶上create命令,我的叫test

test-cli create app

出現如下就說明第一個命令建立成功了

這裡請注意 解析使用者命令引數的操作一定要在最後一行否則什麼都不會出現。

program.parse(process.argv)

到這裡為止我們成功為我們腳手架建立了第一個互動命令,想檢視更多關於 commander 的請點選這裡commander。

建立第一個模板專案

在建立了一個基本命令 create 後我們就要開始建立一個模板並在使用者使用該命令時複製並修改我們所建立的模板。

建立一個模板

我們在複製模板前需要一個模板,現在的我們隨便建立一個資料夾並取名為template裡面建立一個html。

像這樣建立好後,我們就有了一個模板,但我們依然需要讓模板有一個可被下載、查詢的地方,這裡我選擇的是使用 git 組織倉庫,因為這樣可以直接通過git提供的介面進行檔案下載,包括選擇不同的模板等。

上傳模板

我們先去 git 的官網中新建一個存放模板的組織倉庫。

點選圖中的位置進入組織,並點選下圖的建立

會進入到付費的位置,沒有大需求就選免費

填寫資訊完基本就算建立成功了

接下來在組織中建立一個儲存庫

這裡我們暫且選擇可見的倉庫,千萬不要選擇私人倉庫,否則git介面會找不該倉庫

建立好後的倉庫,就直接將模板程式碼提交至也本次建立的倉庫中就可以了,我們在vscode中進行演示。
先點選推播

如果沒有推播的倉庫則會提示是否新增推播倉庫,我們點選推播遠端倉庫,並從中找到自己的倉庫

擇完成後輸入倉庫名稱,然後會報錯,報錯原因就是因為暫無推播的內容,這個使用,正常的在 vscode 中提交程式碼就行了,然後檢視自己的倉庫,會出現上傳的內容

增加一個新的版本標籤

跟著下列圖操作

點選釋出發行版後就可以了。

下載模板

我們上傳模板後可以通過 git 提供的介面來完成下載模板的功能,首先我們先去詢問使用者要下載的模板名稱然後在用依賴包來進行下載:
https://api.github.com/orgs/geeksTest/repos 獲取該組織下的所有模板

create命令後續操作

上傳模板後,我們就可以繼續完成create命令的後續操作了。

create命令下使用建立函數

program
.command('create <app-name>')
.description(chalk.cyan('create a new project'))
// -f or --force 為強制建立,如果建立的目錄存在則直接覆蓋
.option('-f, --force', 'overwrite target directory if it exist')
.action((name, options) => {
    // 列印執行結果
    require("../lib/create")(name, options)
})

建立create檔案

建立 create 檔案用來回應使用者的 create 命令。

這裡用到的依賴

// lib/create.js

const path = require('path')
// fs-extra 是對 fs 模組的擴充套件,支援 promise 語法
const fs = require('fs-extra')
// 用於互動式詢問使用者問題
const inquirer = require('inquirer')
// 匯出Generator類
const Generator = require('./Generator')

//1. 丟擲一個方法用來接收使用者要建立的資料夾(專案)名 和 其他引數
module.exports = async function (name, options) {
  // 當前命令列選擇的目錄
  const cwd  = process.cwd();
  // 需要建立的目錄地址
  const targetAir  = path.join(cwd, name)
  
  //2 判斷是否存在相同的資料夾(專案)名
  // 目錄是否已經存在?
  if (fs.existsSync(targetAir)) {
    // 是否為強制建立?
    if (options.force) {
      await fs.remove(targetAir)
    } 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;
      } else if (action === 'overwrite') {
        // 移除已存在的目錄
        console.log(`rnRemoving...`)
        await fs.remove(targetAir)
      }
    }
  }

  //3 新建generator類
  const generator = new Generator(name, targetAir);
  generator.create();
}

建立generator類

// lib/Generator.js

const { getRepoList, getTagList } = require('./http')
const ora = require('ora')
const inquirer = require('inquirer')
const util = require('util')
const downloadGitRepo = require('download-git-repo') // 不支援 Promise
const chalk = require('chalk')
const path = require('path');
const fs = require("fs-extra");

// 新增載入動畫
async function wrapLoading(fn, message, ...args) {
  // 使用 ora 初始化,傳入提示資訊 message
  const spinner = ora(message);
  // 開始載入動畫
  spinner.start();

  try {
    // 執行傳入方法 fn
    const result = await fn(...args);
    // 狀態為修改為成功
    spinner.succeed();
    return result; 
  } catch (error) {
    // 狀態為修改為失敗
    spinner.fail('Request failed, refetch ...');
  } 
}

class Generator {
  constructor (name, targetDir){
    // 目錄名稱
    this.name = name;
    // 建立位置
    this.targetDir = targetDir;
    // 對 download-git-repo 進行 promise 化改造
    this.downloadGitRepo = util.promisify(downloadGitRepo);
  }

  // 獲取使用者選擇的模板
  // 1)從遠端拉取模板資料
  // 2)使用者選擇自己新下載的模板名稱
  // 3)return 使用者選擇的名稱

  async getRepo() {
    // 1)從遠端拉取模板資料
    const repoList = await wrapLoading(getRepoList, 'waiting fetch template');
    if (!repoList) return;
    // 過濾我們需要的模板名稱
    const repos = repoList.map(item => item.name);

    // 2)使用者選擇自己新下載的模板名稱
    const { repo } = await inquirer.prompt({
      name: 'repo',
      type: 'list',
      choices: repos,
      message: 'Please choose a template to create project'
    })

    // 3)return 使用者選擇的名稱
    return repo;
  }

  // 獲取使用者選擇的版本
  // 1)基於 repo 結果,遠端拉取對應的 tag 列表
  // 2)自動選擇最新版的 tag

  async getTag(repo) {
    // 1)基於 repo 結果,遠端拉取對應的 tag 列表
    const tags = await wrapLoading(getTagList, 'waiting fetch tag', repo);
    if (!tags) return;
    
    // 過濾我們需要的 tag 名稱
    const tagsList = tags.map(item => item.name);

    // 2)return 使用者選擇的 tag
    return tagsList[0]
  }

  // 下載遠端模板
  // 1)拼接下載地址
  // 2)呼叫下載方法
  async download(repo, tag){
    // 1)拼接下載地址
    const requestUrl = `geeksTest/${repo}${tag ? '#'+tag : ''}`;

    // 2)呼叫下載方法
    await wrapLoading(
      this.downloadGitRepo, // 遠端下載方法
      'waiting download template', // 載入提示資訊
      requestUrl, // 引數1: 下載地址
      path.resolve(process.cwd(), this.targetDir) // 引數2: 建立位置
    ) 
  }

  // 核心建立邏輯
  // 1)獲取模板名稱
  // 2)獲取 tag 名稱
  // 3)下載模板到模板目錄
  // 4) 對uniapp模板中部分檔案進行讀寫
  // 5) 模板使用提示
  async create(){

    // 1)獲取模板名稱
    const repo = await this.getRepo()

    // 2) 獲取 tag 名稱
    const tag = await this.getTag(repo)

    // 3)下載模板到模板目錄
    await this.download(repo, tag)
    
    // 5)模板使用提示
    console.log(`rnSuccessfully created project ${chalk.cyan(this.name)}`)
    console.log(`rn  cd ${chalk.cyan(this.name)}`)
    console.log(`rn  啟動前請務必閱讀 ${chalk.cyan("README.md")} 檔案`)
  
  }
}

module.exports = Generator;

建立http檔案

新建一個http.js的檔案用來存放要請求的介面,我們用axios去請求.

依賴安裝

npm i commander
// lib/http.js

// 通過 axios 處理請求
const axios = require('axios')

axios.interceptors.response.use(res => {
  return res.data;
})

/**
 * 獲取模板列表
 * @returns Promise
 */
async function getRepoList() {
  return axios.get('https://api.github.com/orgs/geeksTest/repos')
}

/**
 * 獲取版本資訊
 * @param {string} repo 模板名稱
 * @returns Promise
 */
async function  getTagList(repo) {
  return axios.get(`https://api.github.com/repos/geeksTest/${repo}/tags`)
}

module.exports = {
  getRepoList,
  getTagList
}

最後匯出了兩個方法, 模板列表、模板tag列表。
這個時候的api介面是可以直接在瀏覽器中存取到的,如果不想被人隨意存取讀取資料則可以在git中增加雙因素驗證,然後每次存取api時都會要求帶上git的存取token否則會存取不到,檢視雙因素詳情

搭建完成

完成這一步後我們再去進行test-cli create app命令,會看到下圖。

會詢問要建立的模板專案,我這裡的遠端組織模板叫做test,大家選擇自己的模板回車,稍等一下就會建立成功,並看到在你使用命令的路徑上多出一個專案名的資料夾,就成功了。

如果有對模板在下載後進行操作的需求可以使用fs依賴進行操作,到這裡為止我們已經完成了一個簡易的腳手架搭建,感謝大家耐心觀看。

到此這篇關於手把手教你從0搭建前端腳手架詳解的文章就介紹到這了,更多相關搭建前端腳手架內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!


IT145.com E-mail:sddin#qq.com