大家好,我是wo不是黄蓉,今年学习目标从源码共读开始,希望能跟着若川大佬学习源码的思路学到更多的东西。
本文有感于若川的源码共读第3期。
上篇文章写到想学习一下
vue
的parse
和createTemplate
方法,由于看了源码之后,感觉parse方法没什么好讲的,大致流程就是挨个读取vue
文件内容,使用正则匹配标签,设置游标,每次匹配到标签后进行解析,关键函数parseElement、parseTag
等,解析完成后将节点存入nodes
这个数组中,这个数组对象中存的信息包含节点类型、子节点、属性集合标签名等,每解析一步游标就往前推进一步,直至解析结束。
进入正题
首先clone项目
查看package.json
找到发布相关脚本
"scripts": {
"release": "node scripts/release.js",
},
找到入口函数
main().catch(err => {
updateVersions(currentVersion)
console.error(err)
process.exit(1)
})
第1步:main
函数
如果执行pnpm run release
时带了参数,就指定自定义的版本号,接着执行main
的回调函数,更新版本号流程结束。
如果没有:
1.1
借助prompt
实现交互提示选择打包的版本类型,patch
为打补丁的包,minor
为子包,major
为主包,相关版本号命名规范可以参考
async function main() {
let targetVersion = args._[0]
if (!targetVersion) {
//使用prompt做输入提示
const { release } = await prompt({
type: 'select',
name: 'release',
message: 'Select release type',
choices: versionIncrements.map(i => `${i} (${inc(i)})`).concat(['custom'])
})
if (release === 'custom') {
const result = await prompt({
type: 'input',
name: 'version',
message: 'Input custom version',
initial: currentVersion
})
// @ts-ignore
targetVersion = result.version
} else {
targetVersion = release.match(/((.*))/)[1]
}
}
if (skipPrompts) {
step(
isCanary
? `Releasing canary version v${targetVersion}...`
: `Releasing v${targetVersion}...`
)
} else {
// @ts-ignore
const { yes: confirmRelease } = await prompt({
type: 'confirm',
name: 'yes',
message: `Releasing v${targetVersion}. Confirm?`
})
if (!confirmRelease) {
return
}
}
}
第2步 是否跳过执行测试用例
if (!skipTests) {
step('Checking CI status for HEAD...')
let isCIPassed = await getCIResult()
skipTests ||= isCIPassed
if (isCIPassed && !skipPrompts) {
// @ts-ignore
const { yes: promptSkipTests } = await prompt({
type: 'confirm',
name: 'yes',
message: `CI for this commit passed. Skip local tests?`
})
skipTests = promptSkipTests
}
}
execa
执行命令git rev-parse
显示.git所在位置
git rev-parse
是干啥用的?
找了很多资料都说的是用于操作的辅助管道命令,不理解这句话啥意思,但是执行git rev-parse HEAD
输出的最近一次提交的commitid
关于git api
的这个请求返回的内容,可以参考,这个地方获取的信息应该是需要确认你这次发布的内容中是否有新的提交信息,如果有的话才执行提示命令是否继续执行用例
async function getCIResult() {
try {
//
const { stdout: sha } = await execa('git', ['rev-parse', 'HEAD'])
//列出所有仓库关联的分支,加上参数head_sha表示,只返回与指定head_sha关联的工作流运行,sha也就是最近一次的commitid,也就是返回最近一次提交的所有工作流运行
const res = await fetch(
`https://api.github.com/repos/vuejs/core/actions/runs?head_sha=${sha}` +
`&status=success&exclude_pull_requests=true`
)
const data = await res.json()
return data.workflow_runs.length > 0
} catch (e) {
return false
}
}
1.2.1
run
方法,这个方法只是对execa
方法进行了封装,也是为了执行命令用的,具体执行什么命令要看传参
const run = (bin, args, opts = {}) =>
execa(bin, args, { stdio: 'inherit', ...opts })
//main函数
if (!skipTests) {
step('\nRunning tests...')
if (!isDryRun) {
await run('pnpm', ['test', 'run'])
} else {
console.log(`Skipped (dry run)`)
}
} else {
step('Tests skipped.')
}
第3步 更新版本号
updateVersions
更新版本号,更新packages
中所有子包的版本号,更新主包里面依赖的版本号
function updateVersions(version, getNewPackageName = keepThePackageName) {
// 1. update root package.json
updatePackage(path.resolve(__dirname, '..'), version, getNewPackageName)
// 2. update all packages
packages.forEach(p =>
updatePackage(getPkgRoot(p), version, getNewPackageName)
)
}
第4步 执行打包命令
pnpm run build --withTypes
pnpm run test-dts-only
//main函数
if (!skipBuild && !isDryRun) {
await run('pnpm', ['run', 'build', '--withTypes'])
step('\nTesting built types...')
await run('pnpm', ['test-dts-only'])
} else {
console.log(`(skipped)`)
}
第5步 生成更新日志
pnpm run changelog
//main函数
step('\nGenerating changelog...')
await run(`pnpm`, ['run', 'changelog'])
第6步 git操作
对比两个版本内容git diff
如果有没有提交的内容执行git add -A
git commit -m 'release:v 版本号'
//main函数
if (!skipGit) {
const { stdout } = await run('git', ['diff'], { stdio: 'pipe' })
if (stdout) {
step('\nCommitting changes...')
await runIfNotDry('git', ['add', '-A'])
await runIfNotDry('git', ['commit', '-m', `release: v${targetVersion}`])
} else {
console.log('No changes to commit.')
}
}
第7步 发布包,主要是发布成功时打印提示信息
需要查看发布包的类型,根据发布包的类型生成发布提示信息。如果一开始执行pnpm run release 3.3.0-beta.4
时指定了版本号,则上面步骤可以省略,直接执行到main
函数的回调函数,执行updateVersions
操作
async function publishPackage(pkgName, version) {
if (skippedPackages.includes(pkgName)) {
return
}
const pkgRoot = getPkgRoot(pkgName)
const pkgPath = path.resolve(pkgRoot, 'package.json')
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'))
if (pkg.private) {
return
}
let releaseTag = null
if (args.tag) {
releaseTag = args.tag
} else if (version.includes('alpha')) {
releaseTag = 'alpha'
} else if (version.includes('beta')) {
releaseTag = 'beta'
} else if (version.includes('rc')) {
releaseTag = 'rc'
}
step(`Publishing ${pkgName}...`)
try {
await run(
// note: use of yarn is intentional here as we rely on its publishing
// behavior.
'npm',
[
'publish',
...(releaseTag ? ['--tag', releaseTag] : []),
'--access',
'public',
...(isDryRun ? ['--dry-run'] : [])
],
{
cwd: pkgRoot,
stdio: 'pipe'
}
)
console.log(chalk.green(`Successfully published ${pkgName}@${version}`))
} catch (e) {
if (e.stderr.match(/previously published/)) {
console.log(chalk.red(`Skipping already published: ${pkgName}`))
} else {
throw e
}
}
}
至此,vue发布时的流程已经梳理清楚,代码没有那么复杂,但是可以学一些思路。
从这里面我们可以学到如果我们想做一些用户交互性的内容,就可以借助node
+enquirer
实现用户交互,脚手架相关原理应该也是如此,根据对应的选项做不同的操作。
下节学习代码规范相关内容。
网友评论