2018-11-04 06:30:00 Cloudy and rainy
又是一个美好的周末,早晨六点半就没有了睡意,起床,穿衣服,刷牙(突然想起来,下周要开始新项目了,基础的前端架构该如何搭建呢?继续使用ivew-admin?好像是挺鸡贼的,不过感觉用多了,有点儿弱弱的味道。嗯,快速地刷完牙,打开电脑,clone下来之前写好的的vue-admin,准备愉快地写写代码,然后就发生了接下来的一切......)
入口文件main.js 里面有这么一行代码:
if (process.env.NODE_ENV !== 'production') require('@/mock')
process.env.NODE_ENV 是什么鬼,又是如何设置为production的呢?我大概翻了翻目录,发现没有类似env的配置文件,于是我走向了一条不归路....
项目中的package.json如下所示:
"scripts": {
"dev": "vue-cli-service serve --open",
"build": "vue-cli-service build",
"lint": "vue-cli-service lint",
"test:unit": "vue-cli-service test:unit",
"test:e2e": "vue-cli-service test:e2e"
},
在node_modules里面找到@vue/cli-service/bin 下的vue-cli-service.js
#!/usr/bin/env node
const semver = require('semver')
const { error } = require('@vue/cli-shared-utils')
const requiredVersion = require('../package.json').engines.node
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)
}
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',
'watch',
// serve
'open',
'copy',
'https',
// inspect
'verbose'
]
})
const command = args._[0]
service.run(command, args, rawArgv).catch(err => {
error(err)
process.exit(1)
})
嗯,大概浏览一遍,实例话了 Service
类,执行了 run
函数;process.env.VUE_CLI_CONTEXT
首先这玩意如果不定义是肯定 undefined
,所以读取了当前项目的根目录位置;
Service Class
async run (name, args = {}, rawArgv = []) {
// name = serve;args = {},;rawArgv = []
const mode = args.mode || (name === 'build' && args.watch ? 'development' : this.modes[name])
// mode的取值应该是this.modes[name],那么让我们看一下this.modes是什么(暂且忽略下面的代码哈)
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()
}
const { fn } = command
return fn(args, rawArgv)
}
this.modes[name]
module.exports = class Service {
constructor (context, { plugins, pkg, inlineOptions, useBuiltIn } = {}) {
process.VUE_CLI_SERVICE = this
this.initialized = false
this.context = context
this.inlineOptions = inlineOptions
this.webpackChainFns = []
this.webpackRawConfigFns = []
this.devServerConfigFns = []
this.commands = {}
// Folder containing the target package.json for plugins
this.pkgContext = context
// package.json containing the plugins
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)
// 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)
}, {})
}
在最后一行看到 this.modes
进行了一个初始赋值,来源是 this.plugins
, this.plugins
又来源于 this.resolvePlugins(plugins, useBuiltIn)
,让我们看一下这个函数的内容,同时需要注意的是,this.resolvePlugins(plugins, useBuiltIn)
的第一个参数是undefined
这里面引入了isPlugin函数,我也贴进来
//const pluginRE = /^(@vue\/|vue-|@[\w-]+\/vue-)cli-plugin-/
//exports.isPlugin = id => pluginRE.test(id)
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/dev',
'./config/prod',
'./config/app'
].map(idToPlugin) // 这一步,把内置的模块进行拼装成id,apply(是一个require)的形式
// 代码看到这里,我们大概可以知道,builtInPlugins就是一个二维数组,每一项都有一个id和apply函数
// 由形参可以知道,下面会走else的代码
if (inlinePlugins) {
plugins = useBuiltIn !== false
? builtInPlugins.concat(inlinePlugins)
: inlinePlugins
} else {
// this.pkg就是package.json(项目的)的json结构,感兴趣了可以自己去看看代码如何获取的就行了
const projectPlugins = Object.keys(this.pkg.devDependencies || {})
.concat(Object.keys(this.pkg.dependencies || {}))
.filter(isPlugin) // 过滤了咱们的包里面是@vue/cli-plugin 插件的
.map(id => {
// 下面代码也是走的else
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) // 合并了builtIn插件和project插件
}
******** (暂时省略这么多代码)
}
看到这里,我们可以知道 resolvePlugins
返回的就是一个数组,数组的每一项分别是id和require的内容(这个后面是需要了解的,但是我们暂且不做分析,继续往下走)
回到this.modes[name]
刚才的逻辑中:
this.modes = this.plugins.reduce((modes, { apply: { defaultModes }}) => {
return Object.assign(modes, defaultModes)
}, {})
apply不是函数么?怎么又出来一个defaultModes,这让我好慌啊!我们就随便找一个去看看为啥把。
@vue/cli-service/lib/commands/serve
挑出来它看一下把,翻回文件的末尾:
module.exports.defaultModes = {
serve: 'development'
}
好吧,原来除了暴露了一个函数,还写了一个defaultModes的对象
最终this.modes是具有defaultModes的插件的一个集合,感兴趣的可以自行打印出来看一下,,大概是这样
{ serve: 'development',
build: 'production',
inspect: 'development' }
分析了这么久,才走了一行代码.....
下一步是一个this.init(mode),mode是development字符串;看一下init的代码把:
init (mode = process.env.VUE_CLI_MODE) {
if (this.initialized) {
return
}
this.initialized = true
this.mode = mode
if (mode) { // 执行这一步骤
this.loadEnv(mode)
}
// load base .env
this.loadEnv()
// load user config
const userOptions = this.loadUserOptions()
this.projectOptions = defaultsDeep(userOptions, defaults())
debug('vue:project-config')(this.projectOptions)
// apply plugins.
this.plugins.forEach(({ id, apply }) => {
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)
}
}
loadEnv (mode) {
const logger = debug('vue:env')
const basePath = path.resolve(this.context, `.env${mode ? `.${mode}` : ``}`)
const localPath = `${basePath}.local`
const load = path => {
try {
const res = loadEnv(path)
logger(path, res)
} catch (err) {
// only ignore error if file is not found
if (err.toString().indexOf('ENOENT') < 0) {
error(err)
}
}
}
load(localPath)
load(basePath)
// by default, NODE_ENV and BABEL_ENV are set to "development" unless mode
// is production or test. However the value in .env files will take higher
// priority.
if (mode) {
// always set NODE_ENV during tests
// as that is necessary for tests to not be affected by each other
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
}
}
}
// 非类的函数loadEnv
module.exports = function loadEnv (path = '.env') {
const config = parse(fs.readFileSync(path, 'utf-8'))
Object.keys(config).forEach(key => {
if (typeof process.env[key] === 'undefined') {
process.env[key] = config[key]
}
})
return config
}
核心的步骤就是readFileSync一下 .env
和.env.local
内容,然后赋值给 process.env
,不过关于.env
这一块儿我暂时没看明白,随后再看;然后开始执行
this.plugins.forEach(({ id, apply }) => {
apply(new PluginAPI(id, this), this.projectOptions)
})
在我们看PluginAPI构造函数之前,我们还是线看一下plugins里面apply是干了啥把。
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`,
'--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`
}
}, async function serve (args) {
info('Starting development server...')
// although this is primarily a dev server, it is possible that we
// are running it in a mode with a production env, e.g. in E2E tests.
const isInContainer = checkInContainer()
const isProduction = process.env.NODE_ENV === 'production'
// 下面删除的代码比较多,可能大括号不对应哈
}
前面说到,apply
就是一个require
嘛,内容就是这个暴露的函数,含有两个形参,一个是api
,一个是option
,形参api
应该就是PluginAPI
实例化后的一个方法了,看一下PluginAPI
的代码:
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 || {}} // this值得是Service实例,主要是给实例的service的commands赋值
}
********* // 有删减
}
截至到现在,应该至少知道了this.service.commands['serve']的值是{ fn: async 函数,opts: 那一堆配置 }
然后在Service
的run
函数知道,async函数就要执行了,传递了两个参数,args
和rawArgv
然后就是webpack自己的事情了,开启服务啊,热更新啊什么乱七八糟的...
感觉自己写的不太好,后续再细读修改把!美好的一天要开始了
2018-11-01 10:46:00 Sleep
(完)