1、软件系统开发定制我问了自己一个问题?
- 软件系统开发定制为什么命令行输入
vue-cli-service serve
我们的vue软件系统开发定制项目就能通过http协议访问?
2、软件系统开发定制带着这个问题,软件系统开发定制去看看源码,寻找答案
2.1、vue-cli-service源码
-
代码片段
const Service = require('../lib/Service')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', 'inline-vue', 'watch', // serve 'open', 'copy', 'https', // inspect 'verbose' ]})const command = args._[0]service.run(command, args, rawArgv).catch(err => { error(err) process.exit(1)})
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
-
解读
-
1)、引入Service类,并实例一个service 对象
-
2)、解析命令行输入参数
- 当我们 执行 vue-cli-service serve 命令,打印一下参数看看
-
3)、调用service 对象的run方法,传入解析的命令参数
-
-
到这里肯定不行,我们继续去看 Service类的run 方法的源码
2.2、Service.run源码
-
代码片段
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() } const { fn } = command return fn(args, rawArgv) }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
-
解读
- 1)、解析模式
- 2)、设置需要在执行
init
期间,需要跳过处理的插件 - 3)、加载环境变量、加载用户配置、应用插件
- 4)、根据name选择需要执行command(我也没想好用啥名字称呼,就叫他command)
- 5)、处理异常
- 6)、执行command
-
看到这里,最后又执行了command,command干了啥呢?
2.3、寻找command
-
代码片段0(./node_modules/@vue/cli-service/lib/Service.js)
this.commands = {} // 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)
- 1
- 2
- 3
- 4
- 5
- 6
- 7
-
解读
- 1)、在service类内声明了commands存储command
- 2)、在service类内声明了plugins存储plugins插件
-
代码片段1 (./node_modules/@vue/cli-service/lib/Service.js)
resolvePlugins (inlinePlugins, useBuiltIn) { const idToPlugin = id => ({ id: id.replace(/^.\//, 'built-in:'), apply: require(id) }) let plugins const builtInPlugins = [ './commands/serve', './commands/build', './commands/inspect', './commands/help', // config plugins are order sensitive './config/base', './config/css', './config/prod', './config/app' ].map(idToPlugin) if (inlinePlugins) { plugins = useBuiltIn !== false ? builtInPlugins.concat(inlinePlugins) : inlinePlugins } else { const projectPlugins = Object.keys(this.pkg.devDependencies || {}) .concat(Object.keys(this.pkg.dependencies || {})) .filter(isPlugin) .map(id => { if ( this.pkg.optionalDependencies && id in this.pkg.optionalDependencies ) { let apply = () => {} try { apply = require(id) } catch (e) { warn(`Optional dependency ${id} is not installed.`) } return { id, apply } } else { return idToPlugin(id) } }) plugins = builtInPlugins.concat(projectPlugins) } // Local plugins 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) }))) } return plugins }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
-
解读
- 1)、解析内置插件
- 2)、如果有行内插件,只需解析行内和内置插件或行内插件(内置插件包含:内置命令和其他插件)
- 3)、如果没有行内插件,解析
package.json
中和内置的插件 - 4)、解析本地的插件
- 5)、上一步中,将解析的插件存储在plugins上
-
代码片段2
init (mode = process.env.VUE_CLI_MODE) { // apply plugins. this.plugins.forEach(({ id, apply }) => { if (this.pluginsToSkip.has(id)) return apply(new PluginAPI(id, this), this.projectOptions) }) }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
-
解读 (init中部分代码)
- 1)、使用插件
- 2)、我们去看下插件内的源码(serve为例)
-
代码片段3(./node_modules/@vue/cli-service/lib/commands/serve.js)
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) { ...... })}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
-
解读
- 1)、直接调用了第一个参数的registerCommand方法
- 2)、去看一下registerCommand方法
-
代码片段4 (./node_modules/@vue/cli-service/lib/PluginAPI.js)
class PluginAPI { /** * @param {string} id - Id of the plugin. * @param {Service} service - A vue-cli-service instance. */ constructor (id, service) { this.id = id this.service = service } registerCommand (name, opts, fn) { if (typeof opts === 'function') { fn = opts opts = null } this.service.commands[name] = { fn, opts: opts || {}} }}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
-
解读
- 1)、从上面部分源码结合片段3可以看出,
registerCommand
方法将serve 函数 存入的commands中了
- 1)、从上面部分源码结合片段3可以看出,
-
至此,当我们运行serve命令时,找到了
Service.run
函数中执行的command
了,就是serve.js
中的serve
函数 -
接下来,看看
serve.js
的源码
2.4、serve.js 源码
- 代码片段
const defaults = { host: '0.0.0.0', port: 8080, https: false}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) { const WebpackDevServer = require('webpack-dev-server') // create server const server = new WebpackDevServer(compiler, Object.assign({ logLevel: 'silent', clientLogLevel: 'silent', historyApiFallback: { disableDotRule: true, rewrites: genHistoryApiFallbackRewrites(options.publicPath, options.pages) }, contentBase: api.resolve('public'), watchContentBase: !isProduction, hot: !isProduction, injectClient: false, compress: isProduction, publicPath: options.publicPath, overlay: isProduction // TODO disable this ? false : { warnings: false, errors: true } }, projectDevServerOptions, { https: useHttps, proxy: proxySettings, // eslint-disable-next-line no-shadow before (app, server) { // launch editor support. // this works with vue-devtools & @vue/cli-overlay app.use('/__open-in-editor', launchEditorMiddleware(() => console.log( `To specify an editor, specify the EDITOR env variable or ` + `add "editor" field to your Vue project config.` ))) // allow other plugins to register middlewares, e.g. PWA api.service.devServerConfigFns.forEach(fn => fn(app, server)) // apply in project middlewares projectDevServerOptions.before && projectDevServerOptions.before(app, server) }, // avoid opening browser open: false })) })}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 解读
- 1)、从代码中可以看出,vue-cli 用了 webpack-dev-server 启动开发服务。
3、回答开始提到的问题
- 1)、执行 vue-cli-service serve 命令
- 2)、解析参数行内插件、本地插件、package.json中的插件、内置插件
- 3)、执行run 方法
- 4)、init方法内配置插件,webpack配置函数存入webpackChainFns,命令函数存入commands
- 5)、调用 serve 命令 对应的 command 函数
- 6)、使用webpack-dev-serve 搭建开发服务器
4、vue-cli-service serve 从运行命令到本地访问流程图解
5、vue-cli-service serve 除了启动一个本地服务器,还做了什么?
-
代码片段
// configs that only matters for dev server api.chainWebpack(webpackConfig => { if (process.env.NODE_ENV !== 'production' && process.env.NODE_ENV !== 'test') { webpackConfig .devtool('eval-cheap-module-source-map') webpackConfig .plugin('hmr') .use(require('webpack/lib/HotModuleReplacementPlugin')) // https://github.com/webpack/webpack/issues/6642 // https://github.com/vuejs/vue-cli/issues/3539 webpackConfig .output .globalObject(`(typeof self !== 'undefined' ? self : this)`) if (!process.env.VUE_CLI_TEST && options.devServer.progress !== false) { webpackConfig .plugin('progress') .use(require('webpack/lib/ProgressPlugin')) } } })// expose advanced stats if (args.dashboard) { const DashboardPlugin = require('../webpack/DashboardPlugin') ;(webpackConfig.plugins = webpackConfig.plugins || []).push(new DashboardPlugin({ type: 'serve' })) }// entry arg const entry = args._[0] if (entry) { webpackConfig.entry = { app: api.resolve(entry) } }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
-
解读(serve默认)
-
1)、配置sorcemap,源码映射
-
2)、配置hmr,热更新
-
3)、设置globalObject,全局变量
-
4)、设置progress,进度条
-
5)、如果参数有,则设置dashboard,构建过程可视化
-
6)、设置entry,入口文件
-
7)、设置webpackDevServer
-
8)、还会设置用户的
- ./node_modules/@vue/cli-service/lib/commands/serve.js
// resolve webpack config const webpackConfig = api.resolveWebpackConfig()
- 1
- 2
- ./node_modules/@vue/cli-service/lib/PluginAPI.js
/** * Resolve the final raw webpack config, that will be passed to webpack. * * @param {ChainableWebpackConfig} [chainableConfig] * @return {object} Raw webpack config. */ resolveWebpackConfig (chainableConfig) { return this.service.resolveWebpackConfig(chainableConfig) }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- ./node_modules/@vue/cli-service/lib/Service.js
// apply webpack configs from project config file if (this.projectOptions.chainWebpack) { this.webpackChainFns.push(this.projectOptions.chainWebpack) } resolveChainableWebpackConfig () { const chainableConfig = new Config() // apply chains this.webpackChainFns.forEach(fn => fn(chainableConfig)) return chainableConfig } resolveWebpackConfig (chainableConfig = this.resolveChainableWebpackConfig()) { if (!this.initialized) { throw new Error('Service must call init() before calling resolveWebpackConfig().') } // get raw config let config = chainableConfig.toConfig() const original = config // apply raw config fns this.webpackRawConfigFns.forEach(fn => { if (typeof fn === 'function') { // function with optional return value const res = fn(config) if (res) config = merge(config, res) } else if (fn) { // merge literal values config = merge(config, fn) } }) // #2206 If config is merged by merge-webpack, it discards the __ruleNames // information injected by webpack-chain. Restore the info so that // vue inspect works properly. if (config !== original) { cloneRuleNames( config.module && config.module.rules, original.module && original.module.rules ) } // check if the user has manually mutated output.publicPath const target = process.env.VUE_CLI_BUILD_TARGET if ( !process.env.VUE_CLI_TEST && (target && target !== 'app') && config.output.publicPath !== this.projectOptions.publicPath ) { throw new Error( `Do not modify webpack output.publicPath directly. ` + `Use the "publicPath" option in vue.config.js instead.` ) } if ( !process.env.VUE_CLI_ENTRY_FILES && typeof config.entry !== 'function' ) { let entryFiles if (typeof config.entry === 'string') { entryFiles = [config.entry] } else if (Array.isArray(config.entry)) { entryFiles = config.entry } else { entryFiles = Object.values(config.entry || []).reduce((allEntries, curr) => { return allEntries.concat(curr) }, []) } entryFiles = entryFiles.map(file => path.resolve(this.context, file)) process.env.VUE_CLI_ENTRY_FILES = JSON.stringify(entryFiles) } return config }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
-