腳手架是什么
創(chuàng)建項(xiàng)目基礎(chǔ)結(jié)構(gòu),提供項(xiàng)目規(guī)范和約定。
在眾多的項(xiàng)目中,我們發(fā)現(xiàn)他們總是有著
- 相同的組織結(jié)構(gòu)
- 相同的開發(fā)范式
- 相同的模塊依賴
- 相同的工具配置
- 相同的基礎(chǔ)代碼
腳手架就是用來解決這一些重復(fù)問題的。
常用的腳手架工具
我們常見的腳手架工具大都是為了特定項(xiàng)目類型服務(wù)的,比如vue-cli,create-react-app,只能創(chuàng)建對(duì)應(yīng)的特定項(xiàng)目類型。還有通用的腳手架工具Yeoman,較為靈活易擴(kuò)展。(CLI一般指命令行界面)
Yeoman
Yeoman是一款創(chuàng)建現(xiàn)代化web引用的腳手架工具。老牌,通用,強(qiáng)大,相比vue-cli的特定,他有更多值得我們學(xué)習(xí)的地方。它可以通過我們自定義的generator,創(chuàng)建自己想要的項(xiàng)目。
Yeoman的使用
全局范圍安裝yo
cnpm install -g yo
安裝對(duì)應(yīng)的generator (這里以想創(chuàng)建node項(xiàng)目的腳手架為例)
cnpm install generator-node -g
通過yo運(yùn)行g(shù)enerator
mkdir my-module
yo node
最后通過命令行交互填寫項(xiàng)目結(jié)構(gòu),一些描述,作者什么的
Sub Generator
有時(shí)候我們只是在原有的項(xiàng)目上添加些配置文件,例如eslint配置文件,bable配置文件等,這些文件都有些基礎(chǔ)代碼,如果自己手動(dòng)配的話也容易出錯(cuò),這就需要生成器自動(dòng)幫我們生成,這種需求就適合通過Sub Generator來實(shí)現(xiàn)。
cd my-module
yo node:readme

并不是說有g(shù)enerator都有sub generator, 比如generator-vue就沒有
總結(jié)常規(guī)使用yeoman的步驟
- 明確你的需求
- 找到合適的generator(通過yeoman官網(wǎng)找)
- 全局范圍安裝你找到的generator
- 通過Yo運(yùn)行此generator
- 通過命令行交互填寫項(xiàng)目結(jié)構(gòu)
- 生成你所需要的項(xiàng)目結(jié)構(gòu)
自定義generator,搭建自己的腳手架
比如vue官方的vue-cli,創(chuàng)建完成后只包含vue結(jié)構(gòu),如果我們還想集成vue-router,vuex,那我們就可以自行搭建自己的腳手架。
創(chuàng)建generator模塊
generator本質(zhì)上就是一個(gè)npm模塊

如果想添加Sub Generator,目錄就如下

還有Generator的模塊名稱必須是generator-<name>
mkdir generator-sample && cd generator-sample
創(chuàng)建一個(gè)package.json
npm init
這個(gè)模塊會(huì)為我們提供一個(gè)generator生成器的基類,基類中提供了一些工具函數(shù),可以讓我們寫生成器的時(shí)候更加便捷
npm install yeoman-generator
按照上述圖的目錄標(biāo)準(zhǔn),創(chuàng)建generators/app/index.js文件
// index.js
// 此文件作為 Generator 的核心入口
// 需要導(dǎo)出一個(gè)繼承自 Yeoman Generator 的類型
// Yeoman Generator 在工作時(shí)會(huì)自動(dòng)調(diào)用我們?cè)诖祟愋椭卸x的一些生命周期方法
// 我們?cè)谶@些方法中可以通過調(diào)用父類提供的一些工具方法實(shí)現(xiàn)一些功能,例如文件寫入
const Generator = require('yeoman-generator')
module.exports = class extends Generator {
writing () {
// Yeoman 自動(dòng)在生成文件階段調(diào)用此方法
// 我們這里嘗試往項(xiàng)目目錄中寫入文件
this.fs.write( // 這個(gè)fs不是node模塊的fs,這個(gè)更強(qiáng)大
this.destinationPath('temp.txt'), // 絕對(duì)路徑
Math.random().toString() //填入隨機(jī)數(shù)
)
}
}
這樣一個(gè)簡(jiǎn)單的generator就完成了。
通過命令行把這個(gè)模塊鏈接到全局范圍,使之成為一個(gè)全局模塊包
npm link
mkdir my-sample && cd my-sample
yo sample
=>生成文件啦
根據(jù)模版創(chuàng)建文件
大部分時(shí)候我們要?jiǎng)?chuàng)建的文件較多也較復(fù)雜,所以我們使用模版,可以更便捷一些
創(chuàng)建template/foo.txt
// foo.txt
這是一個(gè)模板文件
內(nèi)部可以使用 EJS 模板標(biāo)記輸出數(shù)據(jù)
例如:<%= title %>
其他的 EJS 語法也支持
<% if (success) { %>
哈哈哈
<% }%>
writing () {
// 通過模板方式寫入文件到目標(biāo)目錄
// 模板文件路徑
const tmpl = this.templatePath('foo.txt')
// 輸出目標(biāo)路徑
const output = this.destinationPath('foo.txt')
// 模板數(shù)據(jù)上下文
const context = { title: 'Hello zce~', success: true }
this.fs.copyTpl(tmpl, output, context)
}
html也同理
接收用戶輸入
通過prompting方法,接收命令行交互中用戶輸入的數(shù)據(jù)
prompting () {
// Yeoman 在詢問用戶環(huán)節(jié)會(huì)自動(dòng)調(diào)用此方法
// 在此方法中可以調(diào)用父類的 prompt() 方法發(fā)出對(duì)用戶的命令行詢問
return this.prompt([
{
type: 'input',
name: 'name',
message: 'Your project name',
default: this.appname // appname 為項(xiàng)目生成目錄名稱
}
])
.then(answers => {
// answers => { name: 'user input value' }
this.answers = answers
})
}
// 在想使用的地方 this.answer.name
Vue-generator案例
開發(fā)一個(gè)vue-cli
https://gitee.com/ericzhouhang/generator-vue-demo
generator發(fā)布
generator本質(zhì)上就是一個(gè)npm模塊,所以就是發(fā)布一個(gè)npm模塊
npm publish
Plop
一個(gè)小型前端腳手架工具,一般用來創(chuàng)建項(xiàng)目中的同類型文件
腳手架工作原理
啟動(dòng)它過后,它會(huì)自動(dòng)的去詢問你一些預(yù)設(shè)的問題,然后將你回答的結(jié)果,結(jié)合模版文件,生成項(xiàng)目的內(nèi)容。 核心代碼:
#!/usr/bin/env node
// Node CLI 應(yīng)用入口文件必須要有這樣的文件頭
//意思是指定用node執(zhí)行腳本文件。
// 腳手架的工作過程:
// 1. 通過命令行交互詢問用戶問題
// 2. 根據(jù)用戶回答的結(jié)果生成文件
const fs = require('fs')
const path = require('path')
const inquirer = require('inquirer') // 命令行交互模塊
const ejs = require('ejs')
inquirer.prompt([
{
type: 'input',
name: 'name',
message: 'Project name?'
}
])
.then(anwsers => {
// console.log(anwsers)
// 根據(jù)用戶回答的結(jié)果生成文件
// 模板目錄
const tmplDir = path.join(__dirname, 'templates') // __dirname:當(dāng)前模塊的目錄名
// 目標(biāo)目錄
const destDir = process.cwd() // Node.js 進(jìn)程的當(dāng)前工作目錄。
// 將模板下的文件全部轉(zhuǎn)換到目標(biāo)目錄
fs.readdir(tmplDir, (err, files) => {
if (err) throw err
files.forEach(file => {
// 通過模板引擎渲染文件
ejs.renderFile(path.join(tmplDir, file), anwsers, (err, result) => {
if (err) throw err
// 將結(jié)果寫入目標(biāo)文件路徑
fs.writeFileSync(path.join(destDir, file), result)
})
})
})
})