迟到很久很久的阅读系列 感谢若川大哥组织的活动,原文:https://juejin.cn/post/6997943192851054606

1.准备动手

  • 继续上次的vue-next项目
  • 一个vscode
  • 进入项目中的script/release.js中查看本次学习的代码

2.开始调试前的准备

2.1npm钩子

第一次知道npm安装原来也是有钩子的,主要分为三部分:

  • preinstall
  • install
  • postinstall

在项目中,使用了preinstall去执行script/checkYarn.js文件,这会在使用npm i安装依赖前使用正则检查是否为yarn安装,如果不是则直接终端进程并报错
image.png

2.2 几个比较有用的依赖库

2.2.1 minimist

const args = require('minimist')(process.argv.slice(2))
截取argv后的参数,并进行参数的解析(ps:单在这里看的时候我还是没看懂具体该怎么用,往后看就明白了)

2.2.2 semver

语义化版本号,用于做版本号校验的一个库

3.开始调试

3.1 基础参数声明

  1. // 前面提到的解析参数
  2. const args = require('minimist')(process.argv.slice(2))
  3. // 获取preId以及beta版本
  4. // 对应 --preid=beta
  5. const preId =
  6. args.preid ||
  7. (semver.prerelease(currentVersion) && semver.prerelease(currentVersion)[0])
  8. // yarn run release --dry
  9. 获取dry是否为true
  10. const isDryRun = args.dry
  11. // 跳过测试
  12. const skipTests = args.skipTests
  13. // 跳过build
  14. const skipBuild = args.skipBuild
  15. // 读取项目中packages文件夹下的不是.ts结尾 且 不是.开头的文件夹
  16. const packages = fs
  17. .readdirSync(path.resolve(__dirname, '../packages'))
  18. .filter(p => !p.endsWith('.ts') && !p.startsWith('.'))

3.2 基础脚本函数声明

  1. // 执行jest命令
  2. const bin = name => path.resolve(__dirname, '../node_modules/.bin/' + name)
  3. // 执行真实运行命令
  4. const run = (bin, args, opts = {}) =>
  5. execa(bin, args, { stdio: 'inherit', ...opts })
  6. // 执行空跑命令,只console.log打印
  7. const dryRun = (bin, args, opts = {}) =>
  8. console.log(chalk.blue(`[dryrun] ${bin} ${args.join(' ')}`), opts)
  9. // 根据上面的声明决定是否需要空跑
  10. const runIfNotDry = isDryRun ? dryRun : run

3.3最重要的main发布函数

3.3.1 确认要发布的版本
  1. // yarn run release && yarn run release 版本号
  2. // 读取版本号
  3. let targetVersion = args._[0]
  4. // 如果没有读取到,将会互动拉取现在的版本号并选择要发布的版本
  5. if (!targetVersion) {
  6. // no explicit version, offer suggestions
  7. const { release } = await prompt({
  8. type: 'select',
  9. name: 'release',
  10. message: 'Select release type',
  11. choices: versionIncrements.map(i => `${i} (${inc(i)})`).concat(['custom'])
  12. })
  13. // 选择其他版本
  14. if (release === 'custom') {
  15. targetVersion = (
  16. await prompt({
  17. type: 'input',
  18. name: 'version',
  19. message: 'Input custom version',
  20. initial: currentVersion
  21. })
  22. ).version
  23. } else {
  24. targetVersion = release.match(/\((.*)\)/)[1]
  25. }
  26. }
  27. // 检查版本号是否符合规范
  28. if (!semver.valid(targetVersion)) {
  29. throw new Error(`invalid target version: ${targetVersion}`)
  30. }
  31. // 如果填写了要发布的版本
  32. const { yes } = await prompt({
  33. type: 'confirm',
  34. name: 'yes',
  35. message: `Releasing v${targetVersion}. Confirm?`
  36. })
  37. if (!yes) {
  38. return
  39. }

3.3.2 执行测试用例
  1. // 调用jest执行测试用例
  2. // 这里会根据输入的命令决定是否需要执行以及执行的具体步骤
  3. step('\nRunning tests...')
  4. if (!skipTests && !isDryRun) {
  5. await run(bin('jest'), ['--clearCache'])
  6. await run('yarn', ['test', '--bail'])
  7. } else {
  8. console.log(`(skipped)`)
  9. }

3.3.3 更新所有版本号
  1. // update all package versions and inter-dependencies
  2. // 主更新函数
  3. step('\nUpdating cross dependencies...')
  4. updateVersions(targetVersion)
  5. // 接着又通过三个函数执行更新操作
  6. // 第一个函数
  7. // 更新package.json中的版本号
  8. // 循环遍历更新所有包钟的版本号
  9. function updateVersions(version) {
  10. // 1. update root package.json
  11. updatePackage(path.resolve(__dirname, '..'), version)
  12. // 2. update all packages
  13. packages.forEach(p => updatePackage(getPkgRoot(p), version))
  14. }
  15. // 第二个函数
  16. // 执行更新package.json
  17. // 更新dependencies部分中 vue相关的依赖版本
  18. // 更新peerDependencies部分中 vue相关的依赖版本
  19. function updatePackage(pkgRoot, version) {
  20. const pkgPath = path.resolve(pkgRoot, 'package.json')
  21. const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'))
  22. pkg.version = version
  23. updateDeps(pkg, 'dependencies', version)
  24. updateDeps(pkg, 'peerDependencies', version)
  25. fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + '\n')
  26. }
  27. // 第三个函数
  28. // 这里执行vue相关依赖的更新
  29. function updateDeps(pkg, depType, version) {
  30. const deps = pkg[depType]
  31. if (!deps) return
  32. Object.keys(deps).forEach(dep => {
  33. if (
  34. dep === 'vue' ||
  35. (dep.startsWith('@vue') && packages.includes(dep.replace(/^@vue\//, '')))
  36. ) {
  37. console.log(
  38. chalk.yellow(`${pkg.name} -> ${depType} -> ${dep}@${version}`)
  39. )
  40. deps[dep] = version
  41. }
  42. })
  43. }

3.3.4 打包编译所有的包
  1. // 执行编译打包
  2. // 同样可以根据执行命令时的参数进行区分执行
  3. step('\nBuilding all packages...')
  4. if (!skipBuild && !isDryRun) {
  5. await run('yarn', ['build', '--release'])
  6. // test generated dts files
  7. step('\nVerifying type declarations...')
  8. await run('yarn', ['test-dts-only'])
  9. } else {
  10. console.log(`(skipped)`)
  11. }

3.3.5 生成changelog

这一部分执行了yarn changelog脚本,实际上执行的是conventional-changelog -p angular -i CHANGELOG.md -s

3.3.6 提交代码

通过git diff找到是否有文件的改动,以及是否需要提交文件

3.3.7 发布包

通过执行yarn publish进行包的发布,同时兼容了vue2.x和vue3.x版本

3.3.8 推送github
  1. // push to GitHub
  2. step('\nPushing to GitHub...')
  3. // 打一个tag
  4. await runIfNotDry('git', ['tag', `v${targetVersion}`])
  5. // 推送这个tag
  6. await runIfNotDry('git', ['push', 'origin', `refs/tags/v${targetVersion}`])
  7. // 执行git push
  8. await runIfNotDry('git', ['push'])

3.3.9 全流程

至此发布vue版本的流程已经全部走完,整个流程可以概括为以下这些步骤
image.png

4. 总结

  • 简单粗略的了解了vue发布的全流程
  • 对发布过程中关键步骤的代码进行了解读

自己在公司的项目开发中并没有过多的了解目前项目发布的流程,会在后续了解项目发布的流程后,与学习到的知识进行对比,了解是否有可优化的点