vue3 release 源码解读

521 阅读3分钟

若川源码解读源地址:juejin.cn/post/699794… 感谢若川哥组织的阅读源码活动,收获很大,想加入的小伙伴看过来: 公告

1. 相关依赖包

const args = require('minimist')(process.argv.slice(2))
const fs = require('fs')
const path = require('path')
const chalk = require('chalk')
const semver = require('semver')
const currentVersion = require('../package.json').version
const { prompt } = require('enquirer')
const execa = require('execa')

minimist

minimist轻量级的命令行参数解析引擎

解析过程中,minimist会依次匹配不同的模式,从long options到short options,匹配之后再进行相应的解析工作。

其中process.argv的第一和第二个元素是Node可执行文件和被执行JavaScript文件的完全限定的文件系统路径,无论你是否这样输入他们。

$ node example/parse.js -a beep -b boop
{ _: [], a: 'beep', b: 'boop' }

$ node example/parse.js -x 3 -y 4 -n5 -abc --beep=boop foo bar baz
{ _: [ 'foo', 'bar', 'baz' ],
  x: 3,
  y: 4,
  n: 5,
  a: true,
  b: true,
  c: true,
  beep: 'boop' }

chalk

chalk 包的作用是修改控制台中字符串的样式,包括:

  1. 字体样式(加粗、隐藏等)
  2. 字体颜色
  3. 背景颜色

semver

语义化版本的nodejs实现,用于版本校验比较等。

semver 定义了两种概念:

  • 版本是指例如 0.4.1、1.2.7、1.2.4-beta.0 这样表示包的特定版本的字符串。
  • 范围则是对满足特定规则的版本的一种表示,例如 1.2.3-2.3.4、1.x、^0.2、>1.4.

在这两种概念上可以进行很多种计算,例如比较两个版本的大小、判断一个版本是否满足一个范围、判断一个版本是否比范围中的任何版本都大等。

版本格式:主版本号.次版本号.修订号,版本号递增规则如下
主版本号:当你做了不兼容的 API 修改,
次版本号:当你做了向下兼容的功能性新增,
修订号:当你做了向下兼容的问题修正。
先行版本号及版本编译信息可以加到“主版本号.次版本号.修订号”的后面,作为延伸。

enquirer

命令行交互工具,例如发布时选择相关版本号

execa

execa是可以调用shell和本地外部程序的javascript封装。会启动子进程执行。支持多操作系统,包括windows。如果父进程退出,则生成的全部子进程都被杀死。

2. 主要流程解读

运行下面的命令 yarn release --dry,会控制台会显示发布的整套流程

// 执行测试用例
Running tests...
(skipped)

// 更新依赖版本
Updating cross dependencies...

// 打包编译所有包
Building all packages...

// 生成CHANGELOG.md文件描述
$ conventional-changelog -p angular -i CHANGELOG.md -s

// commit代码
Committing changes...

// 发布包
Publishing packages...
Publishing compiler-core...

// 提交到github
Pushing to GitHub...

3. main函数

取版本号

// 根据上文 mini 这句代码意思是 yarn run release 3.2.4 
// 取到参数 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'])
  })

  if (release === 'custom') {
    targetVersion = (
      await prompt({
        type: 'input',
        name: 'version',
        message: 'Input custom version',
        initial: currentVersion
      })
    ).version
  } else {
    targetVersion = release.match(/((.*))/)[1]
  }
}

// 校验版本是否符合规范
if (!semver.valid(targetVersion)) {
  throw new Error(`invalid target version: ${targetVersion}`)
}

// 确认要 release
const { yes } = await prompt({
  type: 'confirm',
  name: 'yes',
  message: `Releasing v${targetVersion}. Confirm?`
})

运行测试用例

// run tests before release
  step('\nRunning tests...')
  if (!skipTests && !isDryRun) {
    await run(bin('jest'), ['--clearCache'])
    await run('yarn', ['test', '--bail'])
  } else {
    console.log(`(skipped)`)
  }

更新依赖版本号

流程:

  1. 自己本身 package.json 的版本号
  2. packages.json 中 dependencies 中 vue 相关的依赖修改
  3. packages.json 中 peerDependencies 中 vue 相关的依赖修改
step('\nUpdating cross dependencies...')
updateVersions(targetVersion)

//更新根目录版本号和更新所有依赖package版本
function updateVersions(version) {
  // 1. update root package.json
  updatePackage(path.resolve(__dirname, '..'), version)
  // 2. update all packages
  packages.forEach(p => updatePackage(getPkgRoot(p), version))
}

// 更新版本
function updatePackage(pkgRoot, version) {
  const pkgPath = path.resolve(pkgRoot, 'package.json')
  const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'))
  pkg.version = version
  updateDeps(pkg, 'dependencies', version)
  updateDeps(pkg, 'peerDependencies', version)
  fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + '\n')
}

打包和编译

// build all packages with types
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) {
  step('\nCommitting changes...')
  await runIfNotDry('git', ['add', '-A'])
  await runIfNotDry('git', ['commit', '-m', `release: v${targetVersion}`])
} else {
  console.log('No changes to commit.')
}

// publish packages
step('\nPublishing packages...')
for (const pkg of packages) {
  await publishPackage(pkg, targetVersion, runIfNotDry)
}

// 发布到github
step('\nPushing to GitHub...')
await runIfNotDry('git', ['tag', `v${targetVersion}`])
await runIfNotDry('git', ['push', 'origin', `refs/tags/v${targetVersion}`])
await runIfNotDry('git', ['push'])


// 如果传了这个参数则输出 可以用 git diff 看看更改

// const isDryRun = args.dry
if (isDryRun) {
  console.log(`\nDry run finished - run git diff to see package changes.`)
}

// 如果 跳过的包,则输出以下这些包没有发布。不过代码 `skippedPackages` 里是没有包。
// 所以这段代码也不会执行。
// 我们习惯写 arr.length !== 0 其实 0 就是 false 。可以不写。
if (skippedPackages.length) {
  console.log(
    chalk.yellow(
      `The following packages are skipped and NOT published:\n- ${skippedPackages.join(
        '\n- '
      )}`
    )
  )
}
console.log()

总结

通过这次学习:

  1. 熟悉vue的发布
  2. 会调试部分node
  3. 学习到一些依赖库的功能作用