最新公告
  • 欢迎您光临网站无忧模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • vue3_release流程源码保姆级解读 - 掘金

    正文概述 掘金(惜时自珍)   2021-11-11   799

    前言

    保姆级对于vue3的版本发布流程解读。 详细解答都在每一行代码的注释里面,主要看注释。 学完后可以搭建自己的release流程了(再也不是手动挡了)。 文档较长,建议分次阅读并自己建一个走一遍。

    准备

    1. 准备node-10+环境以及yarn1.x
    2. 克隆vue3_release学习仓库代码项目
    3. cd vue3_release
    4. yarn 安装依赖
    5. yarn release就可以控制台体验999等级的输出了

    先上效果图: vue3_release流程源码保姆级解读 - 掘金

    阅读

    整个文件分为两个部分阅读:

    1. 依赖以及辅助函数
    2. main进程

    依赖以及辅助函数

    args(获取命令行参数)

    const args = require('minimist')(process.argv.slice(2))
    // 命令行参数
    // 例子:
    // process.argv:
    // 第一个是node路径
    // 第二是执行脚步路径
    // 后面是参数,所以去除两个路径
    // node release.js -name zhou
    // [
    //   3:'zhou'
    //   2:'-name'
    //   1:'/Users/zhouguang/Desktop/learn/vue/源码阅读/release/release.js'
    //   0:'/usr/local/bin/node'
    // ]
    
    // args: minimist转换后的参数对象
    // { _: [], name: 'zhou' }
    

    依赖包

    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');
    // 子进程,在终端执行命令
    // 例子:
    // (async () => {
    //   const {stdout} = await execa('echo', ['zhou'])
    //   console.log(stdout) // zhou
    // })();
    

    preId(版本后缀类型)

    const preId = args.preid || (semver.prerelease(currentVersion) && semver.prerelease(currentVersion)[0])
    // 获取preid
    // 命令行参数的发布版本的后缀,或者是当前版本的后缀1.0.0-alpha.x => alpha
    

    isDryRun(是否只log不执行,简称空运行)

    const isDryRun = args.dry
    // 命令行参数控制是否空运行,打开方式:node release.js --dry
    // 本项目package.json中release命令默认携带
    

    skipTests(跳过测试)

    const skipTests = args.skipTests
    // 跳过测试,打开方式:node release.js --skipTests
    

    skipBuild(跳过构建)

    const skipBuild = args.skipBuild
    // 跳过构建,打开方式:node release.js --skipBuild
    

    packages(子包名称数组集合)

    获取在scripts同层packages文件夹下子包名称的数组集合

    const packages = fs
      .readdirSync(path.resolve(__dirname, '../packages'))
      .filter(p => !p.endsWith('.ts') && !p.startsWith('.'))
    // 获取package下非ts结尾并且不是.开头的文件或者文件夹名称
    

    skippedPackages(需要跳过的包)

    const skippedPackages = []
    // 跳过的包
    

    versionIncrements(版本升级类型数组)

    const versionIncrements = [
      'patch',
      'minor',
      'major',
      ...(preId ? ['prepatch', 'preminor', 'premajor', 'prerelease'] : [])
    ]
    // 版本号类型数组,有preId则把后缀类型加入
    

    inc(生成版本号函数)

    const inc = i => semver.inc(currentVersion, i, preId)
    // 生成版本号函数
    // 例子
    // semver.inc('1.0.0', 'patch', 'beta') => 1.0.1
    // semver.inc('1.0.0', 'minor', 'beta') => 1.1.0
    // semver.inc('1.0.0', 'major', 'beta') => 2.0.0
    // pre就是增加后缀
    // semver.inc('1.0.0', 'major', 'beta') => 2.0.0-beta.0
    // prerelease增加后缀的版本号
    // semver.inc('1.0.0-beta.0', 'prerelease', 'beta') => 1.0.0-beta.1
    

    bin(生成命令执行函数)

    const bin = name => path.resolve(__dirname, '../node_modules/.bin/' + name)
    // 生成命令执行函数
    // 利用node_modules下的bin,相当于bin(webapck) => node_modules/.bin/webpack
    

    run(终端执行命令运行函数)

    const run = (bin, args, opts = {}) =>
      execa(bin, args, { stdio: 'inherit', ...opts })
    // 终端执行命令运行函数,对execa进行二次封装
    

    dryRun(空运行函数)

    const bin = name => path.resolve(__dirname, '../node_modules/.bin/' + name)
    // 空运行函数,使用chalk进行打印要执行的命令
    

    runIfNotDry(执行判断函数)

    const bin = name => path.resolve(__dirname, '../node_modules/.bin/' + name)
    // 执行判断函数,利用外部isDryRun变量判断要进行哪个执行
    

    getPkgRoot(获取单个包的路径函数)

    const getPkgRoot = pkg => path.resolve(__dirname, '../packages/' + pkg)
    // 获取单个包的路径函数
    

    step(终端console)

    const step = msg => console.log(chalk.cyan(msg))
    // 终端彩色console,默认蓝色哦
    

    main进程

    直接跳到main函数进行阅读即可,main相关操作函数放置在main前面(个人习惯,哈哈),vue源码是放在main后面。

    1. 生成版本号

    取出命令行内的版本号

      let targetVersion = args._[0]
      // 取出命令行内的版本号,例如node release 3.1.2 
      // args._一个包含未指定参数内容组成的数组 
    

    交互生成版本号

    if (!targetVersion) {
      // 未指定版本号
    
        const { release } = await prompt({
          type: 'select',
          name: 'release',
          message: 'Select release type',
          choices: versionIncrements.map(i => `${i} (${inc(i)})`).concat(['custom'])
        })
        // 命令交互选择版本类型 例子:minor (1.0.1)
        
        if (release === 'custom') {
          // 自定义输入版本号
          targetVersion = (
            await prompt({
              type: 'input',
              name: 'version',
              message: 'Input custom version',
              initial: currentVersion
            })
          ).version
        } else {
          targetVersion = release.match(/\((.*)\)/)[1]
          // 取出release中版本号 例子:minor (1.0.1) => 1.0.1
        }
      } 
    

    校验版本号是否合法

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

    交互确认用户是否要发布这个版本

      const { yes } = await prompt({
        type: 'confirm',
        name: 'yes',
        message: `Releasing v${targetVersion}. Confirm?`
      })
    
      // 不是直接退出
      if (!yes) {
        return
      }
    

    2. jest执行测试

      step('\nRunning tests...')
      // 终端输出正在运行测试
    
      if (!skipTests && !isDryRun) {
        // 非跳过测试以及非空运行才运行测试
        await run(bin('jest'), ['--clearCache'])
        await run('yarn', ['test', '--bail'])
      } else {
        console.log(`(skipped)`)
      }
    

    3. 更新vue以及所有相关包的版本

      step('\nUpdating cross dependencies...')
      // 终端输出更新所有包版本中
    
      updateVersions(targetVersion)
      // 执行更新函数
    

    updateVersions(更新相关包版本)

    当前项目中packages中建立了package1和2两个示例, 它们的版本号会一起变更

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

    updatePackage(更新单个包)

    作用是更新三个地方的version

    • package.json的version
    • dependencies内和vue相关包的版本号
    • peerDependencies内和vue相关包的版本号
    // 更新package
    function updatePackage(pkgRoot, version) {
    
      // 获取路径下的package.json
      const pkgPath = path.resolve(pkgRoot, 'package.json')
    
      // 读取package.json并转换json对象
      const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'))
    
      // 更新version
      pkg.version = version
    
      // 更新dependencies内和vue相关包的版本号
      // dependencies:作为依赖安装的 npm 软件包的列表
      updateDeps(pkg, 'dependencies', version)
    
      // 更新peerDependencies内和vue相关包的版本号
      // peerDependencies:[对等依赖](https://nodejs.org/en/blog/npm/peer-dependencies/),当前包使用必须的其它依赖包
      updateDeps(pkg, 'peerDependencies', version)
    
      // 新数据写入package.json
      // JSON.stringify(json对象, 可选:序列化格式函数, 可选:缩进空白字符个数)
      fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + '\n')
    }
    

    updateDeps(更新查找到的vue相关包的版本号)

    function updateDeps(pkg, depType, version) {
      // 获取依赖数组
      const deps = pkg[depType]
      // 没有则返回
      if (!deps) return
      // 循环对比依赖是否符合以下两个条件,符合则更新版本
      // 1。=== vue
      // 2.@vue开头,并且剔除@vue/后的内容在packages内
      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
        }
      })
    }
    

    chalk打印出所有更新相关包的对应依赖的版本

    vue3_release流程源码保姆级解读 - 掘金

    4. 构建所有包

      step('\nBuilding all packages...')
      // 终端输出构建所有包中
    
      if (!skipBuild && !isDryRun) {
        // 非跳过测试以及非空运行才运行构建
    
        await run('yarn', ['build', '--release'])
        // 运行构建
    
        step('\nVerifying type declarations...')
        // 终端输出校验TS类型声明
        await run('yarn', ['test-dts-only'])
        // 执行校验TS类型声明
      } else {
        console.log(`(skipped)`)
      }
    

    5. 生成changelog说明md文件

    借助conventional-changelog-cli将git commit history中符合angular规范的整合输出changelog.md文件

      // 执行conventional-changelog -p angular -i CHANGELOG.md -s
      // 依赖包:conventional-changelog-cli
      // 按照angular规范生成changelog文件,即版本下列出fix,feat,revert的更新
      await run(`yarn`, ['changelog'])
    

    6. git提交commit

      const { stdout } = await run('git', ['diff'], { stdio: 'pipe' })
      // 命令行执行git diff,检查是否有更改
    
      if (stdout) {
        // 如果有改动
    
        step('\nCommitting changes...')
        // 终端输出git commit中
    
        await runIfNotDry('git', ['add', '-A'])
        // 提交到git暂存区
    
        await runIfNotDry('git', ['commit', '-m', `release: v${targetVersion}`])
        // git commit release: v1.0.1,生成版本commit信息
      } else {
        console.log('No changes to commit.')
      }
    

    7. yarn发布包

      step('\nPublishing packages...')
      // 终端输出包发布中
    
      // 循环packages中的包并发布
      for (const pkg of packages) {
        await publishPackage(pkg, targetVersion, runIfNotDry)
      }
    

    publishPackage(发布包

    async function publishPackage(pkgName, version, runIfNotDry) {
    
      // 如果包含在跳过包名单里,则跳过
      if (skippedPackages.includes(pkgName)) {
        return
      }
    
      // 根据包名组合成包的文件夹路径
      const pkgRoot = getPkgRoot(pkgName)
    
      // 包路径+package.json组成完整路径
      const pkgPath = path.resolve(pkgRoot, 'package.json')
    
      // fs读取包下面的package.json内容并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.
      // 上面的意思是vue3.x 发布为 next标签,对应安装vue3时是 npm i vue@next
    
      // 发布的标签
      let releaseTag = null
      if (args.tag) {
        // 命令行如果有,则用命令行的,例子:node release --tag alpha
        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
        // 当vue3变成默认版本时,则去除next标签,安装就变成 npm i vue
        releaseTag = 'next'
      }
    
      // TODO use inferred release channel after official 3.0 release
      // const releaseTag = semver.prerelease(version)[0] || null
      // 后面计划用semvers生成tag
    
      step(`Publishing ${pkgName}...`)
      // 终端输出 发布xxx包中
    
      // yarn发布包
      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
        }
      }
    }
    

    8. 打标签并推送远程仓库

      step('\nPushing to GitHub...')
      // 终端输出推送到github中
    
      await runIfNotDry('git', ['tag', `v${targetVersion}`])
      // git tag v1.0.0 => git打标签
    
      await runIfNotDry('git', ['push', 'origin', `refs/tags/v${targetVersion}`])
      // git push origin refs/tags/v1.0.0 => git推送标签到远程仓库
    
      await runIfNotDry('git', ['push'])
      // git push => git提交
    

    9. 结束log

      // 如果是空运行,则打印空运行结束,可以使用git diff查看包的变化
      if (isDryRun) {
        console.log(`\nDry run finished - run git diff to see package changes.`)
      }
    
      // 如果有跳过包名单,则终端黄色打印跳过包名单
      if (skippedPackages.length) {
        console.log(
          chalk.yellow(
            `The following packages are skipped and NOT published:\n- ${skippedPackages.join(
              '\n- '
            )}`
          )
        )
      }
    

    执行main

    // 执行main
    main().catch((err) => console.error(err))
    

    总结

    1. 辅助函数内包装依赖包API使用,并且语义化,非常利于写业务进程时辅助使用,用起来。栗子:
    const run = (bin, args, opts = {}) =>
      execa(bin, args, { stdio: 'inherit', ...opts })
    // 终端执行命令运行函数,对execa进行二次封装
    
    const dryRun = (bin, args, opts = {}) =>
      console.log(chalk.blue(`[dryrun] ${bin} ${args.join(' ')}`), opts)
    // 空运行函数,使用chalk进行打印要执行的命令
    
    const runIfNotDry = isDryRun ? dryRun : run
    // 执行判断函数,利用外部isDryRun变量判断要进行哪个执行
    
    1. package.json中peerDependencies(对等依赖)了解,官方文档
    2. 终端交互式发布流程,真香。

    参考


    下载网 » vue3_release流程源码保姆级解读 - 掘金

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元