手把手教你從0搭建前端腳手架詳解
本篇文章用來為大家提供一個搭建簡易前端腳手架的思路。
先來看一眼實現(xiàn)的效果。

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

也就是上圖這句,創(chuàng)建一個新項目,只需要輸入create 項目名便可使用,在創(chuàng)建時執(zhí)行了一系列的操作,這一塊的思路很簡單,就是將git倉庫中的項目模板拷貝下來再依據(jù)使用者的不同操作對復制下來的模板的部分文件進行修改就可以了,大致思路便介紹到這里,接下來我們便來詳細的講講如何實現(xiàn),以及會用到的依賴。
腳手架目錄結構

了解搭建的腳手架
腳手架就是在啟動的時候詢問一些簡單的問題,并且通過用戶回答的結果去渲染對應的模板文件,我們接下來的流程亦是如此
腳手架的初始化
由于它是一個npm的包,因此我們需要使用npm的初始化命令,隨意新建一個文件夾打開命令行,輸入npm init,會出現(xiàn)以下情況。

| 名稱 | 意思 | 默認值 |
| package name | 包的名稱 | 創(chuàng)建文件夾時的名稱 |
| version | 版本號 | 1.0.0 |
| description | 包的描述 | 創(chuàng)建文件時的名稱 |
| entry point | 入口文件 | index.js |
| test command | 測試命令 | — |
| git repository | git倉庫地址 | — |
| git倉庫地址 | 關鍵詞,上傳到npm官網(wǎng)時在頁面中展示的關鍵詞 | — |
| author | 作者信息,對象的形式,里面存儲一些郵箱、作者名、url | — |
| license | 執(zhí)照 | 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
詢問用戶問題
創(chuàng)建入口文件
在詢問問題前我們需要先創(chuàng)建一個入口文件,創(chuàng)建完成后在package.json中添加bin項,并且將入口文件路徑寫進去

填寫完入口文件路徑后在入口文件內隨便輸出一句, 但必須在入口文件頂層聲明文件執(zhí)行方式為node。
聲明代碼:
#! /usr/bin/env node

寫完后我們需要測試一下我們是否可以正常的訪問的我們的腳手架,在本文件夾打開命令行,輸入 npm link ,該命令會創(chuàng)建一個全局訪問的包的快捷方式,這個是臨時的就是本地測試的時候用的,這個在命令行輸入你的腳手架的名稱可以看到入口文件輸出的內容。

最基本的交互命令
在完成上一步后我們就要開始與用戶進行交互了,這個時候我們就需要用到一個用于自定義命令行指令的依賴 commander。
引入依賴:
const program = require('commander')簡單介紹一下commander依賴常用的方法
command
命令。.command()的第一個參數(shù)為命令名稱。命令參數(shù)可以跟在名稱后面,也可以用.argument()單獨指定。
參數(shù)可為必選的(尖括號表示)、可選的(方括號表示)或變長參數(shù)(點號表示,如果使用,只能是最后一個參數(shù))。
例如:
// 創(chuàng)建一個create命令
.command('create <app-name>')
parse
解析。.parse()的第一個參數(shù)是要解析的字符串數(shù)組,也可以省略參數(shù)而使用process.argv,這里我們也是用process.argv用來解析node的參數(shù)。
例如:
// 解析用戶執(zhí)行命令傳入?yún)?shù) program.parse(process.argv);
option
選項。option()可以附加選項的簡介。第一個參數(shù)可以定義一個短選項名稱(-后面接單個字符)和一個長選項名稱(–后面接一個或多個單詞),使用逗號、空格或|分隔。第二個參數(shù)為該選項的簡介。
例如:
.option('-f, --force', '如果存在的話強行覆蓋')
action
處理函數(shù)。用command創(chuàng)建的自定義命令的處理函數(shù),action攜帶的實參順序就是命令上的參數(shù)的順序。
例如:
program.command('create <app-name>')
// 這個name 就代表第一個必填參數(shù) options就代表其余, 如果有第二個就在寫一個,最后一個永遠是剩余參數(shù)
.action((name, options) => {
console.log(name)
// 打印執(zhí)行結果
// require("../lib/create")(name, options)
})
編寫交互命令 create
入口文件
#! /usr/bin/env node
const program = require('commander');
const chalk = require('chalk');
// 定義命令和參數(shù)
// create命令
program
.command('create <app-name>')
.description('create a new project')
// -f or --force 為強制創(chuàng)建,如果創(chuàng)建的目錄存在則直接覆蓋
.option('-f, --force', 'overwrite target directory if it exist')
.action((name, options) => {
// 打印執(zhí)行結果
console.log('項目名稱', name)
})
// 解析用戶執(zhí)行命令傳入?yún)?shù)
program.parse(process.argv);
這里我們創(chuàng)建了一個叫 create 的自定義指令,這個命令有著必填的項目名、可以選擇的強制覆蓋的選項 -f,有著處理函數(shù)action。
我們在action中接收并打印了用戶輸入的項目名稱。
接下來我們再次運行一下自己的腳手架并帶上create命令,我的叫test
test-cli create app
出現(xiàn)如下就說明第一個命令創(chuàng)建成功了

這里請注意 解析用戶命令參數(shù)的操作一定要在最后一行否則什么都不會出現(xiàn)。
program.parse(process.argv)
到這里為止我們成功為我們腳手架創(chuàng)建了第一個交互命令,想查看更多關于 commander 的請點擊這里commander。
創(chuàng)建第一個模板項目
在創(chuàng)建了一個基本命令 create 后我們就要開始創(chuàng)建一個模板并在用戶使用該命令時復制并修改我們所創(chuàng)建的模板。
創(chuàng)建一個模板
我們在復制模板前需要一個模板,現(xiàn)在的我們隨便創(chuàng)建一個文件夾并取名為template里面創(chuàng)建一個html。

像這樣創(chuàng)建好后,我們就有了一個模板,但我們依然需要讓模板有一個可被下載、查詢的地方,這里我選擇的是使用 git 組織倉庫,因為這樣可以直接通過git提供的接口進行文件下載,包括選擇不同的模板等。
上傳模板
我們先去 git 的官網(wǎng)中新建一個存放模板的組織倉庫。

點擊圖中的位置進入組織,并點擊下圖的創(chuàng)建

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

填寫信息完基本就算創(chuàng)建成功了

接下來在組織中創(chuàng)建一個儲存庫

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

創(chuàng)建好后的倉庫,就直接將模板代碼提交至也本次創(chuàng)建的倉庫中就可以了,我們在vscode中進行演示。
先點擊推送

如果沒有推送的倉庫則會提示是否添加推送倉庫,我們點擊推送遠程倉庫,并從中找到自己的倉庫


擇完成后輸入倉庫名稱,然后會報錯,報錯原因就是因為暫無推送的內容,這個使用,正常的在 vscode 中提交代碼就行了,然后查看自己的倉庫,會出現(xiàn)上傳的內容

增加一個新的版本標簽
跟著下列圖操作




點擊發(fā)布發(fā)行版后就可以了。
下載模板
我們上傳模板后可以通過 git 提供的接口來完成下載模板的功能,首先我們先去詢問用戶要下載的模板名稱然后在用依賴包來進行下載:
https://api.github.com/orgs/geeksTest/repos 獲取該組織下的所有模板
create命令后續(xù)操作
上傳模板后,我們就可以繼續(xù)完成create命令的后續(xù)操作了。
create命令下使用創(chuàng)建函數(shù)
program
.command('create <app-name>')
.description(chalk.cyan('create a new project'))
// -f or --force 為強制創(chuàng)建,如果創(chuàng)建的目錄存在則直接覆蓋
.option('-f, --force', 'overwrite target directory if it exist')
.action((name, options) => {
// 打印執(zhí)行結果
require("../lib/create")(name, options)
})
創(chuàng)建create文件
創(chuàng)建 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. 拋出一個方法用來接收用戶要創(chuàng)建的文件夾(項目)名 和 其他參數(shù)
module.exports = async function (name, options) {
// 當前命令行選擇的目錄
const cwd = process.cwd();
// 需要創(chuàng)建的目錄地址
const targetAir = path.join(cwd, name)
//2 判斷是否存在相同的文件夾(項目)名
// 目錄是否已經存在?
if (fs.existsSync(targetAir)) {
// 是否為強制創(chuàng)建?
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(`\r\nRemoving...`)
await fs.remove(targetAir)
}
}
}
//3 新建generator類
const generator = new Generator(name, targetAir);
generator.create();
}
創(chuàng)建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 {
// 執(zhí)行傳入方法 fn
const result = await fn(...args);
// 狀態(tài)為修改為成功
spinner.succeed();
return result;
} catch (error) {
// 狀態(tài)為修改為失敗
spinner.fail('Request failed, refetch ...');
}
}
class Generator {
constructor (name, targetDir){
// 目錄名稱
this.name = name;
// 創(chuàng)建位置
this.targetDir = targetDir;
// 對 download-git-repo 進行 promise 化改造
this.downloadGitRepo = util.promisify(downloadGitRepo);
}
// 獲取用戶選擇的模板
// 1)從遠程拉取模板數(shù)據(jù)
// 2)用戶選擇自己新下載的模板名稱
// 3)return 用戶選擇的名稱
async getRepo() {
// 1)從遠程拉取模板數(shù)據(jù)
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, // 參數(shù)1: 下載地址
path.resolve(process.cwd(), this.targetDir) // 參數(shù)2: 創(chuàng)建位置
)
}
// 核心創(chuàng)建邏輯
// 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(`\r\nSuccessfully created project ${chalk.cyan(this.name)}`)
console.log(`\r\n cd ${chalk.cyan(this.name)}`)
console.log(`\r\n 啟動前請務必閱讀 ${chalk.cyan("README.md")} 文件`)
}
}
module.exports = Generator;
創(chuàng)建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接口是可以直接在瀏覽器中訪問到的,如果不想被人隨意訪問讀取數(shù)據(jù)則可以在git中增加雙因素驗證,然后每次訪問api時都會要求帶上git的訪問token否則會訪問不到,查看雙因素詳情
搭建完成
完成這一步后我們再去進行test-cli create app命令,會看到下圖。

會詢問要創(chuàng)建的模板項目,我這里的遠程組織模板叫做test,大家選擇自己的模板回車,稍等一下就會創(chuàng)建成功,并看到在你使用命令的路徑上多出一個項目名的文件夾,就成功了。

如果有對模板在下載后進行操作的需求可以使用fs依賴進行操作,到這里為止我們已經完成了一個簡易的腳手架搭建,感謝大家耐心觀看。
到此這篇關于手把手教你從0搭建前端腳手架詳解的文章就介紹到這了,更多相關搭建前端腳手架內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!

