乐趣区

关于vue.js:Vue-32-发布了那尤雨溪是怎么发布-Vuejs-的

1. 前言

大家好,我是若川。欢送关注我的公众号:若川视线,最近组织了 源码共读 流动,感兴趣的能够加我微信 ruochuan12,长期交流学习。

之前写的《学习源码整体架构系列》蕴含 jQueryunderscorelodashvuexsentryaxiosreduxkoavue-devtoolsvuex4 十篇源码文章。

写绝对很难的源码,消耗了本人的工夫和精力,也没播种多少浏览点赞,其实是一件挺受打击的事件。从浏览量和读者受害方面来看,不能促成作者继续输入文章。

所以转变思路,写一些绝对通俗易懂的文章。其实源码也不是设想的那么难,至多有很多看得懂

最近尤雨溪公布了 3.2 版本。小版本曾经是 3.2.4 了。本文来学习下尤大是怎么公布 vuejs 的,学习源码为本人所用。

本文波及到的 vue-next/scripts/release.js文件,整个文件代码行数尽管只有 200 余行,但十分值得咱们学习。

歌德曾说:读一本好书,就是在和崇高的人谈话。同理可得:读源码,也算是和作者的一种学习交换的形式。

浏览本文,你将学到:

1. 相熟 vuejs 公布流程
2. 学会调试 nodejs 代码
3. 入手优化公司我的项目公布流程

环境筹备之前,咱们先预览下 vuejs 的公布流程。

2. 环境筹备

关上 vue-next,
开源我的项目个别都能在 README.md 或者 .github/contributing.md 找到奉献指南。

而奉献指南写了很多对于参加我的项目开发的信息。比方怎么跑起来,我的项目目录构造是怎么的。怎么投入开发,须要哪些常识储备等。

你须要确保 Node.js 版本是 10+, 而且 yarn 的版本是 1.x Yarn 1.x。

你装置的 Node.js 版本很可能是低于 10。最简略的方法就是去官网重新安装。也能够应用 nvm等治理 Node.js 版本。

node -v
# v14.16.0
# 全局装置 yarn
# 克隆我的项目
git clone https://github.com/vuejs/vue-next.git
cd vue-next

# 或者克隆我的我的项目
git clone https://github.com/lxchuan12/vue-next-analysis.git
cd vue-next-analysis/vue-next

# 装置 yarn
npm install --global yarn
# 装置依赖
yarn # install the dependencies of the project
# yarn release

2.1 严格校验应用 yarn 装置依赖

接着咱们来看下 vue-next/package.json 文件。

// vue-next/package.json
{
    "private": true,
    "version": "3.2.4",
    "workspaces": ["packages/*"],
    "scripts": {
        // --dry 参数是我加的,如果你是调试 代码也倡议加
        // 不执行测试和编译、不执行 推送 git 等操作
        // 也就是说空跑,只是打印,后文再具体讲述
        "release": "node scripts/release.js --dry",
        "preinstall": "node ./scripts/checkYarn.js",
    }
}

如果你尝试应用 npm 装置依赖,应该是会报错的。为啥会报错呢。
因为 package.json 有个前置 preinstall node ./scripts/checkYarn.js 判断强制要求是应用 yarn 装置。

scripts/checkYarn.js文件如下,也就是在 process.env 环境变量中找执行门路 npm_execpath,如果不是yarn 就输入正告,且过程完结。

// scripts/checkYarn.js
if (!/yarn\.js$/.test(process.env.npm_execpath || '')) {
  console.warn('\u001b[33mThis repository requires Yarn 1.x for scripts to work properly.\u001b[39m\n')
  process.exit(1)
}

如果你想疏忽这个前置的钩子判断,能够应用yarn --ignore-scripts 命令。也有后置的钩子post。更多具体的能够查看 npm 文档

2.2 调试 vue-next/scripts/release.js 文件

接着咱们来学习如何调试 vue-next/scripts/release.js文件。

这里申明下我的 VSCode 版本 是 1.59.0 应该 1.50.0 起就能够按以下步骤调试了。

code -v
# 1.59.0

找到 vue-next/package.json 文件关上,而后在 scripts 上方,会有debug(调试)按钮,点击后,抉择 release。即可进入调试模式。

这时终端会如下图所示,有 Debugger attached. 输入。这时放张图。

更多 nodejs 调试相干 能够查看官网文档

学会调试后,先大抵走一遍流程,在要害中央多打上几个断点多走几遍,就能猜测到源码用意了。

3 文件结尾的一些依赖引入和函数申明

咱们能够跟着断点来,先看文件结尾的一些依赖引入和函数申明

3.1 第一局部

// vue-next/scripts/release.js
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')

通过依赖,咱们能够在 node_modules 找到对应装置的依赖。也能够找到其 READMEgithub仓库。

3.1.1 minimist 命令行参数解析

minimist

简略说,这个库,就是解析命令行参数的。看例子,咱们比拟容易看懂传参和解析后果。

$ 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' }
const args = require('minimist')(process.argv.slice(2))

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

3.1.2 chalk 终端多色调输入

chalk

简略说,这个是用于终端显示多色调输入。

3.1.3 semver 语义化版本

semver

语义化版本的 nodejs 实现,用于版本校验比拟等。对于语义化版本能够看这个语义化版本 2.0.0 文档

版本格局:主版本号. 次版本号. 订正号,版本号递增规定如下:

主版本号:当你做了不兼容的 API 批改,

次版本号:当你做了向下兼容的功能性新增,

订正号:当你做了向下兼容的问题修改。

后行版本号及版本编译信息能够加到“主版本号. 次版本号. 订正号”的前面,作为延长。

3.1.4 enquirer 交互式询问 CLI

简略说就是交互式询问用户输出。

enquirer

3.1.5 execa 执行命令

简略说就是执行命令的,相似咱们本人在终端输出命令,比方 echo 若川

execa

// 例子
const execa = require('execa');

(async () => {const {stdout} = await execa('echo', ['unicorns']);
  console.log(stdout);
  //=> 'unicorns'
})();

看完了第一局部,接着咱们来看第二局部。

3.2 第二局部

// vue-next/scripts/release.js

// 对应 yarn run release --preid=beta
// beta
const preId =
  args.preid ||
  (semver.prerelease(currentVersion) && semver.prerelease(currentVersion)[0])
// 对应 yarn run release --dry
// true
const isDryRun = args.dry
// 对应 yarn run release --skipTests
// true 跳过测试
const skipTests = args.skipTests
// 对应 yarn run release --skipBuild 
// true
const skipBuild = args.skipBuild

// 读取 packages 文件夹,过滤掉 不是 .ts 文件 结尾 并且不是 . 结尾的文件夹
const packages = fs
  .readdirSync(path.resolve(__dirname, '../packages'))
  .filter(p => !p.endsWith('.ts') && !p.startsWith('.'))

第二局部绝对简略,持续看第三局部。

3.3 第三局部

// vue-next/scripts/release.js

// 跳过的包
const skippedPackages = []

// 版本递增
const versionIncrements = [
  'patch',
  'minor',
  'major',
  ...(preId ? ['prepatch', 'preminor', 'premajor', 'prerelease'] : [])
]

const inc = i => semver.inc(currentVersion, i, preId)

这一块可能不是很好了解。inc是生成一个版本。更多能够查看 semver 文档

semver.inc('3.2.4', 'prerelease', 'beta')
// 3.2.5-beta.0

3.4 第四局部

第四局部申明了一些执行脚本函数等

// vue-next/scripts/release.js

// 获取 bin 命令
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))

3.4.1 bin 函数

获取 node_modules/.bin/ 目录下的命令,整个文件就用了一次。

bin('jest')

相当于在命令终端,我的项目根目录 运行 ./node_modules/.bin/jest 命令。

3.4.2 run、dryRun、runIfNotDry

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

run 实在在终端跑命令,比方 yarn build --release

dryRun 则是不跑,只是 console.log(); 打印 ‘yarn build –release’

runIfNotDry 如果不是空跑就执行命令。isDryRun 参数是通过控制台输出的。yarn run release --dry这样就是 truerunIfNotDry 就是只是打印,不执行命令。这样设计的益处在于,能够有时不想间接提交,要先看看执行命令的后果。不得不说,尤大就是会玩。

main 函数开端,也能够看到相似的提醒。能够用 git diff 先看看文件批改。

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

看完了文件结尾的一些依赖引入和函数申明等,咱们接着来看 main 主入口函数。

4 main 主流程

第 4 节,次要都是main 函数拆解剖析。

4.1 流程梳理 main 函数

const chalk = require('chalk')
const step = msg => console.log(chalk.cyan(msg))
// 后面一堆依赖引入和函数定义等
async function main(){
  // 版本校验

  // run tests before release
  step('\nRunning tests...')
  // update all package versions and inter-dependencies
  step('\nUpdating cross dependencies...')
  // build all packages with types
  step('\nBuilding all packages...')

  // generate changelog
  step('\nCommitting changes...')

  // publish packages
  step('\nPublishing packages...')

  // push to GitHub
  step('\nPushing to GitHub...')
}

main().catch(err => {console.error(err)
})

下面的 main 函数省略了很多具体函数实现。接下来咱们拆解 main 函数。

4.2 确认要公布的版本

第一段代码尽管比拟长,然而还好了解。
次要就是确认要公布的版本。

调试时,咱们看下这段的两张截图,就好了解啦。

// 依据上文 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?`
})

// false 间接返回
if (!yes) {return}

4.3 执行测试用例

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

4.4 更新所有包的版本号和外部 vue 相干依赖版本号

这一部分,就是更新根目录下package.json 的版本号和所有 packages 的版本号。

// update all package versions and inter-dependencies
step('\nUpdating cross dependencies...')
updateVersions(targetVersion)
function updateVersions(version) {
  // 1. update root package.json
  updatePackage(path.resolve(__dirname, '..'), version)
  // 2. update all packages
  packages.forEach(p => updatePackage(getPkgRoot(p), version))
}

4.4.1 updatePackage 更新包的版本号

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')
}

次要就是三种批改。

1. 本人自身 package.json 的版本号
2. packages.json 中 dependencies 中 vue 相干的依赖批改
3. packages.json 中 peerDependencies 中 vue 相干的依赖批改

一图胜千言。咱们执行yarn release --drygit diff 查看的 git 批改,局部截图如下。

4.4.2 updateDeps 更新外部 vue 相干依赖的版本号

function updateDeps(pkg, depType, version) {const deps = pkg[depType]
  if (!deps) return
  Object.keys(deps).forEach(dep => {
    if (
      dep === 'vue' ||
      (dep.startsWith('@vue') && packages.includes(dep.replace(/^@vue\//, '')))
    ) {
      console.log(chalk.yellow(`${pkg.name} -> ${depType} -> ${dep}@${version}`)
      )
      deps[dep] = version
    }
  })
}

一图胜千言。咱们在终端执行yarn release --dry。会看到这样是输入。

也就是这句代码输入的。

console.log(chalk.yellow(`${pkg.name} -> ${depType} -> ${dep}@${version}`)
)

4.5 打包编译所有包

// 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)`)
}

4.6 生成 changelog

// generate changelog
await run(`yarn`, ['changelog'])

yarn changelog 对应的脚本是conventional-changelog -p angular -i CHANGELOG.md -s

4.7 提交代码

通过更新版本号后,有文件改变,于是 git diff
是否有文件改变,如果有提交。

git add -A
git commit -m 'release: v${targetVersion}'

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.')
}

4.8 公布包

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

这段函数比拟长,能够不必细看,简略说就是 yarn publish 公布包。
咱们 yarn release --dry后,这块函数在终端输入的如下:

值得一提的是,如果是 vue 默认有个 tagnext。当 Vue 3.x 是默认时删除。

} else if (pkgName === 'vue') {
  // TODO remove when 3.x becomes default
  releaseTag = 'next'
}

也就是为什么咱们当初装置 vue3 还是 npm i vue@next命令。

async function publishPackage(pkgName, version, runIfNotDry) {
  // 如果在 跳过包里 则跳过
  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}

  // For now, all 3.x packages except "vue" can be published as
  // `latest`, whereas "vue" will be published under the "next" tag.
  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'} else if (pkgName === 'vue') {
    // TODO remove when 3.x becomes default
    releaseTag = 'next'
  }

  // TODO use inferred release channel after official 3.0 release
  // const releaseTag = semver.prerelease(version)[0] || null

  step(`Publishing ${pkgName}...`)
  try {
    await runIfNotDry(
      'yarn',
      [
        'publish',
        '--new-version',
        version,
        ...(releaseTag ? ['--tag', releaseTag] : []),
        '--access',
        'public'
      ],
      {
        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}
  }
}

4.9 推送到 github

// push to GitHub
step('\nPushing to GitHub...')
// 打 tag
await runIfNotDry('git', ['tag', `v${targetVersion}`])
// 推送 tag
await runIfNotDry('git', ['push', 'origin', `refs/tags/v${targetVersion}`])
// git push 所有改变到 近程  - github
await runIfNotDry('git', ['push'])
// yarn run release --dry

// 如果传了这个参数则输入 能够用 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()

咱们 yarn release --dry后,这块函数在终端输入的如下:

到这里咱们就拆解剖析完 main 函数了。

整个流程很清晰。

1. 确认要公布的版本
2. 执行测试用例
3. 更新所有包的版本号和外部 vue 相干依赖版本号
    3.1 updatePackage 更新包的版本号
    3.2 updateDeps 更新外部 vue 相干依赖的版本号
4. 打包编译所有包
5. 生成 changelog
6. 提交代码
7. 公布包
8. 推送到 github

用一张图总结则是:

看完 vue-next/scripts/release.js,感兴趣还能够看vue-next/scripts 文件夹下其余代码,绝对行数不多,但收益较大。

5. 总结

通过本文学习,咱们学会了这些。

1. 相熟 vuejs 公布流程
2. 学会调试 nodejs 代码
3. 入手优化公司我的项目公布流程

同时倡议本人入手用 VSCode 多调试,在终端多执行几次,多理解消化。

vuejs公布的文件很多代码咱们能够间接复制粘贴批改,优化咱们本人公布的流程。比方写小程序,绝对可能公布频繁,齐全能够应用这套代码,配合 miniprogram-ci,再加上一些自定义,加以优化。

当然也能够用开源的 release-it。

同时,咱们能够:

引入 git flow,治理 git 分支。预计很多人不晓得 windows git bash 曾经默认反对 git flow命令。

引入 husky 和 lint-staged 提交 commit 时用 ESLint 等校验代码提交是否可能通过检测。

引入 单元测试 jest,测试要害的工具函数等。

引入 conventional-changelog

引入 git-cz 交互式git commit

等等标准本人我的项目的流程。如果一个候选人,通过看 vuejs 公布的源码,积极主动优化本人我的项目。我感觉面试官会认为这个候选人比拟加分。

看开源我的项目源码的益处在于:一方面能够拓展视线,另外一方面能够为本人所用,收益绝对较高。

退出移动版