vue 使用fs_模仿vue-cli,手写一个脚手架
vue-cli
在vue的開發(fā)的過(guò)程中,經(jīng)常會(huì)使用到vue-cli腳手架工具去生成一個(gè)項(xiàng)目。在終端運(yùn)行命令vue create hello-world后,就會(huì)有許多自動(dòng)的腳本運(yùn)行。
- 為什么會(huì)這樣運(yùn)行呢?
- 我們自己是否也能寫一個(gè)腳手架工具?
帶著這樣的疑問(wèn),我們先來(lái)看看vue-cli。
解讀vue-cli
首先我們可以來(lái)到vue-cli的安裝目錄:
mac用戶來(lái)到路徑:/usr/local/lib/node_modules 可以看到(windows可以自行到全局安裝的目錄下查看)
此時(shí)用我們的編輯器 打開@vue文件:
lib內(nèi)放的是具體各種配置和各種類,對(duì)于我們來(lái)說(shuō),這個(gè)目錄內(nèi)的就是所謂的業(yè)務(wù)邏輯。
bin內(nèi)放的就是腳本命令的入口,調(diào)用lib的入口,入口在package.json內(nèi)紅框定義。我們姑且先放下業(yè)務(wù)邏輯,來(lái)看看這個(gè)入口文件。
現(xiàn)在,我們先放下所有的疑慮,我們打開bin/vue.js。我們可以看到以下內(nèi)容:
看完之后有什么感覺(jué)?咦?怎么跟終端內(nèi)輸出的好像?沒(méi)錯(cuò),就是這樣,這就是我們使用vue-cli的時(shí)候具體的命令。我們?nèi)ソK端輸入vue:
發(fā)現(xiàn)了嗎,終端內(nèi)的具體命令全在vue.js內(nèi)定義過(guò)了。
program.command('create <app-name>').description('create a new project powered by vue-cli-service') program.command('add <plugin> [pluginOptions]').description('install a plugin and invoke its generator in an already created project') program.command('invoke <plugin> [pluginOptions]')好了,剩下代碼有興趣的可以自行打開對(duì)應(yīng)目錄讀下去,可以學(xué)習(xí)人家優(yōu)秀的設(shè)計(jì)思想。對(duì)于本文來(lái)說(shuō),剩下的許多東西都是業(yè)務(wù)代碼了。我們開頭的疑問(wèn)為什么會(huì)這樣運(yùn)行呢?已經(jīng)了解了一個(gè)大概了。我們現(xiàn)在來(lái)看看我們自己是否也能寫一個(gè)腳手架工具?,答案是肯定的。開始動(dòng)手吧。
手寫一個(gè)自己的腳手架
先來(lái)看看可能需要用到哪些npm的包:
- commander:參數(shù)解析
- inquirer:交互式命令行工具,有他就可以實(shí)現(xiàn)命令行的選擇功能
- chalk:輸出文本顏色,為了美麗~
后續(xù)可能隨著模塊的增加,會(huì)出現(xiàn)更多需要的包
1. 創(chuàng)建項(xiàng)目
npm init -y # 初始化package.json2. 創(chuàng)建文件目錄
- 在package.json內(nèi)添加“bin”
- bin下的文件沒(méi)有格式,且第一行必須是#! /usr/bin/env node
3. 鏈接包到全局
npm link # // 取消鏈接 npm unlink有時(shí)候可能需要在上面命令后面拼接 --force,mac權(quán)限問(wèn)題記得前面sudo
可以去目錄:/usr/local/lib/node_modules 查看,發(fā)現(xiàn)我們多了一個(gè),同時(shí)在這時(shí)候終端輸入一下試試,我們?cè)趐ackage.json下叫的name叫superman-cli,所以我們的命令就是叫superman-cli:
好了,基礎(chǔ)配置初始化的工作全部結(jié)束了!
4. 第一個(gè)命令
首先安裝包c(diǎn)ommander
npm install commander --save在目錄bin/superman內(nèi)
#! /usr/bin/env node// console.log(1) const program = require('commander')program.version(`Version is ${require('../package.json').version}`).description('從0開始 手寫腳手架').usage('<command> [options]')program.parse(process.argv)測(cè)試有效!
然后我們?cè)谇懊鎂ersion命令下加入代碼:
program.command('create <app-name>').description('create a new project').option('-f, --force', 'Overwrite target directory if it exists').option('-c, --clone', 'Use git clone when fetching remote preset').action((name, cmd) => {console.log('name', name)console.log('cmd', cmd)})仔細(xì)對(duì)比我們的代碼合終端的輸出,我們就可以看到我們寫的很多東西都生效了。接下來(lái)我們就優(yōu)化一下.action下的參數(shù),畢竟一大堆也不好處理:
program.command('create <app-name>').description('create a new project').option('-f, --force', 'Overwrite target directory if it exists').option('-c, --clone', 'Use git clone when fetching remote preset').action((name, cmd) => {const options = cleanArgs(cmd)console.log(options)}) function camelize (str) {return str.replace(/-(w)/g, (_, c) => c ? c.toUpperCase() : '') } function cleanArgs (cmd) {const args = {}// console.log(cmd)cmd.options.forEach(o => {const key = camelize(o.long.replace(/^--/, ''))// console.log(key)// console.log(cmd[key])// console.log(typeof cmd[key])if (typeof cmd[key] !== 'function' && typeof cmd[key] !== 'undefined') {args[key] = cmd[key]}})return args }具體不懂的也可以像我注釋的console.log一樣,慢慢看就明白了
image
我們第一個(gè)命令已經(jīng)完成一大半了,接下來(lái)就是我們這個(gè)create命令具體干什么事情。(在這個(gè)文件里,我們只管命令,就像vue-cli一樣,這也是我們需要學(xué)習(xí)的地方,模塊如何去處理)
// 在上面.action內(nèi)補(bǔ)充一行代碼.action((name, cmd) => {const options = cleanArgs(cmd)console.log(options)require('../lib/create')(name, options)})同時(shí)去lib下創(chuàng)建文件create.js
const path = require('path') // const fs = require('fs-extra') async function create (projectName, options) {console.log(projectName, options)const cwd = process.cwd(); // 獲取當(dāng)前命令執(zhí)行時(shí)的工作目錄const targetDir = path.join(cwd,projectName); // 目標(biāo)目錄console.log(targetDir) }module.exports = (...args) => {return create(...args) }繼續(xù)執(zhí)行superman-cli create hello -f,我們可以得到,force:true,如果新建的話,將來(lái)的目錄會(huì)是/Users/chenjing/hello
接下來(lái)我們嘗試創(chuàng)建目錄hello,不過(guò)我們需要考慮幾個(gè)問(wèn)題:
- 是否已經(jīng)存在目錄hello了?(使用fs-extra包)
- 若存在是要?jiǎng)h除覆蓋還是停止操作?(這里就需要用到插件inquirer啦,進(jìn)行選擇)
直接上代碼:
const path = require('path') const fsextra = require('fs-extra') const fs = require('fs') const Inquirer = require('inquirer') async function create (projectName, options) {console.log(projectName, options)const cwd = process.cwd(); // 獲取當(dāng)前命令執(zhí)行時(shí)的工作目錄const targetDir = path.join(cwd,projectName); // 目標(biāo)目錄console.log(targetDir)if (fsextra.existsSync(targetDir)) {if (options.force) {// 如果強(qiáng)制創(chuàng)建 ,刪除已有的await fsextra.remove(targetDir);console.log('刪除成功')createDir(projectName)} 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) {console.log('取消操作')return} else if (action === 'overwrite') {console.log(`rnRemoving....`);await fsextra.remove(targetDir)console.log('刪除成功')createDir(projectName)}}} else {createDir(projectName)} } function createDir (projectName) {fs.mkdir(`./${projectName}`, function (err) {if (err) {console.log('創(chuàng)建失敗')} else {console.log('創(chuàng)建成功')}}) }module.exports = (...args) => {return create(...args) }看效果,先來(lái)的一個(gè)空目錄
superman-cli create hello superman-cli create hello // 再次 superman-cli create hello -f // 覆蓋4. 小結(jié)
本篇的源碼github地址
并不是說(shuō)我們手寫腳手架就到此結(jié)束了,只是要完整實(shí)現(xiàn)一個(gè)功能不是一兩篇文章可以搞定的。不過(guò)我相信寫到這里,動(dòng)手能力強(qiáng)的一定也能體驗(yàn)一把手動(dòng)模仿vue-cli的爽了。至于能寫出什么牛C的腳手架,真的就是個(gè)人需求和業(yè)務(wù)代碼堆加。當(dāng)然可以發(fā)散思維后續(xù)還可以做許多許多事情。強(qiáng)烈建議閱讀vue-cli。或者其他腳手架的源碼,都在目錄:/usr/local/lib/node_modules 下面,看源碼真的是學(xué)習(xí)最直接的方法了,甚至copy人家的代碼到自己的cli內(nèi)執(zhí)行。其樂(lè)無(wú)窮
作者:超人陳立青
鏈接:https://www.jianshu.com/p/75de24392de8
來(lái)源:簡(jiǎn)書
總結(jié)
以上是生活随笔為你收集整理的vue 使用fs_模仿vue-cli,手写一个脚手架的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 子之错父之过什么意思_胎教是什么意思?胎
- 下一篇: rds本地库到rds恢复_华为云RDS备