多人团队的NPM包发布问题及解决方案探索

1,494 阅读3分钟

背景

随着前端业务的迭代,代码规模的扩大,难免会抽出一些公用组件或者底层抽象逻辑的独立仓库,以npm包的形式在我们的主项目中使用,由此必然会涉及包的发布,为了不让我们的发布过于混乱和难以管理,我们实践了一些较为便捷的方法来帮助包管理的规范;(注: 此实践基于团队人员较多,且日常迭代上线需要经常变动发布这些npm包,不一定通用)

依赖说明

默认都有Node, Npm, Git基础环境,另外还需要:

核心问题与解决思路

所有包都发布到latest域下

正常的业务开发是分为TEST,BETA,LIVE等不同的环境,我们的问题就在于如何让我们的发布更加隔离,首先想到的就是npm的tag,具体见文档;简而言之我们可以通过npm publish --tag xxx来指定发布在对应的tag下;此外我们还会用到npm view参考这个命令;

版本号冲突

standard-version给我们提供了自动递增版本号的功能,但这就意味这所人都基于release的分支开发在test环境发布会撞车,你先bump了版本并在你的分支发布了,我后面发布就会冲突

关于这个问题,我们想让发布不冲突,就需要对每个人做一个隔离,比如每个人的名字都做一个tag,你发你的我发我的,或者根据当前任务的需求来,用task单号做隔离;

(当然你可以自己改版本号发布,这又会带来更不规范的版本号管理问题,不是很推荐,具体见语义化版本

提MR/PR的时候版本区域冲突

上面的版本号冲突问题可知,在提MR/PR的时候分支的版本号各不相同,会带来冲突;

由此我们想在非live环境其实不需要改版本号,只是我们测试而已,我们只有上LIVE的时候会需要一个版本号的升级;所以想到的就是先发布后回退package.json的文件变化;

主体流程

说了那么多还是会有点混乱,直接看下流程:

image.png

具体实现

// publish.js

const shell = require('shelljs')
const { join } = require('path')
const XXX_REG = /xxx-\d+/i // 需求分支的命名遵循一定规则,便于区分

const rootPath = process.cwd()
const pkg = require(`${join(rootPath, 'package.json')}`)
const packageName = pkg.name
const version = pkg.version // 以这个为大版本,如1.1.0

try {
  const alpha = 'alpha'
  const changefiles = shell.exec('git ls-files -m').stdout.trim() // 检查更新的文件
  if (changefiles.length) {
    throw new Error('本地含有未提交文件')
  }

  // 拿到当前的git分支,计算出当前名字
  const gitBranchName = shell.exec('git branch | grep "*"').stdout
  const isTestFeature = XXX_REG.test(gitBranchName)
  const featureName = isTestFeature && XXX_REG.exec(gitBranchName)[0].toLowerCase()

  // 计算出当前需求分支对应的tag, 如 xxx-123-alpha
  const tag = `${featureName}-${alpha}`

  // 这个命令看对应的tag下是否有发布产物了
  const latestVersion = shell.exec(`npm view ${packageName} version --tag=${tag}`).stdout

  // 计算新版本,有旧的就递增,没有就发布0版本
  let newVersion = ''
  if (latestVersion.includes(tag)) {
    const splitVersions = latestVersion.split(`-${tag}.`)
    const latestMainVersion = splitVersions[0]
    const latestNum = Number(splitVersions[1])
    newVersion = `${latestMainVersion}-${tag}.${latestNum + 1}`
  } else {
    newVersion = `${version}-${tag}.0`
  }

  // 正式发布
  if (isTestFeature && featureName) {
    const versionCommand = `npm run release -- --release-as ${newVersion} --skip.changelog --skip.tag --skip.commit`
    const publishCommand = `npm publish --registry https://company.com --tag ${tag}` // 发布到内网
    shell.exec(`${versionCommand} && ${publishCommand}`)
    shell.exec('git checkout -- package.json') // 回退 standard-version带来的package.json的变化
  }
} catch (e) {
  console.log(e)
  process.exit(1)
}

// package.json
{
  "name": "ureponame",
  "version": "1.1.0",
  ...
  "scripts": {
    "release": "standard-version",
    "publish-local": "node build/publish",
    "prepublishOnly": "npm run build"
  },
  ...
}

使用及说明

利用简短的js脚本和git/npm命令来帮助我们发布,使用上较为方便直接运行npm run publish-local即可;

关于BETA,LIVE的发布在上面没有体现,本着能不动手动发布就不手动发布的原则我们将这些逻辑接入了Gitlab的CI,在master,release分支上自动发布;执行的逻辑大体类似不过多了一些CI的逻辑,后面有时间再专门讨论上线部分;

以上是对与npm包更为便捷的一个小探索,此需求建立在团队的高频变更发布与上线,团队之间情况不尽相同,如有好的点子还请留言交流学习~