手把手教你寫一個腳手架(二)

時隔三個月,終於有時間寫腳手架系列第二篇文章了,在北京上班確實比天津忙多了,都沒時間摸魚。若是你沒看過本系列的第一篇文章手把手教你寫一個腳手架,建議先看一遍再來閱讀本文,效果更好。html

mini-cli 項目 github 地址:https://github.com/woai3c/min...vue

v3 版本的代碼在 v3 分支,v4 版本的代碼在 v4 分支。node

第三個版本 v3

第三個版本主要添加了兩個功能:webpack

  1. 將項目拆分爲 monorepo 的組織方式。
  2. 新增 add 命令,能夠經過 mvc add xxx 命令的方式來添加插件。

monorepo

首先來簡單瞭解一下 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

monorepo 改造過程

全局安裝 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。

使用 monorepo 的優勢

  1. 若是採用 multirepo 的方式開發,在本地調試時若是須要調用其餘插件,則須要先執行 npm i 安裝,才能使用。採用 monorepo 則沒有這種煩惱,能夠直接調用在 packages 目錄裏的其餘插件,方便開發調試。
  2. 若是多個插件都進行了修改,執行 lerna publish 時能夠同時發佈已經修改過的插件,不用每一個單獨發佈。

add 命令

將項目改形成 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

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 是直接寫死的,主要是沒時間,因此沒有再深刻研究。

發佈到 npm 後

下載 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 開頭的前綴。

參考資料

相關文章
相關標籤/搜索