vue-cli系列之vue-cli-service整体架构浅析
概述
vue启动一个项目的时候,需要执行npmrunserve,其中这个serve的内容就是vue-cli-serviceserve。可见,项目的启动关键是这个vue-cli-service与它的参数serve。接下来我们一起看看service中主要写了什么东东(主要内容以备注形式写到代码中。)。
关键代码
vue-cli-service.js
constsemver=require('semver') const{error}=require('@vue/cli-shared-utils') constrequiredVersion=require('../package.json').engines.node //检测node版本是否符合vue-cli运行的需求。不符合则打印错误并退出。 if(!semver.satisfies(process.version,requiredVersion)){ error( `YouareusingNode${process.version},butvue-cli-service`+ `requiresNode${requiredVersion}.\nPleaseupgradeyourNodeversion.` ) process.exit(1) } //cli-service的核心类。 constService=require('../lib/Service') //新建一个service的实例。并将项目路径传入。一般我们在项目根路径下运行该cli命令。所以process.cwd()的结果一般是项目根路径 constservice=newService(process.env.VUE_CLI_CONTEXT||process.cwd()) //参数处理。 constrawArgv=process.argv.slice(2) constargs=require('minimist')(rawArgv,{ boolean:[ //build 'modern', 'report', 'report-json', 'watch', //serve 'open', 'copy', 'https', //inspect 'verbose' ] }) constcommand=args._[0] //将参数传入service这个实例并启动后续工作。如果我们运行的是npmrunserve。则command="serve"。 service.run(command,args,rawArgv).catch(err=>{ error(err) process.exit(1) })
Service.js
上面实例化并调用了service的run方法,这里从构造函数到run一路浏览即可。
constfs=require('fs') constpath=require('path') constdebug=require('debug') constchalk=require('chalk') constreadPkg=require('read-pkg') constmerge=require('webpack-merge') constConfig=require('webpack-chain') constPluginAPI=require('./PluginAPI') constloadEnv=require('./util/loadEnv') constdefaultsDeep=require('lodash.defaultsdeep') const{warn,error,isPlugin,loadModule}=require('@vue/cli-shared-utils') const{defaults,validate}=require('./options') module.exports=classService{ constructor(context,{plugins,pkg,inlineOptions,useBuiltIn}={}){ process.VUE_CLI_SERVICE=this this.initialized=false //一般是项目根目录路径。 this.context=context this.inlineOptions=inlineOptions //webpack相关收集。不是本文重点。所以未列出该方法实现 this.webpackChainFns=[] this.webpackRawConfigFns=[] this.devServerConfigFns=[] //存储的命令。 this.commands={} //Foldercontainingthetargetpackage.jsonforplugins this.pkgContext=context //键值对存储的pakcage.json对象,不是本文重点。所以未列出该方法实现 this.pkg=this.resolvePkg(pkg) //**这个方法下方需要重点阅读。** this.plugins=this.resolvePlugins(plugins,useBuiltIn) //结果为{build:production,serve:development,...}。大意是收集插件中的默认配置信息 //标注build命令主要用于生产环境。 this.modes=this.plugins.reduce((modes,{apply:{defaultModes}})=>{ returnObject.assign(modes,defaultModes) },{}) } init(mode=process.env.VUE_CLI_MODE){ if(this.initialized){ return } this.initialized=true this.mode=mode //加载.env文件中的配置 if(mode){ this.loadEnv(mode) } //loadbase.env this.loadEnv() //读取用户的配置信息.一般为vue.config.js constuserOptions=this.loadUserOptions() //读取项目的配置信息并与用户的配置合并(用户的优先级高) this.projectOptions=defaultsDeep(userOptions,defaults()) debug('vue:project-config')(this.projectOptions) //注册插件。 this.plugins.forEach(({id,apply})=>{ apply(newPluginAPI(id,this),this.projectOptions) }) //wepback相关配置收集 if(this.projectOptions.chainWebpack){ this.webpackChainFns.push(this.projectOptions.chainWebpack) } if(this.projectOptions.configureWebpack){ this.webpackRawConfigFns.push(this.projectOptions.configureWebpack) } } resolvePlugins(inlinePlugins,useBuiltIn){ constidToPlugin=id=>({ id:id.replace(/^.\//,'built-in:'), apply:require(id) }) letplugins //主要是这里。map得到的每个插件都是一个{id,apply的形式} //其中require(id)将直接import每个插件的默认导出。 //每个插件的导出api为 //module.exports=(PluginAPIInstance,projectOptions)=>{ //PluginAPIInstance.registerCommand('cmdName(例如npmrunserve中的serve)',args=>{ ////根据命令行收到的参数,执行该插件的业务逻辑 //}) ////业务逻辑需要的其他函数 //} //注意着里是先在构造函数中resolve了插件。然后再run->init->方法中将命令,通过这里的的apply方法, //将插件对应的命令注册到了service实例。 constbuiltInPlugins=[ './commands/serve', './commands/build', './commands/inspect', './commands/help', //configpluginsareordersensitive './config/base', './config/css', './config/dev', './config/prod', './config/app' ].map(idToPlugin) //inlinePlugins与非inline得处理。默认生成的项目直接运行时候,除了上述数组的插件['./commands/serve'...]外,还会有 //['@vue/cli-plugin-babel','@vue/cli-plugin-eslint','@vue/cli-service']。 //处理结果是两者的合并,细节省略。 if(inlinePlugins){ //... }else{ //...默认走这条路线 plugins=builtInPlugins.concat(projectPlugins) } //Localplugins处理package.json中引入插件的形式,具体代码省略。 returnplugins } asyncrun(name,args={},rawArgv=[]){ //mode是dev还是prod? constmode=args.mode||(name==='build'&&args.watch?'development':this.modes[name]) //收集环境变量、插件、用户配置 this.init(mode) args._=args._||[] letcommand=this.commands[name] if(!command&&name){ error(`command"${name}"doesnotexist.`) process.exit(1) } if(!command||args.help){ command=this.commands.help }else{ args._.shift()//removecommanditself rawArgv.shift() } //执行命令。例如vue-cli-serviceserve则,执行serve命令。 const{fn}=command returnfn(args,rawArgv) } //收集vue.config.js中的用户配置。并以对象形式返回。 loadUserOptions(){ //此处代码省略,可以简单理解为 //require(vue.config.js) returnresolved } }
PluginAPI
这里主要是连接了plugin的注册和service实例。抽象过的代码如下
classPluginAPI{ constructor(id,service){ this.id=id this.service=service } //在service的init方法中 //该函数会被调用,调用处如下。 ////applyplugins. //这里的apply就是插件暴露出来的函数。该函数将PluginAPI实例和项目配置信息(例如vue.config.js)作为参数传入 //通过PluginAPIInstance.registerCommand方法,将命令注册到service实例。 //this.plugins.forEach(({id,apply})=>{ //apply(newPluginAPI(id,this),this.projectOptions) //}) registerCommand(name,opts,fn){ if(typeofopts==='function'){ fn=opts opts=null } this.service.commands[name]={fn,opts:opts||{}} } } module.exports=PluginAPI
总结
通过vue-cli-service中的newService,加载插件信息,缓存到Service实例的plugins变量中。
当得到命令行参数后,在通过newService的run方法,执行命令。
该run方法中调用了init方法获取到项目中的配置信息(默认&用户的合并),例如用户的配置在vue.config.js中。
init过程中通过pluginAPI这个类,将service和插件plugins建立关联。关系存放到service.commands中。
最后通过commands[cmdArgName]调用该方法,完成了插件方法的调用。
初次阅读,只是看到了命令模式的实际应用。能想到的好就是,新增加一个插件的时候,只需要增加一个插件的文件,并不需要更改其他文件的逻辑。其他的部分,再慢慢体会吧。。。以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持毛票票。