什么是腳手架?
簡(jiǎn)而言之它就是一個(gè)工具,方便我們新建項(xiàng)目用的,通過這個(gè)工具創(chuàng)建的項(xiàng)目之后我們可以直接開發(fā)了懊昨。
市面常見的腳手架?
- vue-cli 提供vue開發(fā)的webpack,pwa等模板
- create-react-app React團(tuán)隊(duì)官方出的一個(gè)構(gòu)建React單頁面應(yīng)用的腳手架工具
- Yeoman 通用型腳手架春宣,過于通用,不夠?qū)W⒓的悖褂寐闊?/li>
為什么要自己搭建月帝?
- 專心快速的完成業(yè)務(wù)
- 代碼更加規(guī)范化
- 少造輪子,少拷貝代碼幽污,簡(jiǎn)化流程
如何搭建一款簡(jiǎn)易腳手架嚷辅?
一、目錄搭建
- 創(chuàng)建一個(gè)文件夾距误,取名為lang-cli
- 在該目錄下執(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"
}
- 新建一個(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')
- 由于一直在本地用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)建邏輯
- 創(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