时隔三个月,终于有工夫写脚手架系列第二篇文章了,在北京下班的确比天津忙多了,都没工夫摸鱼。如果你没看过本系列的第一篇文章手把手教你写一个脚手架,倡议先看一遍再来浏览本文,成果更好。

mini-cli 我的项目 github 地址:https://github.com/woai3c/min...

v3 版本的代码在 v3 分支,v4 版本的代码在 v4 分支。

第三个版本 v3

第三个版本次要增加了两个性能:

  1. 将我的项目拆分为 monorepo 的组织形式。
  2. 新增 add 命令,能够通过 mvc add xxx 命令的形式来增加插件。

monorepo

首先来简略理解一下 monorepo 和 multirepo。它们都是项目管理的一种形式,multirepo 就是将不同的我的项目放在不同的 git 仓库保护,而 monorepo 将多个我的项目放在同一个 git 仓库中保护。在 v3 版本里,我要将 mini-cli 革新成 monorepo 的形式,把不同的插件当成一个个独立的我的项目来保护。

在将我的项目革新成 monorepo 后,目录如下所示:

├─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 革新过程

全局装置 lerna

npm install -g lerna

创立我的项目

git init mini-cli

初始化

cd mini-clilerna init

创立 package

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 nodeconst 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 结尾的前缀。

参考资料

  • lerna多包治理实际
  • vue-cli
  • npm scripts 使用指南