Real operating npm run serve / build behind

vue CLI with them really comfortable, convenient and save time, but he is hard to see through the layers of the package, after you perform the npm run serve / build what he did not even know how the whole project is up and running, and today own take the time to go to Chou Chou, to deepen hereby record recording

First, to explore the real operating npm run behind

1, take a look at npm run serve

Choice from the npm run servestart, the whole should be very familiar, after executing this command is executed, package.json the script to a value in the key behind the serve


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

In fact, the real implementation of this command is a npm run vue-cli-service servecommand, then this is a what we meant to be a test, add a test for testing


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

Command run again under execution, see the following print


	D:\YLKJPro\fgzs>npm run test
	
	> sdz@0.1.0 test D:\YLKJPro\fgzs
	> echo hello vue
	
	hello vue

In fact, carried out behind the test echo, then npm run vue-cli-service servebehind the serve is doing it? Let's look at


	D:\YLKJPro\fgzs>npm run test serve
	
	> [email protected] test D:\YLKJPro\fgzs
	> echo hello vue  "serve"
	
	hello vue  "serve"

In fact, as the back of the parameters

2, fake a serve

If you do not believe, let's do a test to see (fake a serve)

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

Npm run test performed following output


D:\YLKJPro\fgzs>npm run test

> [email protected] test D:\YLKJPro\fgzs
> my-npm-test serve

serve

Hey, strange, serve out how to print it, I did not use echo? In fact, I was imitating the original script,


2-1. Create a test folder

Create a directory under the bin directory js create a test, as
Here Insert Picture Description
this test is very simple js is to print out the received parameters, as follows:


#!/usr/bin/env node

const rawArgv = process.argv.slice(2)

console.log(rawArgv[0])


2-2. Create a test script in node_modules / .bin

Here Insert Picture Description
Linux and windows added a shell script (my-npm-test and my-npm-test.cmd)
In fact, the path on the inside of some of the goals js


2-3. Add my-npm-test

we-altitude-test

#!/bin/sh
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")

case `uname` in
    *CYGWIN*) basedir=`cygpath -w "$basedir"`;;
esac

if [ -x "$basedir/node" ]; then
  "$basedir/node"  "$basedir/../mytest/bin/my-npm-test.js" "$@"
  ret=$?
else
  node  "$basedir/../mytest/bin/my-npm-test.js" "$@"
  ret=$?
fi
exit $ret


2-4. Add my-npm-test.cmd

my-npm-test.cmd for side windows


@IF EXIST "%~dp0\node.exe" (
  "%~dp0\node.exe"  "%~dp0\..\mytest\bin\my-npm-test.js" %*
) ELSE (
  @SETLOCAL
  @SET PATHEXT=%PATHEXT:;.JS;=;%
  node  "%~dp0\..\mytest\bin\my-npm-test.js" %*
)

Here finally to npm run some understanding;

In fact, the implementation of npm help run the official explanation also want to cope as
Here Insert Picture Description


2-5. Principle execution

Use npm run script script execution time will create a shell, and then execute the specified script in the shell.

This executable shell will depend on the current project directory (ie node_modules / .bin) added to the environment variable path, when after performing after another to restore it. That script command name will directly depend find node_modules / .bin below the corresponding script, without the need to add the path.


2-6. TELL explore npm run serve

Well, to finally know that the npm run is not so mysterious, Hey So now not as if it comes, npm run serve related things, in fact, this is already finished, I think carefully, npm run serve=== npm run vue-cli-service serve, so node_modules.bin Here there must be two vue-cli-service file, look. . .
Here Insert Picture Description
Sure, and then open look, what he eventually js execution yes. Open the file
Here Insert Picture Description
can be found in the path follows JS
Here Insert Picture Description
the OK, finally we found a true performer, then this document and what did it, so the project started?

Second, the project is compiled Detailed

We open the VUE-cli-service.js ( code is not trekking explain in detail, by means of direct Gangster blog https://segmentfault.com/a/1190000017876208 )

1, on 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]
	
	// 将我们执行npm run serve 的serve参数传入service这个实例并启动后续工作。(如果我们运行的是npm run build。那么接收的参数即为build)。
	service.run(command, args, rawArgv).catch(err => {
	  error(err)
	  process.exit(1)
	})
	

Js above last call ../lib/Servicein the run to build the project, you could look at Service.jsand done

2, on Service.js

 // ...省略import

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
  }
}

There is a PluginAPI be compiled plug-js

3, on PluginAPI
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

These files are all operating together to complete the construction of our projects vue, their own browser, enter the address you can see the effect of the

Published 115 original articles · won praise 19 · views 50000 +

Guess you like

Origin blog.csdn.net/qq_34817440/article/details/104558785