本文参加了由公众号@若川视野 发起的每周源码共读活动,点击了解详情一起参与。
本篇文章要讲解的是Vue.js的发布流程,学习资料github.com/vuejs/core
前期准备
- 将项目克隆下来后,可以查看package.json文件或者查看官方项目的贡献指南去寻找自己想要得到的信息
- 这里我们查看package.json文件,vue的发布是scripts下的release命令
- 根据命令我们知道运行的是release.js文件
- 在阅读源码的过程中,由于之前对release.js文件中的相关依赖不是很熟悉,在了解了相关依赖后,阅读起来还是相对顺畅的,所以阅读前先了解文件中的相关依赖
相关依赖
minimist www.npmjs.com/package/min… 解析命令行参数选项
chalk www.npmjs.com/package/cha… 终端多色彩输出
semver www.npmjs.com/package/sem… 语义版本器,用于版本比较和验证
enquirer www.npmjs.com/package/enq… 创建CLI提示,用于交互
execa www.npmjs.com/package/exe… 执行终端命令
源码阅读
这一部分定义了一些变量
// 根据命令行参数取得preid
const preId =
args.preid ||
(semver.prerelease(currentVersion) && semver.prerelease(currentVersion)[0])
// 根据命令行参数取得dry
const isDryRun = args.dry
// 根据命令行参数取得skipTests
const skipTests = args.skipTests
// 根据命令行参数取得skipBuild
const skipBuild = args.skipBuild
// 读取packages文件夹,过滤掉以.ts结尾和以.开头的文件
const packages = fs
.readdirSync(path.resolve(__dirname, '../packages'))
.filter(p => !p.endsWith('.ts') && !p.startsWith('.'))
// 要跳过的包
const skippedPackages = []
// 版本递增 可以看semver文档
const versionIncrements = [
'patch',
'minor',
'major',
...(preId ? ['prepatch', 'preminor', 'premajor', 'prerelease'] : [])
]
这一部分是函数的定义
// 生成版本函数
const inc = i => semver.inc(currentVersion, i, preId)
const bin = name => path.resolve(__dirname, '../node_modules/.bin/' + name)
// 执行命令函数
const run = (bin, args, opts = {}) =>
execa(bin, args, { stdio: 'inherit', ...opts })
const dryRun = (bin, args, opts = {}) =>
console.log(chalk.blue(`[dryrun] ${bin} ${args.join(' ')}`), opts)
const runIfNotDry = isDryRun ? dryRun : run
// 获取包文件夹函数
const getPkgRoot = pkg => path.resolve(__dirname, '../packages/' + pkg)
// 控制台输出函数
const step = msg => console.log(chalk.cyan(msg))
主函数main
发布的主要流程都在main函数中
async main() {}
// 取得执行命令时要发布的版本号 yarn release 3.2.4
let targetVersion = args._[0]
// 没有版本号时,提供选项
if (!targetVersion) {
// no explicit version, offer suggestions
const { release } = await prompt({
type: 'select',
name: 'release',
message: 'Select release type',
choices: versionIncrements.map(i => `${i} (${inc(i)})`).concat(['custom'])
})
// 如果选择的是custom
if (release === 'custom') {
targetVersion = (
await prompt({
type: 'input',
name: 'version',
message: 'Input custom version',
initial: currentVersion
})
).version
} else {
// 否则targetVersion为3.2.5
targetVersion = release.match(/\((.*)\)/)[1]
}
}
上面这段代码在结合调试结果来看,执行yarn release 3.2.4,targetVersion为3.2.4
直接执行命令没有提供版本号时,就会给我们选择
// 校验版本是否符合规范
if (!semver.valid(targetVersion)) {
throw new Error(`invalid target version: ${targetVersion}`)
}
// 确认
const { yes } = await prompt({
type: 'confirm',
name: 'yes',
message: `Releasing v${targetVersion}. Confirm?`
})
if (!yes) {
return
}
执行测试
step('\nRunning tests...')
// 没有skipTests和isDryRun为false时
if (!skipTests && !isDryRun) {
await run(bin('jest'), ['--clearCache'])
await run('yarn', ['test', '--bail'])
} else {
console.log(`(skipped)`)
}
更新所有包
step('\nUpdating cross dependencies...')
updateVersions(targetVersion)
打包所有包
step('\nBuilding all packages...')
if (!skipBuild && !isDryRun) {
await run('yarn', ['build', '--release'])
// test generated dts files
step('\nVerifying type declarations...')
await run('yarn', ['test-dts-only'])
} else {
console.log(`(skipped)`)
}
生成changelog
await run(`yarn`, ['changelog'])
代码提交
// 文件是否有改变
const { stdout } = await run('git', ['diff'], { stdio: 'pipe' })
if (stdout) {
// 有改变执行git add git commit
step('\nCommitting changes...')
await runIfNotDry('git', ['add', '-A'])
await runIfNotDry('git', ['commit', '-m', `release: v${targetVersion}`])
} else {
console.log('No changes to commit.')
}
发布包
step('\nPublishing packages...')
for (const pkg of packages) {
await publishPackage(pkg, targetVersion, runIfNotDry)
}
push到GitHub
step('\nPushing to GitHub...')
await runIfNotDry('git', ['tag', `v${targetVersion}`])
await runIfNotDry('git', ['push', 'origin', `refs/tags/v${targetVersion}`])
await runIfNotDry('git', ['push'])
if (isDryRun) {
console.log(`\nDry run finished - run git diff to see package changes.`)
}
...
console.log()
}
main函数的主题函数就是这些了,还有些函数的具体实现可以到源码中查看,基本上没什么难度能看懂
总结
这里的总结借用若川哥的一张图