詳解基于node.js的腳手架工具開發(fā)經(jīng)歷
前言
我們團(tuán)隊(duì)的前端項(xiàng)目是基于一套內(nèi)部的后臺(tái)框架進(jìn)行開發(fā)的,這套框架是基于vue和ElementUI進(jìn)行了一些定制化包裝,并加入了一些自己團(tuán)隊(duì)設(shè)計(jì)的模塊,可以進(jìn)一步簡(jiǎn)化后臺(tái)頁(yè)面的開發(fā)工作。
這套框架拆分為基礎(chǔ)組件模塊,用戶權(quán)限模塊,數(shù)據(jù)圖表模塊三個(gè)模塊,后臺(tái)業(yè)務(wù)層的開發(fā)至少要基于基礎(chǔ)組件模塊,可以根據(jù)具體需要加入用戶權(quán)限模塊或者數(shù)據(jù)圖表模塊。盡管vue提供了一些腳手架工具vue-cli,但由于我們的項(xiàng)目是基于多頁(yè)面的配置進(jìn)行開發(fā)和打包,與vue-cli生成的項(xiàng)目結(jié)構(gòu)和配置有些不一樣,所以創(chuàng)建項(xiàng)目的時(shí)候,仍然需要人工去修改很多地方,甚至為了方便,直接從之前的項(xiàng)目copy過(guò)來(lái)然后進(jìn)行魔改。表面上看問(wèn)題不大,但其實(shí)存在很多問(wèn)題:
- 重復(fù)性工作,繁瑣而且浪費(fèi)時(shí)間
- copy過(guò)來(lái)的模板容易存在無(wú)關(guān)的代碼
- 項(xiàng)目中有很多需要配置的地方,容易忽略一些配置點(diǎn),進(jìn)而埋坑
- 人工操作永遠(yuǎn)都有可能犯錯(cuò),建新項(xiàng)目時(shí),總要花時(shí)間去排錯(cuò)
- 內(nèi)部框架也在不停的迭代,人工建項(xiàng)目往往不知道框架最新的版本號(hào)是多少,使用舊版本的框架可能會(huì)重新引入一些bug
針對(duì)以上問(wèn)題,我開發(fā)了一個(gè)腳手架工具,可以根據(jù)交互動(dòng)態(tài)生成項(xiàng)目結(jié)構(gòu),自動(dòng)添加依賴和配置,并移除不需要的文件。
接下來(lái)整理一下我的整個(gè)開發(fā)經(jīng)歷。
基本思路
開始擼代碼之前,先捋一捋思路。其實(shí),在實(shí)現(xiàn)自己的腳手架之前,我反復(fù)整理分析了vue-cli的實(shí)現(xiàn),發(fā)現(xiàn)很多有意思的模塊,并從中借鑒了它的一些好的思想。

工程結(jié)構(gòu)
工程基于 nodejs 8.4 以及 ES6 進(jìn)行開發(fā),目錄結(jié)構(gòu)如下
/bin # ------ 命令執(zhí)行文件 /lib # ------ 工具模塊 package.json
下面的部分代碼需要你先對(duì) Promise 有一定的了解才更好的理解。
使用commander.js開發(fā)命令行工具
nodejs內(nèi)置了對(duì)命令行操作的支持,node工程下 package.json 中的 bin 字段可以定義命令名和關(guān)聯(lián)的執(zhí)行文件。
{
"name": "macaw-cli",
"version": "1.0.0",
"description": "我的cli",
"bin": {
"macaw": "./bin/macaw.js"
}
}經(jīng)過(guò)這樣配置的nodejs項(xiàng)目,在使用 -g 選項(xiàng)進(jìn)行全局安裝的時(shí)候,會(huì)自動(dòng)在系統(tǒng)的 [prefix]/bin 目錄下創(chuàng)建相應(yīng)的符號(hào)鏈接(symlink)關(guān)聯(lián)到執(zhí)行文件。如果是本地安裝,這個(gè)符號(hào)鏈接會(huì)生成在 ./node_modules/.bin 目錄下。這樣做的好處是可以直接在終端中像執(zhí)行命令一樣執(zhí)行nodejs文件。關(guān)于 prefix ,可以通過(guò) npm config get prefix 獲取。
hello, commander.js
在bin目錄下創(chuàng)建一個(gè)macaw.js文件,用于處理命令行的邏輯。
touch ./bin/macaw.js
接下來(lái)就要用到github上一位神級(jí)人物——tj ——開發(fā)的模塊commander.js 。commander.js可以自動(dòng)的解析命令和參數(shù),合并多選項(xiàng),處理短參,等等,功能強(qiáng)大,上手簡(jiǎn)單。具體的使用方法可以參見(jiàn)項(xiàng)目的README。
在 macaw.js 中編寫命令行的入口邏輯
#!/usr/bin/env node
const program = require('commander') // npm i commander -D
program.version('1.0.0')
.usage('<command> [項(xiàng)目名稱]')
.command('hello', 'hello')
.parse(process.argv)接著,在 bin 目錄下創(chuàng)建 macaw-hello.js ,放一個(gè)打印語(yǔ)句
touch ./bin/macaw-hello.js
echo "console.log('hello, commander')" > ./bin/macaw-hello.js這樣,通過(guò)node命令測(cè)試一下
node ./bin/macaw.js hello
不出意外,可以在終端上看到一句話:hello, commander。
commander支持git風(fēng)格的子命令處理 ,可以根據(jù)子命令自動(dòng)引導(dǎo)到以特定格式命名的命令執(zhí)行文件,文件名的格式是 [command]-[subcommand] ,例如:
- macaw hello => macaw-hello
- macaw init => macaw-init
定義init子命令
我們需要通過(guò)一個(gè)命令來(lái)新建項(xiàng)目,按照常用的一些名詞,我們可以定義一個(gè)名為 init 的子命令。
對(duì) bin/macaw.js 做一些改動(dòng)。
const program = require('commander')
program.version('1.0.0')
.usage('<command> [項(xiàng)目名稱]')
.command('init', '創(chuàng)建新項(xiàng)目')
.parse(process.argv)在bin目錄下創(chuàng)建一個(gè) init 命令關(guān)聯(lián)的執(zhí)行文件
touch ./bin/macaw-init.js
添加如下代碼
#!/usr/bin/env node
const program = require('commander')
program.usage('<project-name>').parse(process.argv)
// 根據(jù)輸入,獲取項(xiàng)目名稱
let projectName = program.args[0]
if (!projectName) { // project-name 必填
// 相當(dāng)于執(zhí)行命令的--help選項(xiàng),顯示help信息,這是commander內(nèi)置的一個(gè)命令選項(xiàng)
program.help()
return
}
go()
function go () {
// 預(yù)留,處理子命令
}注意第一行 #!/usr/bin/env node 是干嘛的,有個(gè)關(guān)鍵詞叫Shebang,不了解的可以去搜搜看
project-name 是必填參數(shù),不過(guò),我想對(duì) project-name 進(jìn)行一些自動(dòng)化的處理。
- 當(dāng)前目錄為空,如果當(dāng)前目錄的名稱和
project-name一樣,則直接在當(dāng)前目錄下創(chuàng)建工程,否則,在當(dāng)前目錄下創(chuàng)建以project-name作為名稱的目錄作為工程的根目錄 - 當(dāng)前目錄不為空,如果目錄中不存在與
project-name同名的目錄,則創(chuàng)建以project-name作為名稱的目錄作為工程的根目錄,否則提示項(xiàng)目已經(jīng)存在,結(jié)束命令執(zhí)行。
根據(jù)以上設(shè)定,再對(duì)執(zhí)行文件做一些完善
#!/usr/bin/env node
const program = require('commander')
const path = require('path')
const fs = require('fs')
const glob = require('glob') // npm i glob -D
program.usage('<project-name>')
// 根據(jù)輸入,獲取項(xiàng)目名稱
let projectName = program.args[0]
if (!projectName) { // project-name 必填
// 相當(dāng)于執(zhí)行命令的--help選項(xiàng),顯示help信息,這是commander內(nèi)置的一個(gè)命令選項(xiàng)
program.help()
return
}
const list = glob.sync('*') // 遍歷當(dāng)前目錄
let rootName = path.basename(process.cwd())
if (list.length) { // 如果當(dāng)前目錄不為空
if (list.filter(name => {
const fileName = path.resolve(process.cwd(), path.join('.', name))
const isDir = fs.stat(fileName).isDirectory()
return name.indexOf(projectName) !== -1 && isDir
}).length !== 0) {
console.log(`項(xiàng)目${projectName}已經(jīng)存在`)
return
}
rootName = projectName
} else if (rootName === projectName) {
rootName = '.'
} else {
rootName = projectName
}
go()
function go () {
// 預(yù)留,處理子命令
console.log(path.resolve(process.cwd(), path.join('.', rootName)))
}隨意找個(gè)路徑下建一個(gè)空目錄,然后在這個(gè)目錄下執(zhí)行咱們定義的初始化命令
node /[pathto]/macaw-cli/bin/macaw.js init hello-cli
正常的話,可以看到終端上打印出項(xiàng)目的路徑。

使用download-git-repo下載模板
下載模板的工具用到另外一個(gè)node模塊download-git-repo ,參照項(xiàng)目的README,對(duì)下載工具進(jìn)行簡(jiǎn)單的封裝。
在 lib 目錄下創(chuàng)建一個(gè) download.js
const download = require('download-git-repo')
module.exports = function (target) {
target = path.join(target || '.', '.download-temp')
return new Promise(resolve, reject) {
// 這里可以根據(jù)具體的模板地址設(shè)置下載的url,注意,如果是git,url后面的branch不能忽略
download('https://github.com:username/templates-repo.git#master',
target, { clone: true }, (err) => {
if (err) {
reject(err)
} else {
// 下載的模板存放在一個(gè)臨時(shí)路徑中,下載完成后,可以向下通知這個(gè)臨時(shí)路徑,以便后續(xù)處理
resolve(target)
}
})
}
}download-git-repo模塊本質(zhì)上就是一個(gè)方法,它遵循node.js的CPS,用回調(diào)的方式處理異步結(jié)果。如果熟悉node.js的話,應(yīng)該都知道這樣處理存在一個(gè)弊端,我把它進(jìn)行了封裝,轉(zhuǎn)換成現(xiàn)在更加流行的Promise的風(fēng)格處理異步。
再一次對(duì)之前的 macaw-init.js 進(jìn)行修改
const download = require('./lib/download')
... // 之前的省略
function go () {
download(rootName)
.then(target => console.log(target))
.catch(err => console.log(err))
}下載完成之后,再將臨時(shí)下載目錄中的項(xiàng)目模板文件轉(zhuǎn)移到項(xiàng)目目錄中,一個(gè)簡(jiǎn)單的腳手架算是基本完成了。轉(zhuǎn)移的具體實(shí)現(xiàn)方法就不細(xì)說(shuō)了,可以參見(jiàn)node.js的API。你的node.js版本如果在8以下,可以用stream和pipe的方式實(shí)現(xiàn),如果是8或者9,可以使用新的API——copyFile()或者 copyFileSync() 。
but...
這個(gè)世界并非我們想象的那么簡(jiǎn)單。我們可能會(huì)希望項(xiàng)目模板中有些文件或者代碼可以動(dòng)態(tài)處理。比如:
- 新項(xiàng)目的 名稱 、 版本號(hào) 、 描述 等信息等,可以通過(guò)腳手架的交互進(jìn)行輸入,然后將輸入到模板中
- 項(xiàng)目模板并非所有文件都會(huì)用到,可以通過(guò)腳手架提供的選項(xiàng)移除掉那些無(wú)用的文件或者目錄。
對(duì)于這類情況,我們還需要借助其他工具包來(lái)完成。
使用inquirer.js處理命令行交互
對(duì)于命令行交互的功能,可以用inquirer.js 來(lái)處理。用法其實(shí)很簡(jiǎn)單:
const inquirer = require('inquirer') // npm i inquirer -D
inquirer.prompt([
{
name: 'projectName',
message: '請(qǐng)輸入項(xiàng)目名稱'
}
]).then(answers => {
console.log(`你輸入的項(xiàng)目名稱是:${answers.projectName}`)
})prompt() 接受一個(gè)問(wèn)題對(duì)象 的數(shù)據(jù),在用戶與終端交互過(guò)程中,將用戶的輸入存放在一個(gè) 答案對(duì)象 中,然后返回一個(gè) Promise ,通過(guò) then() 獲取到這個(gè)答案對(duì)象。so easy!
接下來(lái)繼續(xù)對(duì)macaw-init.js進(jìn)行完善。
// ...
const inquirer = require('inquirer')
const list = glob.sync('*')
let next = undefined
if (list.length) {
if (list.filter(name => {
const fileName = path.resolve(process.cwd(), path.join('.', name))
const isDir = fs.stat(fileName).isDirectory()
return name.indexOf(projectName) !== -1 && isDir
}).length !== 0) {
console.log(`項(xiàng)目${projectName}已經(jīng)存在`)
return
}
next = Promise.resolve(projectName)
} else if (rootName === projectName) {
next = inquirer.prompt([
{
name: 'buildInCurrent',
message: '當(dāng)前目錄為空,且目錄名稱和項(xiàng)目名稱相同,是否直接在當(dāng)前目錄下創(chuàng)建新項(xiàng)目?'
type: 'confirm',
default: true
}
]).then(answer => {
return Promise.resolve(answer.buildInCurrent ? '.' : projectName)
})
} else {
next = Promise.resolve(projectName)
}
next && go()
function go () {
next.then(projectRoot => {
if (projectRoot !== '.') {
fs.mkdirSync(projectRoot)
}
return download(projectRoot).then(target => {
return {
projectRoot,
downloadTemp: target
}
})
})
}如果當(dāng)前目錄是空的,并且目錄名稱和項(xiàng)目名稱相同,那么就通過(guò)終端交互的方式確認(rèn)是否直接在當(dāng)前目錄下創(chuàng)建項(xiàng)目,這樣會(huì)讓腳手架更加人性化。
前面提到,新項(xiàng)目的名稱、版本號(hào)、描述等信息可以直接通過(guò)終端交互到項(xiàng)目模板中,那么再進(jìn)一步完善交互流程。
// ...
// 這個(gè)模塊可以獲取node包的最新版本
const latestVersion = require('latest-version') // npm i latest-version -D
// ...
function go () {
next.then(projectRoot => {
if (projectRoot !== '.') {
fs.mkdirSync(projectRoot)
}
return download(projectRoot).then(target => {
return {
name: projectRoot,
root: projectRoot,
downloadTemp: target
}
})
}).then(context => {
return inquirer.prompt([
{
name: 'projectName',
message: '項(xiàng)目的名稱',
default: context.name
}, {
name: 'projectVersion',
message: '項(xiàng)目的版本號(hào)',
default: '1.0.0'
}, {
name: 'projectDescription',
message: '項(xiàng)目的簡(jiǎn)介',
default: `A project named ${context.name}`
}
]).then(answers => {
return latestVersion('macaw-ui').then(version => {
answers.supportUiVersion = version
return {
...context,
metadata: {
...answers
}
}
}).catch(err => {
return Promise.reject(err)
})
})
}).then(context => {
console.log(context)
}).catch(err => {
console.error(err)
})
}下載完成后,提示用戶輸入新項(xiàng)目信息。當(dāng)然,交互的問(wèn)題不僅限于此,可以根據(jù)自己項(xiàng)目的情況,添加更多的交互問(wèn)題。inquirer.js強(qiáng)大的地方在于,支持很多種交互類型,除了簡(jiǎn)單的 input ,還有 confirm 、 list 、 password 、 checkbox 等,具體可以參見(jiàn)項(xiàng)目的 README 。
然后,怎么把這些輸入的內(nèi)容到模板中呢,這時(shí)候又用到另外一個(gè)簡(jiǎn)單但又不簡(jiǎn)單的工具包——metalsmith。
使用metalsmith處理模板
引用官網(wǎng)的介紹:
An extremely simple, pluggable static site generator.
它就是一個(gè)靜態(tài)網(wǎng)站生成器,可以用在批量處理模板的場(chǎng)景,類似的工具包還有Wintersmith、 Assemble 、Hexo。它最大的一個(gè)特點(diǎn)就是 EVERYTHING IS PLUGIN ,所以,metalsmith本質(zhì)上就是一個(gè)膠水框架,通過(guò)黏合各種插件來(lái)完成生產(chǎn)工作。
給項(xiàng)目模板添加變量占位符
模板引擎我選擇handlebars。當(dāng)然,還可以有其他選擇,例如ejs、 jade 、 swig 。
用handlebars的語(yǔ)法對(duì)模板做一些調(diào)整,例如修改模板中的 package.json
{
"name": "{{projectName}}",
"version": "{{projectVersion}}",
"description": "{{projectDescription}}",
"author": "Forcs Zhang",
"private": true,
"scripts": {
"dev": "node build/dev-server.js",
"start": "node build/dev-server.js",
"build": "node build/build.js",
"unit": "cross-env BABEL_ENV=test karma start test/unit/karma.conf.js --single-run",
"test": "npm run unit",
"lint": "eslint --ext .js,.vue src test/unit/specs"
},
"dependencies": {
"element-ui": "^2.0.7",
"macaw-ui": "{{supportUiVersion}}",
"vue": "^2.5.2",
"vue-router": "^2.3.1"
},
...
}package.json 的 name 、 version 、 description 字段的內(nèi)容被替換成了handlebar語(yǔ)法的占位符,模板中其他地方也做類似的替換,完成后重新提交模板的更新。
實(shí)現(xiàn)腳手架給模板插值的功能
在 lib 目錄下創(chuàng)建 generator.js ,封裝metalsmith。
touch ./lib/generator.js
// npm i handlebars metalsmith -D
const Metalsmith = require('metalsmith')
const Handlebars = require('handlebars')
const rm = require('rimraf').sync
module.exports = function (metadata = {}, src, dest = '.') {
if (!src) {
return Promise.reject(new Error(`無(wú)效的source:${src}`))
}
return new Promise((resolve, reject) => {
Metalsmith(process.cwd())
.metadata(metadata)
.clean(false)
.source(src)
.destination(dest)
.use((files, metalsmith, done) => {
const meta = metalsmith.metadata()
Object.keys(files).forEach(fileName => {
const t = files[fileName].contents.toString()
files[fileName].contents = new Buffer(Handlebars.compile(t)(meta))
})
done()
}).build(err => {
rm(src)
err ? reject(err) : resolve()
})
})
}給 macaw-init.js 的 go() 添加生成邏輯。
// ...
const generator = require('../lib/generator')
function go () {
next.then(projectRoot => {
// ...
}).then(context => {
// 添加生成的邏輯
return generator(context)
}).then(context => {
console.log('創(chuàng)建成功:)')
}).catch(err => {
console.error(`創(chuàng)建失?。?{err.message}`)
})
}至此,一個(gè)帶交互,可動(dòng)態(tài)給模板插值的腳手架算是基本完成了。
tips:推薦一下tj的另一個(gè)工具包: consolidate.js ,在vue-cli中發(fā)現(xiàn)的,感興趣的話可以去了解一下。
美化我們的腳手架
通過(guò)一些工具包,讓腳手架更加人性化。這里介紹兩個(gè)在vue-cli中發(fā)現(xiàn)的工具包:
ora - 顯示spinner
chalk - 給枯燥的終端界面添加一些色彩
這兩個(gè)工具包用起來(lái)不復(fù)雜,用好了會(huì)讓腳手架看起來(lái)更加高大上
用ora優(yōu)化加載等待的交互
ora可以用在加載等待的場(chǎng)景中,比如腳手架中下載項(xiàng)目模板的時(shí)候可以使用,如果給模板插值生成項(xiàng)目的過(guò)程也有明顯等待的話,也可以使用。
以下載為例,對(duì) download.js 做一些改良:
npm i ora -D
const download = require('download-git-repo')
const ora = require('ora')
module.exports = function (target) {
target = path.join(target || '.', '.download-temp')
return new Promise(resolve, reject) {
const url = 'https://github.com:username/templates-repo.git#master'
const spinner = ora(`正在下載項(xiàng)目模板,源地址:${url}`)
spinner.start()
download(url, target, { clone: true }, (err) => {
if (err) {
spinner.fail() // wrong :(
reject(err)
} else {
spinner.succeed() // ok :)
resolve(target)
}
})
}
}用chalk優(yōu)化終端信息的顯示效果
chalk可以給終端文字設(shè)置顏色。
// ...
const chalk = require('chalk')
const logSymbols = require('log-symbols')
// ...
function go () {
// ...
next.then(/* ... */)
/* ... */
.then(context => {
// 成功用綠色顯示,給出積極的反饋
console.log(logSymbols.success, chalk.green('創(chuàng)建成功:)'))
console.log()
console.log(chalk.green('cd ' + context.root + '\nnpm install\nnpm run dev'))
}).catch(err => {
// 失敗了用紅色,增強(qiáng)提示
console.error(logSymbols.error, chalk.red(`創(chuàng)建失敗:${error.message}`))
})
}
根據(jù)輸入項(xiàng)移除模板中不需要的文件
有時(shí)候,項(xiàng)目模板中并不是所有文件都是需要的。為了保證新生成的項(xiàng)目中盡可能的不存在臟代碼,我們可能需要根據(jù)腳手架的輸入項(xiàng)來(lái)確認(rèn)最終生成的項(xiàng)目結(jié)構(gòu),將沒(méi)用的文件或者目錄移除。比如vue-cli,創(chuàng)建項(xiàng)目時(shí)會(huì)詢問(wèn)我們是否需要加入測(cè)試模塊,如果不需要,最終生成的項(xiàng)目代碼中是不包含測(cè)試相關(guān)的代碼的。這個(gè)功能如何實(shí)現(xiàn)呢?
實(shí)現(xiàn)的思路
我參考了git的思路,定義個(gè) ignore 文件,將需要被忽略的文件名列在這個(gè) ignore 文件里,配上模板語(yǔ)法。腳手架在生成項(xiàng)目的時(shí)候,根據(jù)輸入項(xiàng)先渲染這個(gè) ignore 文件,然后根據(jù) ignore 文件的內(nèi)容移除不需要的模板文件,然后再渲染真正會(huì)用到的項(xiàng)目模板,最終生成項(xiàng)目。

實(shí)現(xiàn)方案
根據(jù)以上思路,我先定義了屬于我們項(xiàng)目自己的 ignore 文件,取名為 templates.ignore 。
然后在這個(gè) ignore 文件中添加需要被忽略的文件名。
{{#unless supportMacawAdmin}}
# 如果不開啟admin后臺(tái),登錄頁(yè)面和密碼修改頁(yè)面是不需要的
src/entry/login.js
src/entry/password.js
{{/unless}}
# 最終生成的項(xiàng)目中不需要ignore文字自身
templates.ignore然后在 lib/generator.js 中添加對(duì) templates.ignore 的處理邏輯
// ...
const minimatch = require('minimatch') // https://github.com/isaacs/minimatch
module.exports = function (metadata = {}, src, dest = '.') {
if (!src) {
return Promise.reject(new Error(`無(wú)效的source:${src}`))
}
return new Promise((resolve, reject) => {
const metalsmith = Metalsmith(process.cwd())
.metadata(metadata)
.clean(false)
.source(src)
.destination(dest)
// 判斷下載的項(xiàng)目模板中是否有templates.ignore
const ignoreFile = path.join(src, 'templates.ignore')
if (fs.existsSync(ignoreFile)) {
// 定義一個(gè)用于移除模板中被忽略文件的metalsmith插件
metalsmith.use((files, metalsmith, done) => {
const meta = metalsmith.metadata()
// 先對(duì)ignore文件進(jìn)行渲染,然后按行切割ignore文件的內(nèi)容,拿到被忽略清單
const ignores = Handlebars.compile(fs.readFileSync(ignoreFile).toString())(meta)
.split('\n').filter(item => !!item.length)
Object.keys(files).forEach(fileName => {
// 移除被忽略的文件
ignores.forEach(ignorePattern => {
if (minimatch(fileName, ignorePattern)) {
delete files[fileName]
}
})
})
done()
})
}
metalsmith.use((files, metalsmith, done) => {
const meta = metalsmith.metadata()
Object.keys(files).forEach(fileName => {
const t = files[fileName].contents.toString()
files[fileName].contents = new Buffer(Handlebars.compile(t)(meta))
})
done()
}).build(err => {
rm(src)
err ? reject(err) : resolve()
})
})
}基于插件思想的metalsmith很好擴(kuò)展,實(shí)現(xiàn)也不復(fù)雜,具體過(guò)程可參見(jiàn)代碼中的注釋。
總結(jié)
經(jīng)過(guò)對(duì)vue-cli的整理,借助了很多node模塊,整個(gè)腳手架的實(shí)現(xiàn)并不復(fù)雜。
- 將項(xiàng)目模板與腳手架工具分離,可以更好的維護(hù)模板和腳手架工具。
- 通過(guò)commander.js處理命令行
- 通過(guò)download-git-repo處理下載
- 通過(guò)inquirer.js處理終端交互
- 通過(guò)metalsmith和模板引擎將交互輸入項(xiàng)到項(xiàng)目模板中
- 參考了git的ignore的思路,利用自定義的templates.ignore動(dòng)態(tài)化的移除不必要的文件和目錄
以上就是我開發(fā)腳手架的主要經(jīng)歷,中間還有很多不足的地方,今后再慢慢完善吧。
最后說(shuō)一下,其實(shí)vue-cli能做的事情還有很多,具體的可以看看項(xiàng)目的README和源碼。關(guān)于腳手架的開發(fā),不一定要完全造個(gè)輪子,可以看看另外一個(gè)很強(qiáng)大的模塊YEOMAN,借助這個(gè)模塊也可以很快的實(shí)現(xiàn)自己的腳手架工具。
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
如何在Nestjs和Vue3中使用socket.io示例詳解
這篇文章主要為大家介紹了如何在Nestjs和Vue3中使用socket.io示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-08-08
node.js多個(gè)異步過(guò)程中判斷執(zhí)行是否完成的解決方案
這篇文章主要給大家介紹了關(guān)于node.js多個(gè)異步過(guò)程中判斷執(zhí)行是否完成的幾種解決方案,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來(lái)一起看看吧。2017-12-12
如何利用Node.js與JSON搭建簡(jiǎn)單的動(dòng)態(tài)服務(wù)器
這篇文章主要給大家介紹了關(guān)于如何利用Node.js與JSON搭建簡(jiǎn)單的動(dòng)態(tài)服務(wù)器的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或者使用Node.js具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-06-06
Node.js內(nèi)置模塊events事件監(jiān)聽(tīng)發(fā)射詳解
這篇文章主要為大家介紹了Node.js內(nèi)置模塊events事件監(jiān)聽(tīng)發(fā)射詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-02-02
node.js 中國(guó)天氣預(yù)報(bào) 簡(jiǎn)單實(shí)現(xiàn)
本文主要介紹使用node.js做一個(gè)自用的天氣插件,需要的朋友可以參考下。2016-06-06
node通過(guò)express搭建自己的服務(wù)器
本篇文章主要介紹了node通過(guò)express搭建自己的服務(wù)器 ,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-09-09

