MENU

npm run serve后发生了什么

October 16, 2021 • Read: 845 • 杂谈阅读设置

在用vue-cli脚手架创建项目后通常用npm run serve来启动项目,但是这中间到底发生了什么呢?

溯源

通过vue create test创建测试目录,可以观察基本的目录结构为

├── test
|    ├── node_modules      // 当前项目所有依赖,一般不可以移植给其他电脑环境
|    ├── public            
|    |    ├── favicon.ico    // 标签图标
|    |    └── index.html    // 当前项目唯一的页面
|    ├── src
|    |    ├── assets        // 静态资源img、css、js
|    |    ├── components    // 小组件
|    |    ├── views        // 页面组件
|    |    ├── App.vue        // 根组件
|    |    ├── main.js        // 全局脚本文件(项目的入口)
|    |    ├── router
|    |    |    └── index.js// 路由脚本文件(配置路由 url链接 与 页面组件的映射关系)
|    |    └── store    
|    |    |    └── index.js// 仓库脚本文件(vuex插件的配置文件,数据仓库)
|    ├── README.md
└    └── package.json等配置文件

查看根目录下的package.json文件,存在如下代码

  "scripts": {
    "serve": "vue-cli-service serve",
    "build": "vue-cli-service build",
    "lint": "vue-cli-service lint"
  },

这里的serve就是npm run serveserve,即通过serve定位到这里后npm run serve就相当于执行`vue-cli-service serve命令。这里我们也可以将serve改为dev然后执行npm run dev,效果是一样的。

可我执行在命令行下输入vue-cli-service是无效的,那npm run serve是如何启动的呢?原来运行npm run serve命令时npm会把node_modules下的.bin目录添加到Path,执行完后再删掉。

node_modules\.bin\vue-cli-service相当于一个快捷方式,它被添加到了环境变量,但它指向的是node_modules\@vue\cli-service\bin\vue-cli-service.js

综上所述,npm run serve就是在运行vue-cli-service.js这个js文件

函数解析

#!/usr/bin/env node

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

// 检测node版本是否符合vue-cli运行的需求。不符合则打印错误并退出。
if (!semver.satisfies(process.version, requiredVersion, { includePrerelease: true })) {
  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的实例。并将项目路径传入
const service = new Service(process.env.VUE_CLI_CONTEXT || process.cwd())// 传入当前项目的所在路径

// 参数处理
const rawArgv = process.argv.slice(2)
// minimist 命令行参数解析器
const args = require('minimist')(rawArgv, {
  boolean: [
    // build
    'modern',
    'report',
    'report-json',
    'inline-vue',
    '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)
})

vue-cli-service.js即创建了service并对象调用了它的run 方法。Service类定义位于\node_modules\@vue\cli-service\lib\Service.js.

阅读Service.js,代码比较长,只摘录其中run方法以及init。

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
    // package.json containing the plugins
    // 键值对存储的pakcage.json对象
    this.pkg = this.resolvePkg(pkg)
    // If there are inline plugins, they will be used instead of those
    // found in package.json.
    // When useBuiltIn === false, built-in plugins are disabled. This is mostly
    // for testing.
    this.plugins = this.resolvePlugins(plugins, useBuiltIn)
    // pluginsToSkip will be populated during run()
    this.pluginsToSkip = new Set()
    // resolve the default mode to use for each command
    // this is provided by plugins as module.exports.defaultModes
    // so we can get the information without actually applying the plugin.
    //收集插件中的默认配置信息
    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

    // load mode .env
    if (mode) {
      this.loadEnv(mode)
    }
    // load base .env
    this.loadEnv()

    // load user config
    // 读取用户的配置信息.一般为vue.config.js
    const userOptions = this.loadUserOptions()
    this.projectOptions = defaultsDeep(userOptions, defaults())

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

    // apply plugins.
    this.plugins.forEach(({ id, apply }) => {
      if (this.pluginsToSkip.has(id)) return
      apply(new PluginAPI(id, this), this.projectOptions)
    })

    // apply webpack configs from project config file
    if (this.projectOptions.chainWebpack) {
      this.webpackChainFns.push(this.projectOptions.chainWebpack)
    }
    if (this.projectOptions.configureWebpack) {
      this.webpackRawConfigFns.push(this.projectOptions.configureWebpack)
    }
  }
  async run (name, args = {}, rawArgv = []) {
    // resolve mode
    // prioritize inline --mode
    // fallback to resolved default modes from plugins or development if --watch is defined
    const mode = args.mode || (name === 'build' && args.watch ? 'development' : this.modes[name])

    // --skip-plugins arg may have plugins that should be skipped during init()
    this.setPluginsToSkip(args)

    // load env variables, load user config, apply plugins
    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 || args.h) {
      command = this.commands.help
    } else {
      args._.shift() // remove command itself
      rawArgv.shift()
    }
     // 执行命令。例如vue-cli-service serve 则,执行serve命令。
    const { fn } = command
    return fn(args, rawArgv)
  }

  resolveChainableWebpackConfig () {
    const chainableConfig = new Config()
    // apply chains
    this.webpackChainFns.forEach(fn => fn(chainableConfig))
    return chainableConfig
  }

其实质就相当于是一个配置文件。通过vue-cli-service中的new Service,加载插件信息,缓存到Service实例的plugins变量中。
当得到命令行参数后,在通过new Servicerun方法,异步执行命令。run 方法调用init方法项目配置信息。init方法又调用node_modules\@vue\cli-service\lib\PluginAPI.js中的PluginAPI.js类,关联Service和插件,并存放关系到Service.commands中,最后commands[cmdArgName]调用该方法,完成插件调用。

总结

npm run serve即创建存在当前插件的一个环境并运行当前vue项目的

参考链接

https://www.cnblogs.com/jhpy/p/11873270.html

https://blog.csdn.net/weixin_39555179/article/details/111137470

https://segmentfault.com/a/1190000017876208

Last Modified: November 9, 2021