2018-11-04 06:30:00 Cloudy and rainy
又是一個(gè)美好的周末撒桨,早晨六點(diǎn)半就沒(méi)有了睡意煤禽,起床,穿衣服振亮,刷牙(突然想起來(lái)巧还,下周要開(kāi)始新項(xiàng)目了,基礎(chǔ)的前端架構(gòu)該如何搭建呢坊秸?繼續(xù)使用ivew-admin麸祷?好像是挺雞賊的,不過(guò)感覺(jué)用多了褒搔,有點(diǎn)兒弱弱的味道阶牍。嗯,快速地刷完牙星瘾,打開(kāi)電腦走孽,clone下來(lái)之前寫好的的vue-admin,準(zhǔn)備愉快地寫寫代碼琳状,然后就發(fā)生了接下來(lái)的一切......)
入口文件main.js 里面有這么一行代碼:
if (process.env.NODE_ENV !== 'production') require('@/mock')
process.env.NODE_ENV 是什么鬼磕瓷,又是如何設(shè)置為production的呢?我大概翻了翻目錄念逞,發(fā)現(xiàn)沒(méi)有類似env的配置文件生宛,于是我走向了一條不歸路....
項(xiàng)目中的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)
})
嗯,大概瀏覽一遍肮柜,實(shí)例話了 Service
類陷舅,執(zhí)行了 run
函數(shù);process.env.VUE_CLI_CONTEXT
首先這玩意如果不定義是肯定 undefined
审洞,所以讀取了當(dāng)前項(xiàng)目的根目錄位置莱睁;
Service Class
async run (name, args = {}, rawArgv = []) {
// name = serve;args = {},芒澜;rawArgv = []
const mode = args.mode || (name === 'build' && args.watch ? 'development' : this.modes[name])
// mode的取值應(yīng)該是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
進(jìn)行了一個(gè)初始賦值,來(lái)源是 this.plugins
痴晦, this.plugins
又來(lái)源于 this.resolvePlugins(plugins, useBuiltIn)
南吮,讓我們看一下這個(gè)函數(shù)的內(nèi)容,同時(shí)需要注意的是誊酌,this.resolvePlugins(plugins, useBuiltIn)
的第一個(gè)參數(shù)是undefined
這里面引入了isPlugin函數(shù)部凑,我也貼進(jìn)來(lái)
//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) // 這一步露乏,把內(nèi)置的模塊進(jìn)行拼裝成id,apply(是一個(gè)require)的形式
// 代碼看到這里涂邀,我們大概可以知道瘟仿,builtInPlugins就是一個(gè)二維數(shù)組,每一項(xiàng)都有一個(gè)id和apply函數(shù)
// 由形參可以知道比勉,下面會(huì)走else的代碼
if (inlinePlugins) {
plugins = useBuiltIn !== false
? builtInPlugins.concat(inlinePlugins)
: inlinePlugins
} else {
// this.pkg就是package.json(項(xiàng)目的)的json結(jié)構(gòu)劳较,感興趣了可以自己去看看代碼如何獲取的就行了
const projectPlugins = Object.keys(this.pkg.devDependencies || {})
.concat(Object.keys(this.pkg.dependencies || {}))
.filter(isPlugin) // 過(guò)濾了咱們的包里面是@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插件
}
******** (暫時(shí)省略這么多代碼)
}
看到這里,我們可以知道 resolvePlugins
返回的就是一個(gè)數(shù)組浩聋,數(shù)組的每一項(xiàng)分別是id和require的內(nèi)容(這個(gè)后面是需要了解的观蜗,但是我們暫且不做分析,繼續(xù)往下走)
回到this.modes[name]
剛才的邏輯中:
this.modes = this.plugins.reduce((modes, { apply: { defaultModes }}) => {
return Object.assign(modes, defaultModes)
}, {})
apply不是函數(shù)么衣洁?怎么又出來(lái)一個(gè)defaultModes嫂便,這讓我好慌啊闸与!我們就隨便找一個(gè)去看看為啥把。
@vue/cli-service/lib/commands/serve
挑出來(lái)它看一下把岸售,翻回文件的末尾:
module.exports.defaultModes = {
serve: 'development'
}
好吧践樱,原來(lái)除了暴露了一個(gè)函數(shù),還寫了一個(gè)defaultModes的對(duì)象
最終this.modes是具有defaultModes的插件的一個(gè)集合凸丸,感興趣的可以自行打印出來(lái)看一下,拷邢,大概是這樣
{ serve: 'development',
build: 'production',
inspect: 'development' }
分析了這么久,才走了一行代碼.....
下一步是一個(gè)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) { // 執(zhí)行這一步驟
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
}
}
}
// 非類的函數(shù)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
內(nèi)容,然后賦值給 process.env
腻惠,不過(guò)關(guān)于.env
這一塊兒我暫時(shí)沒(méi)看明白环肘,隨后再看;然后開(kāi)始執(zhí)行
this.plugins.forEach(({ id, apply }) => {
apply(new PluginAPI(id, this), this.projectOptions)
})
在我們看PluginAPI構(gòu)造函數(shù)之前集灌,我們還是線看一下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'
// 下面刪除的代碼比較多,可能大括號(hào)不對(duì)應(yīng)哈
}
前面說(shuō)到欣喧,apply
就是一個(gè)require
嘛腌零,內(nèi)容就是這個(gè)暴露的函數(shù),含有兩個(gè)形參唆阿,一個(gè)是api
益涧,一個(gè)是option
,形參api
應(yīng)該就是PluginAPI
實(shí)例化后的一個(gè)方法了驯鳖,看一下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實(shí)例闲询,主要是給實(shí)例的service的commands賦值
}
********* // 有刪減
}
截至到現(xiàn)在久免,應(yīng)該至少知道了this.service.commands['serve']的值是{ fn: async 函數(shù),opts: 那一堆配置 }
然后在Service
的run
函數(shù)知道嘹裂,async函數(shù)就要執(zhí)行了妄壶,傳遞了兩個(gè)參數(shù),args
和rawArgv
然后就是webpack自己的事情了寄狼,開(kāi)啟服務(wù)啊丁寄,熱更新啊什么亂七八糟的...
感覺(jué)自己寫的不太好,后續(xù)再細(xì)讀修改把泊愧!美好的一天要開(kāi)始了
2018-11-01 10:46:00 Sleep
(完)