我们先来看下upgrade命令的配置
packages/@vue/cli/bin/vue.js
program
.command('upgrade [plugin-name]')
.description('(experimental) upgrade vue cli service / plugins')
.option('-t, --to <version>', 'Upgrade <package-name> to a version that is not latest')
.option('-f, --from <version>', 'Skip probing installed plugin, assuming it is upgraded from the designated version')
.option('-r, --registry <url>', 'Use specified npm registry when installing dependencies')
.option('--all', 'Upgrade all plugins')
.option('--next', 'Also check for alpha / beta / rc versions when upgrading')
.action((packageName, options) => {
require('../lib/upgrade')(packageName, options)
})
upgrade命令是用来升级@vue/cli-service和vue-cli插件的,它有以下选项:
-t, --to <version>:升级 到指定的版本-f, --from <version>:跳过本地版本检测,默认插件是从此处指定的版本升级上来-r, --registry <url>:使用指定的 registry 地址安装依赖--all:升级所有的插件--next:检查插件新版本时,包括 alpha/beta/rc 版本在内
命令处理函数中在../lib/upgrade文件中,我们打开该文件可以看到导出了upgrade函数。
packages/@vue/cli/lib/upgrade.js
module.exports = (...args) => {
return upgrade(...args).catch(err => {
error(err)
if (!process.env.VUE_CLI_TEST) {
process.exit(1)
}
})
}
接下来我们来分析下upgrade函数
async function upgrade (packageName, options, context = process.cwd()) {
// 判断是否有修改了未提交的文件
if (!(await confirmIfGitDirty(context))) {
return
}
// 新建一个Upgrader实例
const upgrader = new Upgrader(context)
// 没有指定具体的npm包名称
if (!packageName) {
// 目标版本
if (options.to) {
error(`Must specify a package name to upgrade to ${options.to}`)
process.exit(1)
}
// 是否全部进行升级
if (options.all) {
return upgrader.upgradeAll(options.next)
}
// 查找可以进行升级的npm包
const upgradable = await upgrader.checkForUpdates(options.next)
if (upgradable) {
// 询问是否升级这些包
const { ok } = await inquirer.prompt([
{
name: 'ok',
type: 'confirm',
message: 'Continue to upgrade these plugins?',
default: true
}
])
// 允许升级的话则升级这些可以升级的包
if (ok) {
return upgrader.upgradeAll(options.next)
}
}
return
}
// 升级指定的包
return upgrader.upgrade(packageName, options)
}
这里主要是对命令选项的一些判断,如果没有初始化git或者有待提交的文件是不允许进行升级的,如果有修改了的文件则询问用户是否进行升级。没有指定具体要升级的插件名的话,指定了目标版本则会报错,如果是升级全部插件的话,则执行全部升级,不是全部升级的话,则遍历插件看哪些可以进行升级,遍历完成后询问用户是否进行这些插件的升级,用户允许的话则执行这些插件的升级。
接着我们来看下Upgrader实例
packages/@vue/cli/lib/Upgrader.js
先来看下构造函数里定义的属性
constructor (context = process.cwd()) {
// 获取当前工作目录
this.context = context
// 保存package.json的内容
this.pkg = getPkg(this.context)
// 创建一个包管理对象(获取包地址,安装、升级、删除包等)
this.pm = new PackageManager({ context })
}
然后该实例定义了四个函数
批量升级npm包
async upgradeAll (includeNext) {
// TODO: should confirm for major version upgrades
// for patch & minor versions, upgrade directly
// for major versions, prompt before upgrading
// 判断哪些npm包可以进行升级(包括alpha/beta/rc版本在内)
const upgradable = await this.getUpgradable(includeNext)
// 所有的npm包都是最新的,没有可以升级的
if (!upgradable.length) {
done('Seems all plugins are up to date. Good work!')
return
}
// 循环可以升级的包
for (const p of upgradable) {
// reread to avoid accidentally writing outdated package.json back
// 这里再一次读取了package.json的内容,避免包不是最新的
this.pkg = getPkg(this.context)
// 依次进行升级
await this.upgrade(p.name, { to: p.latest })
}
done('All plugins are up to date!')
}
npm包单个进行升级
async upgrade (pluginId, options) {
// 解析出包名
const packageName = resolvePluginId(pluginId)
let depEntry, required
for (const depType of ['dependencies', 'devDependencies', 'optionalDependencies']) {
if (this.pkg[depType] && this.pkg[depType][packageName]) {
depEntry = depType
// 解析出需要升级的包版本
required = this.pkg[depType][packageName]
break
}
}
// 若没有解析出来,则说明不存在
if (!required) {
throw new Error(`Can't find ${chalk.yellow(packageName)} in ${chalk.yellow('package.json')}`)
}
// 判断插件是从哪个版本升级
const installed = options.from || this.pm.getInstalledVersion(packageName)
// 不存在则抛出错误
if (!installed) {
throw new Error(
`Can't find ${chalk.yellow(packageName)} in ${chalk.yellow('node_modules')}. Please install the dependencies first.\n` +
`Or to force upgrade, you can specify your current plugin version with the ${chalk.cyan('--from')} option`
)
}
// 要升级到的指定版本,没有指定则升级到最新版本
let targetVersion = options.to || 'latest'
// if the targetVersion is not an exact version
// 判断版本号是否符合格式
if (!/\d+\.\d+\.\d+/.test(targetVersion)) {
if (targetVersion === 'latest') {
logWithSpinner(`Getting latest version of ${packageName}`)
} else {
logWithSpinner(`Getting max satisfying version of ${packageName}@${options.to}`)
}
// 获取该npm包的版本
targetVersion = await this.pm.getRemoteVersion(packageName, targetVersion)
// 如果没有指定版本并且版本检查包括 alpha/beta/rc 版本在内
if (!options.to && options.next) {
const next = await this.pm.getRemoteVersion(packageName, 'next')
if (next) {
// targetVersion >= next的话取targetVersion,反之取next
targetVersion = semver.gte(targetVersion, next) ? targetVersion : next
}
}
stopSpinner()
}
// 如果目标版本是已经安装的版本
if (targetVersion === installed) {
log(`Already installed ${packageName}@${targetVersion}`)
// 取较大的版本号
const newRange = tryGetNewerRange(`~${targetVersion}`, required)
if (newRange !== required) {
this.pkg[depEntry][packageName] = newRange
fs.writeFileSync(path.resolve(this.context, 'package.json'), JSON.stringify(this.pkg, null, 2))
log(`${chalk.green('✔')} Updated version range in ${chalk.yellow('package.json')}`)
}
return
}
log(`Upgrading ${packageName} from ${installed} to ${targetVersion}`)
// 执行升级
await this.pm.upgrade(`${packageName}@~${targetVersion}`)
// The cached `pkg` field won't automatically update after running `this.pm.upgrade`.
// Also, `npm install pkg@~version` won't replace the original `"pkg": "^version"` field.
// So we have to manually update `this.pkg` and write to the file system in `runMigrator`
this.pkg[depEntry][packageName] = `~${targetVersion}`
const resolvedPluginMigrator =
resolveModule(`${packageName}/migrator`, this.context)
if (resolvedPluginMigrator) {
// for unit tests, need to run migrator in the same process for mocks to work
// TODO: fix the tests and remove this special case
if (process.env.VUE_CLI_TEST) {
clearRequireCache()
await require('./migrate').runMigrator(
this.context,
{
id: packageName,
apply: loadModule(`${packageName}/migrator`, this.context),
baseVersion: installed
},
this.pkg
)
return
}
const cliBin = path.resolve(__dirname, '../bin/vue.js')
// Run migrator in a separate process to avoid all kinds of require cache issues
await execa('node', [cliBin, 'migrate', packageName, '--from', installed], {
cwd: this.context,
stdio: 'inherit'
})
}
}
先解析出包名,看是否存在于package.json中,不存在则报错,存在则获取当前安装的版本号以及要升级到的目标版本号,进行升级并同时修改package.json文件。
获取哪些包可以进行升级的函数
async getUpgradable (includeNext) {
const upgradable = []
// get current deps
// filter @vue/cli-service, @vue/cli-plugin-* & vue-cli-plugin-*
// 遍历package.json中的依赖
for (const depType of ['dependencies', 'devDependencies', 'optionalDependencies']) {
for (const [name, range] of Object.entries(this.pkg[depType] || {})) {
if (name !== '@vue/cli-service' && !isPlugin(name)) {
continue
}
// 获取当前安装的版本号
const installed = await this.pm.getInstalledVersion(name)
// 获取远程版本号
const wanted = await this.pm.getRemoteVersion(name, range)
// 没有获取到,则说明没有安装
if (!installed) {
throw new Error(`At least one dependency can't be found. Please install the dependencies before trying to upgrade`)
}
// 获取最新的版本号
let latest = await this.pm.getRemoteVersion(name)
// 如果可以安装非正式版本,则获取非正式版本号
if (includeNext) {
const next = await this.pm.getRemoteVersion(name, 'next')
if (next) {
latest = semver.gte(latest, next) ? latest : next
}
}
// 如果当前安装的版本号小于最新的版本号
if (semver.lt(installed, latest)) {
// always list @vue/cli-service as the first one
// as it's depended by all other plugins
if (name === '@vue/cli-service') {
upgradable.unshift({ name, installed, wanted, latest })
} else {
upgradable.push({ name, installed, wanted, latest })
}
}
}
}
// 返回可以进行升级的npm包
return upgradable
}
获取需要升级的包,打印出相关信息
async checkForUpdates (includeNext) {
logWithSpinner('Gathering package information...')
// 获取可以进行升级的包
const upgradable = await this.getUpgradable(includeNext)
stopSpinner()
// 若长度为0,则代表没有可以升级的包,均是最新的
if (!upgradable.length) {
done('Seems all plugins are up to date. Good work!')
return
}
// format the output
// adapted from @angular/cli
// 格式化输出升级的信息
const names = upgradable.map(dep => dep.name)
let namePad = Math.max(...names.map(x => x.length)) + 2
if (!Number.isFinite(namePad)) {
namePad = 30
}
const pads = [namePad, 16, 16, 16, 0]
console.log(
' ' +
['Name', 'Installed', 'Wanted', 'Latest', 'Command to upgrade'].map(
(x, i) => chalk.underline(x.padEnd(pads[i]))
).join('')
)
for (const p of upgradable) {
const fields = [
p.name,
p.installed || 'N/A',
p.wanted,
p.latest,
`vue upgrade ${p.name}${includeNext ? ' --next' : ''}`
]
// TODO: highlight the diff part, like in `yarn outdated`
console.log(' ' + fields.map((x, i) => x.padEnd(pads[i])).join(''))
}
// 返回需要升级的包
return upgradable
}
}