依赖安装
在上节中我们分析了获取 preset
,通过 preset
我们可以知道每个 feature
的配置以及整个项目所需的一些插件,接下来我们继续看源码。
const packageManager = (
cliOptions.packageManager ||
loadOptions().packageManager ||
(hasYarn() ? 'yarn' : 'npm')
)
await clearConsole() // 清空控制台
logWithSpinner(`✨`, `Creating project in ${chalk.yellow(context)}.`)
this.emit('creation', { event: 'creating' })
// get latest CLI version
const { latest } = await getVersions()
// generate package.json with plugin dependencies
const pkg = {
name,
version: '0.1.0',
private: true,
devDependencies: {}
}
const deps = Object.keys(preset.plugins)
deps.forEach(dep => {
if (preset.plugins[dep]._isPreset) {
return
}
pkg.devDependencies[dep] = (
preset.plugins[dep].version ||
((/^@vue/.test(dep) && latest[dep]) ? `^${latest[dep]}` : `latest`)
)
})
// write package.json
await writeFileTree(context, {
'package.json': JSON.stringify(pkg, null, 2)
})
这段代码主要有两个作用:获取最新 CLI (包含插件)的版本 和 生成 package.json,接下来一个一个地看。
getVersions
getVersions 的代码不多,看下比较核心的代码:
module.exports = async function getVersions () {
if (sessionCached) {
return sessionCached
}
let latest
const local = require('vue-cli-version-marker').devDependencies
if (process.env.VUE_CLI_TEST || process.env.VUE_CLI_DEBUG) {
return (sessionCached = {
current: local,
latest: local
})
}
if (!fs.existsSync(fsCachePath)) {
// if the cache file doesn't exist, this is likely a fresh install
// then create a cache file with the bundled version map
await fs.writeFile(fsCachePath, JSON.stringify(local))
}
const cached = JSON.parse(await fs.readFile(fsCachePath, 'utf-8'))
const lastChecked = (await fs.stat(fsCachePath)).mtimeMs
const daysPassed = (Date.now() - lastChecked) / (60 * 60 * 1000 * 24)
if (daysPassed > 1) { // 距离上次检查更新超过一天
// if we haven't check for a new version in a day, wait for the check
// before proceeding
latest = await getAndCacheLatestVersions(cached)
} else {
// Otherwise, do a check in the background. If the result was updated,
// it will be used for the next 24 hours.
getAndCacheLatestVersions(cached) // 后台更新
latest = cached
}
return (sessionCached = {
current: local,
latest
})
}
这段代码按顺序读下应该就知道其中的作用了,简单说下就注意两个变量:
- local:本地 CLI 以及插件的版本
- latest:远程 CLI 以及插件的版本
local 和 latest 包含了 CLI 以及相关插件的版本,它们可以用于判断 @vue/cli 是否需要更新以及初始化项目中相关插件的版本。还有点需要注意的是,获取 CLI 的版本并不是直接获取, 而是通过 vue-cli-version-marker npm 包获取的 CLI 版本,为什么会这样做,主要原因有两点:
- vue-cli 从 3.0(@vue/cli) 开始就放在了 @vue 下面,即是一个 scoped package, 而 scoped package 又不支持通过
npm registry
来获取 latest 版本信息。比如 vue-cli-version-marker/latest可以正常访问,而 @vue/cli/latest 则不可以。
- vue-cli 从 3.0(@vue/cli) 开始就放在了 @vue 下面,即是一个 scoped package, 而 scoped package 又不支持通过
- 获取 scoped packages 的数据比获取 unscoped package 通常要慢 300ms。
正是由于上述两个原因,因此通过 unscoped package vue-cli-version-marker
来获取 CLI 版本,vue-cli-version-marker
的内容比较简单,就是一个
package.json,通过获取里面 devDependencies 的版本信息,从而获取 @vue/cli 以及一些插件的版本号。获取了插件版本之后遍历 preset 中所有 plugin 为其初始化版本号,并调用
writeFileTree
生成 package.json 。
installDeps
在生成 package.json 之后,我们再继续看下面的代码:
// intilaize git repository before installing deps
// so that vue-cli-service can setup git hooks.
const shouldInitGit = await this.shouldInitGit(cliOptions)
if (shouldInitGit) {
logWithSpinner(`🗃`, `Initializing git repository...`)
this.emit('creation', { event: 'git-init' })
await run('git init')
}
// install plugins
stopSpinner()
log(`⚙ Installing CLI plugins. This might take a while...`)
log()
this.emit('creation', { event: 'plugins-install' })
if (isTestOrDebug) {
// in development, avoid installation process
await require('./util/setupDevProject')(context) // @vue/cli-service/bin/vue-cli-service
} else {
await installDeps(context, packageManager, cliOptions.registry)
}
这段代码会先调用 shouldInitGit 来判断是否需要 git 初始化,判断的情形有以下几种:
- 没有安装 git (
!hasGit()
):false; - vue create 含有 --git 或者 -g 选项:true;
- vue create 含有 --no-git 或者 -n 选项:false;
- 生成项目的目录是否已经含有 git (
!hasProjectGit(this.context)
):如果有,则返回 false,否则返回 true。
在判断完是否需要 git 初始化项目后,接下来就会调用 installDeps 安装依赖,还是看下 installDeps 的源码:
exports.installDeps = async function installDeps (targetDir, command, cliRegistry) {
const args = []
if (command === 'npm') {
args.push('install', '--loglevel', 'error')
} else if (command === 'yarn') {
// do nothing
} else {
throw new Error(`Unknown package manager: ${command}`)
}
await addRegistryToArgs(command, args, cliRegistry)
debug(`command: `, command) // DEBUG=vue-cli:install vue create demo
debug(`args: `, args)
await executeCommand(command, args, targetDir)
}
源码很简洁,里面又先调用了 addRegistryToArgs
函数,它的作用就是安装依赖是指定安装源,如果 vue create
还有 -r 选项则采用设置的安装源,否则调用 shouldUseTaobao
函数来判断是否需要使用淘宝 NPM 镜像源。实现原理就是发送两个 Promise 使用默认安装源和淘宝镜像源去请求同一个 npm 包,然后利用 Promise.race
看在哪种源下返回结果更快就将此
设置为安装源,另外如果 ~/.vuerc 中设置了useTaobaoRegistry
,则使用设置的安装源。设置了安装源之后则调用 executeCommand
函数利用 execa 执行 npm 或者 yarn 安装命令。
到这里安装依赖就大致介绍完了,在下面一节将介绍 vue create
核心部分 Generator
。