首页 > 其他分享 >启动vue项目时发生了什么

启动vue项目时发生了什么

时间:2024-05-01 20:23:57浏览次数:22  
标签:插件 vue const cli service 项目 启动 mode

简介

最近在做vue项目时,遇到一些vue cli方面的报错,于是便想深入研究一下vue cli。这里先简单写一篇,如果有更细致的探究,再另作打算。

执行npm run dev

前提是你已经安装了node,并且附带了npm。

执行npm run dev时,npm会自动搜索当前目录下的package.json,找到scripts配置项中的dev脚本。

"scripts": {
  "dev": "vue-cli-service serve --mode dev",
  "build": "vue-cli-service build --mode prod",
  "rsync": "rsync -av -e ssh ./dist/template-webapp-client-vue3 root@aliyun:/srv/www",
  "lint": "vue-cli-service lint"
},

可以看到npm run dev实际上在执行vue-cli-service serve --mode dev

vue-cli-service命令被放在node_modules/.bin下,这是使用vue create创建项目时添加的。

辅助资料:

  1. 附录/执行npm run时发生了什么
  2. 附录/node_modules/.bin下的文件是怎么来的

vue-cli-service做了什么

先查看node_modules/.bin/vue-cli-service.cmd这份文件:

@ECHO off
GOTO start

:find_dp0
SET dp0=%~dp0
EXIT /b

:start
SETLOCAL
CALL :find_dp0
IF EXIST "%dp0%\node.exe" (
  SET "_prog=%dp0%\node.exe"
) ELSE (
  SET "_prog=node"
  SET PATHEXT=%PATHEXT:;.JS;=;%
)
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%"  "%dp0%\..\@vue\cli-service\bin\vue-cli-service.js" %*

这个批处理文件的主要目的是确定Node.js的路径,并使用该路径来运行一个特定的Vue CLI服务脚本。而这个被运行的脚本就是node_modules\@vue\cli-service\bin\vue-cli-service.js

好的,我们继续查看node_modules\@vue\cli-service\bin\vue-cli-service.js

#!/usr/bin/env node  
// 指定该脚本使用 Node.js 来执行,并且使用 `/usr/bin/env` 来查找 node 的实际安装路径  
  
const { semver, error } = require('@vue/cli-shared-utils') // `semver` 是一个用于处理语义化版本号的库 ,`error` 可能是一个用于输出错误并可能以某种方式退出的函数  
const requiredVersion = require('../package.json').engines.node // 获取项目所需的 Node.js 版本  
  
// 使用 semver 的 satisfies 函数检查当前 Node.js 的版本号是否满足项目所需版本  
// 如果不满足,则使用 `error` 函数输出错误消息,并提示用户升级 Node.js 版本  
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) // 退出程序,并返回状态码 1,通常表示出现了错误  
}  

// 使用 `VUE_CLI_CONTEXT` 环境变量(如果存在)或当前工作目录作为上下文创建一个Service对象
const Service = require('../lib/Service')
const service = new Service(process.env.VUE_CLI_CONTEXT || process.cwd()) 

// 解析参数
const rawArgv = process.argv.slice(2) // 获取命令行参数(不包括 `node` 和脚本名),存储在 `rawArgv` 中  
const args = require('minimist')(rawArgv, {  // 使用 'minimist' 库解析命令行参数  
  boolean: [ // 定义哪些命令行参数是布尔类型的(即它们的存在与否表示 true 或 false)  
    'modern',  
    'report',  
    'report-json',  
    'inline-vue',  
    'watch',  
    'open',  
    'copy',  
    'https',  
    'verbose'  
  ]  
})

const command = args._[0] // 获取命令行中的第一个参数(通常是命令名),存储在 `command` 中  

// 执行命令
service.run(command, args, rawArgv).catch(err => {
  error(err)
  process.exit(1)
})

这份文件的意思就是创建一个Service对象,并执行run()方法。执行的时候使用命令行传入的参数。

Service类

全局变量和函数

// # 三方库
const path = require('path')
const debug = require('debug')
const { merge } = require('webpack-merge')
const Config = require('webpack-chain')
const dotenv = require('dotenv')
const dotenvExpand = require('dotenv-expand')
const defaultsDeep = require('lodash.defaultsdeep')

// # vue-cli自带库
const { warn, error, isPlugin, resolvePluginId, loadModule, resolvePkg, resolveModule, sortPlugins } = require('@vue/cli-shared-utils')


const PluginAPI = require('./PluginAPI')
const { defaults } = require('./options')
const loadFileConfig = require('./util/loadFileConfig')
const resolveUserConfig = require('./util/resolveUserConfig')

// Seems we can't use `instanceof Promise` here (would fail the tests)
const isPromise = p => p && typeof p.then === 'function'
module.exports = class Service {
  // ...
}

/** @type {import('../types/index').defineConfig} */
module.exports.defineConfig = (config) => config

constructor

constructor (context, { plugins, pkg, inlineOptions, useBuiltIn } = {}) {
  process.VUE_CLI_SERVICE = this
  this.initialized = false
  this.context = context // process.pwd(),也就是我们的项目根目录
  this.inlineOptions = inlineOptions
  this.webpackChainFns = []
  this.webpackRawConfigFns = []
  this.devServerConfigFns = []
  this.commands = {}
  this.pkgContext = context
  this.pkg = this.resolvePkg(pkg)
  this.plugins = this.resolvePlugins(plugins, useBuiltIn)
  this.pluginsToSkip = new Set() // 在执行run()时填充
  // 采集每个command类插件所对应的默认mode
  // command的默认mode可以在其文件中看到(modules.exports.defaultModes)
  this.modes = this.plugins.reduce((modes, { apply: { defaultModes } }) => {
    return Object.assign(modes, defaultModes)
  }, {})
}

resolvePkg

// # 解析并读取packageJson配置
// 使用context指定路径(即项目根目录)下的package.json
// 如果package.json中有pkg.vuePlugins.resolveFrom这个配置,则使用该配置指定的package.json
resolvePkg (inlinePkg, context = this.context) {
  // 如果有inlinePkg,则使用inlinePkg
  // 然而从源码中来看,并没有传入inlinePkg,所以这个分支不会执行
  if (inlinePkg) {
    return inlinePkg
  }
  const pkg = resolvePkg(context) // 读取指定上下文下的package.json
  if (pkg.vuePlugins && pkg.vuePlugins.resolveFrom) {
    this.pkgContext = path.resolve(context, pkg.vuePlugins.resolveFrom)
    return this.resolvePkg(null, this.pkgContext)
  }
  return pkg
}

const pkg = resolvePkg(context)这一行中的resolvePkg()源码如下:

const fs = require('fs')
const path = require('path')
const readPkg = require('read-pkg')

exports.resolvePkg = function (context) {
  if (fs.existsSync(path.join(context, 'package.json'))) {
    return readPkg.sync({ cwd: context })
  }
  return {}
}

read-pkg是用于读取package.json的。这是因为在ES模块中,无法使用import导入json文件,所以需要read-pkg这个包来解决这个问题。

resolvePlugins

解析、加载plugins后返回。

plugin可以来自内置插件、命令行中指定的插件、package.json中指定的插件。

如果使用了命令行中指定的插件,则会忽略package.json中指定的插件。

内置(built-in)插件在@vue/cli-service/lib目录下,包括:

// 命令类插件
@vue/cli-service/lib/commands/serve
@vue/cli-service/lib/commands/build
@vue/cli-service/lib/commands/inspect
@vue/cli-service/lib/commands/help
// 配置类插件,用于向webpack配置文件添加配置项
@vue/cli-service/lib/config/base
@vue/cli-service/lib/config/assets
@vue/cli-service/lib/config/css
@vue/cli-service/lib/config/prod
@vue/cli-service/lib/config/app
// package.json中的插件
@vue/cli-plugin-babel
@vue/cli-plugin-eslint
@vue/cli-plugin-router
@vue/cli-plugin-typescript
@vue/cli-plugin-vuex
resolvePlugins (inlinePlugins, useBuiltIn) {
  const idToPlugin = (id, absolutePath) => ({
    id: id.replace(/^.\//, 'built-in:'), // 如果是'./commands/serve'这样的内置插件,则转为'built-in:commands/serve'
    apply: require(absolutePath || id) // 导入插件,可以是绝对路径的三方插件,也可以是相对路径的内置插件
  })

  let plugins

  const builtInPlugins = [
    './commands/serve',
    './commands/build',
    './commands/inspect',
    './commands/help',
    // config plugins are order sensitive
    './config/base',
    './config/assets',
    './config/css',
    './config/prod',
    './config/app'
  ].map((id) => idToPlugin(id))

  // 如果有inlinePlugins,则使用inlinePlugins或[...ininePlugins, ...builtInPlugins]
  if (inlinePlugins) {
    plugins = useBuiltIn !== false
      ? builtInPlugins.concat(inlinePlugins)
    : inlinePlugins
  }
  // 否则使用package.json中的dependencies和devDependencies中的vue plugin
  // (这里会根据包名自动判断是否是vue plugin)
  else {
    const projectPlugins = Object.keys(this.pkg.devDependencies || {})
    .concat(Object.keys(this.pkg.dependencies || {}))
    .filter(isPlugin) // 包名符合@vue/cli-plugin-xxx或vue-cli-plugin-xxx
    .map(id => {
      if (
        this.pkg.optionalDependencies &&
        id in this.pkg.optionalDependencies
      ) {
        let apply = loadModule(id, this.pkgContext)
        if (!apply) {
          warn(`Optional dependency ${id} is not installed.`)
          apply = () => {}
        }

        return { id, apply }
      } else {
        return idToPlugin(id, resolveModule(id, this.pkgContext))
      }
    })

    plugins = builtInPlugins.concat(projectPlugins)
  }

  // 加载本地插件
  // 本地插件应该指的是放在项目根目录下由用户自定义的插件
  if (this.pkg.vuePlugins && this.pkg.vuePlugins.service) {
    const files = this.pkg.vuePlugins.service
    if (!Array.isArray(files)) {
      throw new Error(`Invalid type for option 'vuePlugins.service', expected 'array' but got ${typeof files}.`)
    }
    plugins = plugins.concat(files.map(file => ({
      id: `local:${file}`,
      apply: loadModule(`./${file}`, this.pkgContext)
    })))
  }
  debug('vue:plugins')(plugins)

  // 对插件进行排序
  // 有一些插件需要在指定的其他插件运行之后/前运行
  const orderedPlugins = sortPlugins(plugins)
  debug('vue:plugins-ordered')(orderedPlugins)

  return orderedPlugins
}

run

async run (name, args = {}, rawArgv = []) {
  // 计算mode,优先使用--mode参数
  // 次之,在build命令且有--watch时使用development
  // 最次,使用命令的默认mode
  const mode = args.mode || (name === 'build' && args.watch ? 'development' : this.modes[name])

  // 跳过--skip-plugins指定的插件
  this.setPluginsToSkip(args, rawArgv)

  // 加载环境变量、加载用户配置、应用插件
  await this.init(mode)

  // 执行name对应的命令
  // 这些命令是在插件中注册到this.commands中的
  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()
  }
  const { fn } = command
  return fn(args, rawArgv)
}

init

init做以下几件事:

  1. 加载mode .env和base .env中的内容到process.env中
  2. 加载vue.config.js,并且生成本项目的配置清单
  3. 应用插件
  4. 挂载webpack配置
init (mode = process.env.VUE_CLI_MODE) {
  if (this.initialized) {
    return
  }
  this.initialized = true
  this.mode = mode

  // 加载mode .env和base .env
  if (mode) {
    this.loadEnv(mode)
  }
  this.loadEnv()

  // 加载vue.config.js(可能是异步的)
  const userOptions = this.loadUserOptions()

  // 加载完毕后的回调
  const loadedCallback = (loadedUserOptions) => {
    // 将vue.config.js和默认选项结合,形成本项目的配置
    this.projectOptions = defaultsDeep(loadedUserOptions, defaults())

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

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

    // 应用来自vue.config.js中的webpack配置
    if (this.projectOptions.chainWebpack) {
      this.webpackChainFns.push(this.projectOptions.chainWebpack)
    }
    if (this.projectOptions.configureWebpack) {
      this.webpackRawConfigFns.push(this.projectOptions.configureWebpack)
    }
  }

  if (isPromise(userOptions)) {
    return userOptions.then(loadedCallback)
  } else {
    return loadedCallback(userOptions)
  }
}

loadEnv

从项目根目录下加载.env或.env.${mode}和.env.${mode}.local中的环境变量到process.env中。

loadEnv (mode) {
  const logger = debug('vue:env')
  const basePath = path.resolve(this.context, `.env${mode ? `.${mode}` : ``}`)
  const localPath = `${basePath}.local`

  const load = envPath => {
    try {
      const env = dotenv.config({ path: envPath, debug: process.env.DEBUG })
      dotenvExpand(env)
      logger(envPath, env)
    } catch (err) {
      // only ignore error if file is not found
      if (err.toString().indexOf('ENOENT') < 0) {
        error(err)
      }
    }
  }

  load(localPath)
  load(basePath)

  // 设置NODE_ENV和BABEL_ENV
  if (mode) {
    // 在test模式时强制设置NODE_ENV和BABEL_ENV为defaultEnv
    const shouldForceDefaultEnv = (
      process.env.VUE_CLI_TEST &&
      !process.env.VUE_CLI_TEST_TESTING_ENV
    )
    const defaultNodeEnv = (mode === 'production' || mode === 'test')
    ? mode
    : 'development'
    if (shouldForceDefaultEnv || process.env.NODE_ENV == null) {
      process.env.NODE_ENV = defaultNodeEnv
    }
    if (shouldForceDefaultEnv || process.env.BABEL_ENV == null) {
      process.env.BABEL_ENV = defaultNodeEnv
    }
  }
}

loadUserOptions

加载vue.config.js。

loadUserOptions () {
  const { fileConfig, fileConfigPath } = loadFileConfig(this.context)

  if (isPromise(fileConfig)) {
    return fileConfig
      .then(mod => mod.default)
      .then(loadedConfig => resolveUserConfig({
      inlineOptions: this.inlineOptions,
      pkgConfig: this.pkg.vue,
      fileConfig: loadedConfig,
      fileConfigPath
    }))
  }

  return resolveUserConfig({
    inlineOptions: this.inlineOptions,
    pkgConfig: this.pkg.vue,
    fileConfig,
    fileConfigPath
  })
}

* PluginAPI

这是对Vue CLI插件暴露的API对象类。将PluginAPI对象传给插件,通过这个对象,插件可以向service对象进行以下关键操作:

  • 获取cwd
  • 注册command
  • 添加webpack配置
  • 添加dev server配置

总结

这个类做了以下事情:

  • 读取package.json文件,主要需要知道其中关于Vue CLI插件的信息
  • 读取并安装VueCLI内置插件、package.json中的三方Vue CLI插件
  • 加载.env文件中的环境变量到process.env中
  • 加载vue.config.js,并将其中的配置合并到webpack配置中

然后Vue CLI插件赋予Service额外的特性,比如:

  • 添加command
  • 添加webpack配置
  • 添加dev server配置

serve.js

从Service类的resolvePlugins()函数中我们可以知道,vue-cli-service serve实际上在执行@vue/cli-service/lib/commands/serve.js这个脚本。那我们继续来看这个文件吧。

这份文件是一个命令型插件,service会加载并安装它:

module.exports = (api, options) => {
  // ... 
  api.registerCommand('serve', {
    description: 'start development server',
    usage: 'vue-cli-service serve [options] [entry]',
    options: {
      '--open': `open browser on server start`,
      '--copy': `copy url to clipboard on server start`,
      '--stdin': `close when stdin ends`,
      '--mode': `specify env mode (default: development)`,
      '--host': `specify host (default: ${defaults.host})`,
      '--port': `specify port (default: ${defaults.port})`,
      '--https': `use https (default: ${defaults.https})`,
      '--public': `specify the public network URL for the HMR client`,
      '--skip-plugins': `comma-separated list of plugin names to skip for this run`
    }
  }, async function serve (args) {
    // ... 
  })
}

插件的主体部分比较琐碎,但主要就是围绕着两件事情:创建webpack对象用来编译,然后使用webpackDevServer运行编译完的结果。

// ...
const webpack = require('webpack')
const WebpackDevServer = require('webpack-dev-server')
// ...
const compiler = webpack(webpackConfig)
// ...
const server = new WebpackDevServer(
	// 一些服务器配置
  compiler
)
// ...
server.start().catch(err => reject(err))

总结

综上,vue cli项目启动的过程简单地说如下:

  1. 执行npm run dev
  2. 执行dev对应的vue-cli-service serve --mode dev
  3. vue-cli-service中创建Service类,读取从命令行、package.json、.env、vue.config.js这些配置文件中拿到的配置,应用插件获得特性
  4. 执行serve.js对应的命令,其主要内容就是创建webpack编译器和启动webpackDevServer。

标签:插件,vue,const,cli,service,项目,启动,mode
From: https://www.cnblogs.com/hdxg/p/18169601

相关文章

  • WDS+MDT网络启动自动部署windows(十二)查错的方法
    简介各种错误不断,那么怎么检查呢?MDT日志MDT终端是待安装的,而且也不知道安装临时文件是存在内存的虚拟磁盘还是真实磁盘。我不深究。那么就需要将MDT的日志回写到服务器上,才方便服务器检查错误。共享在任意服务器创建logs$共享,允许mdt写入,记得共享权限和NTFS权限。mdt,是我们......
  • Vue mockjs mock.js
    https://www.jianshu.com/p/0d6a0bdce55c?utm_campaign=maleskine&utm_content=note&utm_medium=seo_notes&utm_source=recommendationhttps://blog.csdn.net/cuclife/article/details/131119983        操作步骤           1)安装mockjs和axios:  ......
  • Vue .browserslistrc
    Vue .browserslistrc 在使用脚手架搭建项目时,会自动生成.browserslistrc文件,该文件只要是配置兼容浏览器对于部分配置参数做一些解释:">1%":代表着全球超过1%人使用的浏览器“last2versions”:表示所有浏览器兼容到最后两个版本“notie<=8”:表示IE浏览器版本大于8......
  • Vue .eslintignore
    Vue .eslintignore项目根目录如果没有 .eslintignore文件,需要手动添加即可 用法如下指定某文件夹包括里面的所有文件都忽略buildsrc/assets指定某文件夹里面的指定文件类型都忽略build/*.js指定某文件夹里面的指定文件忽略src/index.js指定某文件夹里的除......
  • Vue 生命周期
    https://www.bilibili.com/video/BV1ub4y1i78b?p=2 第五章什么是vue3的生命周期https://www.bilibili.com/video/BV1ua4y1u7N8/  Vue生命周期created,mounted        Created和mounted的区别Created:是在组件实例一旦创建完成的时候立即调......
  • Vue main.js
    Vue main.jsmain.js是项目的入口文件,项目中所有的页面都会加载main.jsmain.js配置定义:main.js是项目的入口文件,项目中所有的页面都会加载main.js。主要有三个作用: 1.实例化Vue。 2.放置项目中经常会用到的插件和CSS样式。3.存储全局变量。项目创建完毕,main.js中会有......
  • Vue App.vue
    Vue App.vueVue的App.vue文件是整个Vue项目中最核心的文件之一,它是所有组件的基础组件,也是整个Vue应用的入口文件。我们可以在 App.vue中定义一些全局的样式、组件、路由和状态管理等,从而方便其他组件的调用。App.vue内部包含三个主要部分:模板、逻辑代码和样式。其中......
  • 前端Vue 启动过程 启动流程 执行流程
    前端Vue执行流程Vue的执行流程一般来说,当启动vue程序时,系统会先调用main.js文件 在main.js中,创建了一个新的vue对象并将其挂载到App.vue中id为app的html组件中 在App.js中,引入<router-view/>标签来进行路由管理,系统会进入router文件夹中的index.js文件中来寻找路由i......
  • Vue项目中每个文件夹和文件的用处
     myfirstvue#项目名-node_modules#文件夹,放了该项目所有的依赖,很小很多,以后把项目传给别人,这个要删除,别人拿到执行cnpminstall安装依赖-public#文件夹-favicon.ico#小图标,浏览器上面显示,可以替换-index.htm......
  • Vue项目中main.js、App.vue、import...from...等的作用和意义
      https://www.cnblogs.com/webwangjie/p/11471542.html 一、main.js  1、 main.js程序入口文件,初始化vue实例,并引入使用需要的插件和各种公共组件.importVuefrom'vue'importAppfrom'./App'importrouterfrom'./router'importLessfrom'Less'......