vue-cli系列之vue-cli-service整体架构浅析


Posted in Javascript onJanuary 14, 2019

概述

vue启动一个项目的时候,需要执行npm run serve,其中这个serve的内容就是vue-cli-service serve。可见,项目的启动关键是这个vue-cli-service与它的参数serve。接下来我们一起看看service中主要写了什么东东(主要内容以备注形式写到代码中。)。

关键代码

vue-cli-service.js

const semver = require('semver')
const { error } = require('@vue/cli-shared-utils')
const requiredVersion = require('../package.json').engines.node

// 检测node版本是否符合vue-cli运行的需求。不符合则打印错误并退出。
if (!semver.satisfies(process.version, requiredVersion)) {
 error(
  `You are using Node ${process.version}, but vue-cli-service ` +
  `requires Node ${requiredVersion}.\nPlease upgrade your Node version.`
 )
 process.exit(1)
}

// cli-service的核心类。
const Service = require('../lib/Service')
// 新建一个service的实例。并将项目路径传入。一般我们在项目根路径下运行该cli命令。所以process.cwd()的结果一般是项目根路径
const service = new Service(process.env.VUE_CLI_CONTEXT || process.cwd())

// 参数处理。
const rawArgv = process.argv.slice(2)
const args = require('minimist')(rawArgv, {
 boolean: [
  // build
  'modern',
  'report',
  'report-json',
  'watch',
  // serve
  'open',
  'copy',
  'https',
  // inspect
  'verbose'
 ]
})
const command = args._[0]

// 将参数传入service这个实例并启动后续工作。如果我们运行的是npm run serve。则command = "serve"。
service.run(command, args, rawArgv).catch(err => {
 error(err)
 process.exit(1)
})

Service.js

上面实例化并调用了service的run方法,这里从构造函数到run一路浏览即可。

const fs = require('fs')
const path = require('path')
const debug = require('debug')
const chalk = require('chalk')
const readPkg = require('read-pkg')
const merge = require('webpack-merge')
const Config = require('webpack-chain')
const PluginAPI = require('./PluginAPI')
const loadEnv = require('./util/loadEnv')
const defaultsDeep = require('lodash.defaultsdeep')
const { warn, error, isPlugin, loadModule } = require('@vue/cli-shared-utils')

const { defaults, validate } = require('./options')

module.exports = class Service {
 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 = {}
  // Folder containing the target package.json for plugins
  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 }}) => {
   return Object.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)
  }
  // load base .env
  this.loadEnv()

  // 读取用户的配置信息.一般为vue.config.js
  const userOptions = this.loadUserOptions()
  // 读取项目的配置信息并与用户的配置合并(用户的优先级高)
  this.projectOptions = defaultsDeep(userOptions, defaults())

  debug('vue:project-config')(this.projectOptions)

  // 注册插件。
  this.plugins.forEach(({ id, apply }) => {
   apply(new PluginAPI(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) {
  const idToPlugin = id => ({
   id: id.replace(/^.\//, 'built-in:'),
   apply: require(id)
  })

  let plugins
  
  
  // 主要是这里。map得到的每个插件都是一个{id, apply的形式}
  // 其中require(id)将直接import每个插件的默认导出。
  // 每个插件的导出api为
  // module.exports = (PluginAPIInstance,projectOptions) => {
  //  PluginAPIInstance.registerCommand('cmdName(例如npm run serve中的serve)', args => {
  //    // 根据命令行收到的参数,执行该插件的业务逻辑
  //  })
  //  // 业务逻辑需要的其他函数
  //}
  // 注意着里是先在构造函数中resolve了插件。然后再run->init->方法中将命令,通过这里的的apply方法,
  // 将插件对应的命令注册到了service实例。
  const builtInPlugins = [
   './commands/serve',
   './commands/build',
   './commands/inspect',
   './commands/help',
   // config plugins are order sensitive
   './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)
  }

  // Local plugins 处理package.json中引入插件的形式,具体代码省略。

  return plugins
 }

 async run (name, args = {}, rawArgv = []) {
  // mode是dev还是prod?
  const mode = args.mode || (name === 'build' && args.watch ? 'development' : this.modes[name])

  // 收集环境变量、插件、用户配置
  this.init(mode)

  args._ = args._ || []
  let command = this.commands[name]
  if (!command && name) {
   error(`command "${name}" does not exist.`)
   process.exit(1)
  }
  if (!command || args.help) {
   command = this.commands.help
  } else {
   args._.shift() // remove command itself
   rawArgv.shift()
  }
  // 执行命令。例如vue-cli-service serve 则,执行serve命令。
  const { fn } = command
  return fn(args, rawArgv)
 }

 // 收集vue.config.js中的用户配置。并以对象形式返回。
 loadUserOptions () {
  // 此处代码省略,可以简单理解为
  // require(vue.config.js)
  return resolved
 }
}

PluginAPI

这里主要是连接了plugin的注册和service实例。抽象过的代码如下

class PluginAPI {

 constructor (id, service) {
  this.id = id
  this.service = service
 }
 // 在service的init方法中
 // 该函数会被调用,调用处如下。
 // // apply plugins.
 // 这里的apply就是插件暴露出来的函数。该函数将PluginAPI实例和项目配置信息(例如vue.config.js)作为参数传入
 // 通过PluginAPIInstance.registerCommand方法,将命令注册到service实例。
 // this.plugins.forEach(({ id, apply }) => {
 //  apply(new PluginAPI(id, this), this.projectOptions)
 // })
 registerCommand (name, opts, fn) {
  if (typeof opts === 'function') {
   fn = opts
   opts = null
  }
  this.service.commands[name] = { fn, opts: opts || {}}
 }


}

module.exports = PluginAPI

总结

通过vue-cli-service中的new Service,加载插件信息,缓存到Service实例的plugins变量中。

当得到命令行参数后,在通过new Service的run方法,执行命令。

该run方法中调用了init方法获取到项目中的配置信息(默认&用户的合并),例如用户的配置在vue.config.js中。

init过程中通过pluginAPI这个类,将service和插件plugins建立关联。关系存放到service.commands中。
最后通过commands[cmdArgName]调用该方法,完成了插件方法的调用。

初次阅读,只是看到了命令模式的实际应用。能想到的好就是,新增加一个插件的时候,只需要增加一个插件的文件,并不需要更改其他文件的逻辑。其他的部分,再慢慢体会吧。。。以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
提高网站信任度的技巧
Oct 17 Javascript
jQuery 处理表单元素的代码
Feb 15 Javascript
jquery获取复选框被选中的值
Apr 10 Javascript
jQuery中toggleClass()方法用法实例
Jan 05 Javascript
Jquery全选与反选点击执行一次的解决方案
Aug 14 Javascript
根据user-agent判断蜘蛛代码黑帽跳转代码(js版与php版本)
Sep 14 Javascript
JavaScript中两个字符串的匹配
Jun 08 Javascript
JS实现鼠标滑过显示边框的菜单效果
Sep 21 Javascript
js实现日期显示的一些操作(实例讲解)
Jul 27 Javascript
vue webuploader 文件上传组件开发
Sep 23 Javascript
two.js之实现动画效果示例
Nov 06 Javascript
微信小程序 select 下拉框组件功能
Sep 09 Javascript
JS数组求和的常用方法总结【5种方法】
Jan 14 #Javascript
JS实现根据数组对象的某一属性排序操作示例
Jan 14 #Javascript
vue项目中使用vue-i18n报错的解决方法
Jan 13 #Javascript
vscode下vue项目中eslint的使用方法
Jan 13 #Javascript
jQuery实现的中英文切换功能示例
Jan 11 #jQuery
JavaScript寄生组合式继承原理与用法分析
Jan 11 #Javascript
JavaScript常见继承模式实例小结
Jan 11 #Javascript
You might like
PHP Memcached + APC + 文件缓存封装实现代码
2010/03/11 PHP
php 强制下载文件实现代码
2013/10/28 PHP
php简单防盗链实现方法
2015/07/29 PHP
php提交过来的数据生成为txt文件
2016/04/28 PHP
phpmyadmin下载、安装、配置教程
2017/05/16 PHP
Node.js中的模块机制学习笔记
2014/11/04 Javascript
node.js中的buffer.write方法使用说明
2014/12/10 Javascript
Bootstrap基础学习
2015/06/16 Javascript
学习JavaScript设计模式(接口)
2015/11/26 Javascript
深入解析Backbone.js框架的依赖库Underscore.js的作用
2016/05/07 Javascript
详解Angular开发中的登陆与身份验证
2016/07/27 Javascript
jQuery控制控件文本的长度的操作方法
2016/12/05 Javascript
JavaScript 获取元素在父节点中的下标(推荐)
2017/06/28 Javascript
基于js的变量提升和函数提升(详解)
2017/09/17 Javascript
原生javascript实现的全屏滚动功能示例
2017/09/19 Javascript
JavaScript定义及输出螺旋矩阵的方法详解
2017/12/01 Javascript
JS实现字符串去重及数组去重的方法示例
2018/04/21 Javascript
微信小程序后台持续定位功能使用详解
2019/08/23 Javascript
Js on及addEventListener原理用法区别解析
2020/07/11 Javascript
跟老齐学Python之编写类之一创建实例
2014/10/11 Python
请不要重复犯我在学习Python和Linux系统上的错误
2016/12/12 Python
python实现跨excel的工作表sheet之间的复制方法
2018/05/03 Python
使用Python快速制作可视化报表的方法
2019/02/03 Python
python orm 框架中sqlalchemy用法实例详解
2020/02/02 Python
python Shapely使用指南详解
2020/02/18 Python
记一次pyinstaller打包pygame项目为exe的过程(带图片)
2020/03/02 Python
Python 输出详细的异常信息(traceback)方式
2020/04/08 Python
为什么相对PHP黑python的更少
2020/06/21 Python
python 利用jieba.analyse进行 关键词提取
2020/12/17 Python
20佳惊艳的HTML5应用程序示例分享
2011/05/03 HTML / CSS
中国包裹转运寄送国际服务:Famiboat
2019/07/24 全球购物
终止劳动合同协议书
2014/10/05 职场文书
全国爱牙日活动总结
2015/02/05 职场文书
工程技术负责人岗位职责
2015/04/13 职场文书
Go语言 详解net的tcp服务
2022/04/14 Golang
JS高级程序设计之class继承重点详解
2022/07/07 Javascript