時隔三個月,終於有時間寫腳手架系列第二篇文章了,在北京上班確實比天津忙多了,都沒時間摸魚。若是你沒看過本系列的第一篇文章手把手教你寫一個腳手架,建議先看一遍再來閱讀本文,效果更好。html
mini-cli 項目 github 地址:https://github.com/woai3c/min...vue
v3 版本的代碼在 v3 分支,v4 版本的代碼在 v4 分支。node
第三個版本主要添加了兩個功能:webpack
mvc add xxx
命令的方式來添加插件。首先來簡單瞭解一下 monorepo 和 multirepo。它們都是項目管理的一種方式,multirepo 就是將不一樣的項目放在不一樣的 git 倉庫維護,而 monorepo 將多個項目放在同一個 git 倉庫中維護。在 v3 版本里,我要將 mini-cli 改形成 monorepo 的方式,把不一樣的插件當成一個個獨立的項目來維護。git
在將項目改形成 monorepo 後,目錄以下所示:github
├─packages │ ├─@mvc │ │ ├─cli # 核心插件 │ │ ├─cli-plugin-babel # babel 插件 │ │ ├─cli-plugin-linter # linter 插件 │ │ ├─cli-plugin-router # router 插件 │ │ ├─cli-plugin-vue # vue 插件 │ │ ├─cli-plugin-vuex # vuex 插件 │ │ └─cli-plugin-webpack # webpack 插件 └─scripts # commit message 驗證腳本 和項目無關 不需關注 │─lerna.json |─package.json
全局安裝 lernaweb
npm install -g lerna
建立項目vue-router
git init mini-cli
初始化vuex
cd mini-cli lerna init
建立 packagevue-cli
lerna create xxx
因爲 cli
是腳手架核心代碼,在這裏須要調用其餘插件,由於要將其餘插件添加到 @mvc/cli
的依賴項
# 若是是添加到 devDependencies,則須要在後面加上 --dev # 下載第三方依賴也是一樣的命令 lerna add @mvc/cli-plugin-babel --scope=@mvc/cli
改形成 monorepo-repo 後的腳手架功能和第二版沒有區別,只是將插件相關的代碼獨立成一個單獨的 repo,後續能夠將插件單獨發佈到 npm。
npm i
安裝,才能使用。採用 monorepo 則沒有這種煩惱,能夠直接調用在 packages 目錄裏的其餘插件,方便開發調試。lerna publish
時能夠同時發佈已經修改過的插件,不用每一個單獨發佈。將項目改形成 monorepo-repo 的目的就是爲了後續方便作擴展。例如生成的項目原來是不支持 router 的,在中途忽然想加入 router 功能,就能夠執行命令 mvc add router
添加 vue-router
依賴以及相關的模板代碼。
先來看一下 add 命令的代碼:
const path = require('path') const inquirer = require('inquirer') const Generator = require('./Generator') const clearConsole = require('./utils/clearConsole') const PackageManager = require('./PackageManager') const getPackage = require('./utils/getPackage') const readFiles = require('./utils/readFiles') async function add(name) { const targetDir = process.cwd() const pkg = getPackage(targetDir) // 清空控制檯 clearConsole() let answers = {} try { const pluginPrompts = require(`@mvc/cli-plugin-${name}/prompts`) answers = await inquirer.prompt(pluginPrompts) } catch (error) { console.log(error) } const generator = new Generator(pkg, targetDir, await readFiles(targetDir)) const pm = new PackageManager(targetDir, answers.packageManager) require(`@mvc/cli-plugin-${name}/generator`)(generator, answers) await generator.generate() // 下載依賴 await pm.install() } module.exports = add
因爲 v3 版本仍然是在本地開發的,因此沒有將相關插件發佈到 npm 上,由於能夠直接引用插件,而不需執行 npm i
安裝。在 v2 版本執行 create
命令建立項目時,全部的交互提示語都是放在 cli
插件下的,可是 add 命令是單獨添加一個插件,所以還須要在每一個插件下添加一個 prompts.js
文件(若是不須要,能夠不加),裏面是一些和用戶交互的語句。例如用 add 命令添加 router 插件時,會詢問是否選擇 history 模式。
const chalk = require('chalk') module.exports = [ { name: 'historyMode', type: 'confirm', message: `Use history mode for router? ${chalk.yellow(`(Requires proper server setup for index fallback in production)`)}`, description: `By using the HTML5 History API, the URLs don't need the '#' character anymore.`, }, ]
從 add 命令的代碼邏輯能夠看出來,若是新加的插件有 prompts.js 文件就讀取代碼彈出交互語句。不然跳過,直接進行下載。
v4 版本主要將 webpack 的 dev 和 build 功能作成了動態,原來的腳手架生成的項目是有一個 build 目錄,裏面是 webpack 的一些配置代碼。v4 版本的腳手架生成的項目是沒有 build 目錄的。
這一個功能經過新增的 mvc-cli-service
插件來實現,生成的項目中會有如下兩個腳本命令:
scripts: { serve: 'mvc-cli-service serve', build: 'mvc-cli-service build', },
當運行 npm run serve
時,就會執行命令 mvc-cli-service serve
。這一塊的代碼以下:
#!/usr/bin/env node const webpack = require('webpack') const WebpackDevServer = require('webpack-dev-server') const devConfig = require('../lib/dev.config') const buildConfig = require('../lib/pro.config') const args = process.argv.slice(2) if (args[0] === 'serve') { const compiler = webpack(devConfig) const server = new WebpackDevServer(compiler) server.listen(8080, '0.0.0.0', err => { console.log(err) }) } else if (args[0] === 'build') { webpack(buildConfig, (err, stats) => { if (err) console.log(err) if (stats.hasErrors()) { console.log(new Error('Build failed with errors.')) } }) } else { console.log('error command') }
原理以下(npm scripts 使用指南):
npm 腳本的原理很是簡單。每當執行npm run,就會自動新建一個 Shell,在這個 Shell 裏面執行指定的腳本命令。所以,只要是 Shell(通常是 Bash)能夠運行的命令,就能夠寫在 npm 腳本里面。
比較特別的是,npm run新建的這個 Shell,會將當前目錄的node_modules/.bin子目錄加入PATH變量,執行結束後,再將PATH變量恢復原樣。
上述代碼對執行的命令進行了判斷,若是是 serve
,就 new 一個 WebpackDevServer
實例啓動開發環境。若是是 build
,就用 webpack 進行打包。
vue-cli 的 webpack 配置是動態的,使用了 chainwebpack 來動態添加不一樣的配置,我這個 demo 是直接寫死的,主要是沒時間,因此沒有再深刻研究。
下載 mini-cli
腳手架,其實下載的只是核心插件 mvc-cli
。若是這個插件須要引用其餘插件,則須要先進行安裝,再調用。所以對 create
add
命令須要作一些修改。下面看一下 create
命令代碼的改動:
answers.features.forEach(feature => { if (feature !== 'service') { pkg.devDependencies[`mvc-cli-plugin-${feature}`] = '~1.0.0' } else { pkg.devDependencies['mvc-cli-service'] = '~1.0.0' } }) await writeFileTree(targetDir, { 'package.json': JSON.stringify(pkg, null, 2), }) await pm.install() // 根據用戶選擇的選項加載相應的模塊,在 package.json 寫入對應的依賴項 // 而且將對應的 template 模塊渲染 answers.features.forEach(feature => { if (feature !== 'service') { require(`mvc-cli-plugin-${feature}/generator`)(generator, answers) } else { require(`mvc-cli-service/generator`)(generator, answers) } }) await generator.generate() // 下載依賴 await pm.install()
上面的代碼就是新增的邏輯,在用戶選擇完須要的插件後,將這些插件寫入到 pkg
對象,而後生成 package.json
文件,再執行 npm install
安裝依賴。安裝完插件後,再讀取每一個插件的 generator
目錄/文件代碼,從而生成模板或再次添加不一樣的依賴。而後再執行一次安裝。
v3 版本的插件有一個前綴 @mvc
,因爲帶有 @ 前綴的 npm 包會默認做爲私人包,所以遇到了一些坑。花費了挺長的時間,後來懶得弄了,乾脆將全部的插件從新改了前綴名,變成 mvc
開頭的前綴。