时隔三个月,终于有时间写脚手架系列的第二篇了。在北京工作确实比天津忙很多,没时间钓鱼。如果你还没有看过本系列第一篇教你写脚手架的文章,建议你先看完再看这篇文章,效果会更好。mini-cli项目的github地址:https://github.com/woai3c/min...v3版本的代码在v3分支,v4版本的代码在v4分支。第三版v3第三版主要增加了两个功能:将项目拆分成monorepo的组织方式。添加了add命令,可以通过mvcaddxxx命令添加插件。monorepo首先,我们简单了解一下monorepo和multirepo。它们都是项目管理的一种方式。Multirepo是在不同的git仓库维护不同的项目,而monorepo是在同一个git仓库维护多个项目。在v3版本中,我会将mini-cli改造成monorepo,将不同的插件作为独立的项目来维护。将项目转换为monorepo后,目录如下所示:├─packages│├─@mvc││├─cli#coreplugin│├─cli-plugin-babel#babelplugin││├─cli-plugin-linter#linter插件││├─cli-plugin-router#路由器插件│├─cli-plugin-vue#vue插件│├─cli-plugin-vuex#vuex插件││└─cli-plugin-webpack#webpackPlugin└─scripts#commitmessage验证脚本与项目无关,无需关注│─lerna.json|─package.jsonmonorepo改造过程全局安装lernanpminstall-glernacreateprojectgitinitmini-cliinitializationcdmini-clilernainitcreatepackagelernacreatexxx由于cli是脚手架的核心代码,所以这里需要调用其他插件,因为其他插件需要添加到@mvc/cli的依赖中#如果添加到devDependencies中,则需要add--dev#下载第三方依赖也是同一个命令lernaadd@mvc/cli-plugin-babel--scope=@mvc/cli改造后进入monorepo-repo,脚手架功能同第二个版本,但是插件相关的代码独立成一个单独的repo,以后可以单独发布插件到npm。使用monorepo的优点如果使用multirepo开发,本地调试时如果需要调用其他插件,需要先执行npmiinstallation再使用。使用monorepo就没有这种麻烦,可以直接调用packages目录下的其他插件,方便开发调试。如果修改了多个插件,可以在执行lernapublish的时候同时发布修改后的插件,而不是一个一个单独发布。add命令将项目改造为monorepo-repo的目的是为了方便后续扩展。比如生成的工程本来是不支持router的。如果中途突然想添加router功能,可以执行命令mvcaddrouter添加vue-router依赖和相关模板代码。先来看一下添加指令的代码:constpath=require('path')constinquirer=require('inquirer')constGenerator=require('./Generator')constclearConsole=require('./utils/clearConsole')constPackageManager=require('./PackageManager')constgetPackage=require('./utils/getPackage')constreadFiles=require('./utils/readFiles')asyncfunctionadd(name){consttargetDir=process.cwd()constpkg=getPackage(targetDir)//清空控制台clearConsole()letanswers={}try{constpluginPrompts=require(`@mvc/cli-plugin-${name}/prompts`)answers=awaitinquirer.prompt(pluginPrompts)}catch(error){console.log(error)}constgenerator=newGenerator(pkg,targetDir,awaitreadFiles(targetDir))constpm=newPackageManager(targetDir,answers.packageManager)require(`@mvc/cli-plugin-${name}/generator`)(generator,answers)awaitgenerator.generate()//下载依赖awaitpm.install()}module.exports=add取决于v3版本仍然在本地开发,所以相关插件没有发布在npm上,因为不需要执行npmi就可以直接引用插件。安装v2版本执行create命令创建项目时,所有交互提示都放在cli插件中,而add命令是单独添加一个插件,所以需要添加prompts.js文件在每个插件下(如果不需要就不用加),里面包含了一些与用户交互的语句。比如使用add命令添加路由器插件时,会询问是否选择历史模式。constchalk=require('chalk')module.exports=[{name:'historyMode',type:'confirm',message:`为路由器使用历史模式?${chalk.yellow(`(需要在生产环境中为索引回退设置适当的服务器)`)}`,描述:`通过使用HTML5HistoryAPI,URL不再需要“#”字符。`,},】从add命令的代码逻辑可以看出,如果一个新的插件有一个prompts.js文件读取代码弹出一个交互语句。否则,跳过并直接下载。第四个版本,v4v4,主要是让webpack的dev和build功能动态化。原来脚手架生成的工程有一个build目录,里面有webpack的一些配置代码。v4脚手架生成的项目没有build目录。该功能是通过新增的mvc-cli-service插件实现的,生成的工程会有如下两条脚本命令:scripts:{serve:'mvc-cli-serviceserve',build:'mvc-cli-servicebuild',},当运行npmrunserve时,命令mvc-cli-serviceserve将被执行。这块的代码如下:#!/usr/bin/envnodeconstwebpack=require('webpack')constWebpackDevServer=require('webpack-dev-server')constdevConfig=require('../lib/dev.config')constbuildConfig=require('../lib/pro.config')constargs=process.argv.slice(2)if(args[0]==='serve'){constcompiler=webpack(devConfig)constserver=newWebpackDevServer(compiler)server.listen(8080,'0.0.0.0',err=>{console.log(err)})}elseif(args[0]==='build'){webpack(buildConfig,(err,stats)=>{if(err)console.log(err)if(stats.hasErrors()){console.log(newError('Buildfailedwitherrors.'))}})}else{console.log('errorcommand')}原理如下(npmscripts使用指南):npmscripts的原理很简单。每当执行npmrun时,都会自动创建一个新的shell,并在这个shell中执行指定的脚本命令。因此,只要是能被Shell(一般是Bash)运行的命令,都可以写在npm脚本中。比较特别的是,npmrun创建的新shell会将当前目录的node_modules/.bin子目录添加到PATH变量中,执行完成后,PATH变量会恢复到原来的状态。上面的代码判断执行的命令。如果是serve,则创建一个新的WebpackDevServer实例来启动开发环境。如果是构建,使用webpack进行打包。vue-cli的webpack配置是动态的,使用chainwebpack动态添加不同的配置。我的demo直接写死了,主要是没时间,所以没有深入研究。发布到npm后,下载mini-cli脚手架。其实只下载了核心插件mvc-cli。如果该插件需要引用其他插件,需要先安装,再调用。所以需要对createadd命令做一些修改。我们来看看创建命令代码的变化: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'}})awaitwriteFileTree(targetDir,{'package.json':JSON.stringify(pkg,null,2),})awaitpm.install()//根据用户选择的选项加载对应的模块,在package.json中写入对应的依赖//渲染对应的模板模块answers.features.forEach(feature=>{if(feature!=='service'){require(`mvc-cli-plugin-${feature}/generator`)(generator,answers)}else{require(`mvc-cli-service/generator`)(generator,answers)}})awaitgenerator.generate()//下载依赖awaitpm.install()上面的代码是新增的逻辑,用户选择需要的插件后,将这些插件写入pkg对象,然后生成package.json文件,执行npminstall安装d附件。安装插件后,读取各个插件的生成器目录/文件代码生成模板或者重新添加不同的依赖。然后再次执行安装。v3版本的插件有一个前缀@mvc。由于@前缀的npm包默认会作为私有包使用,所以遇到了一些坑。时间长了,后面懒得做了,干脆把所有插件的前缀名都改成了mvc开头的前缀。参考资料lerna多包管理实战vue-clinpm脚本使用指南
