current position:Home>Vue 3.2 was released. How did you Yuxi release vue.js?

Vue 3.2 was released. How did you Yuxi release vue.js?

2021-08-27 02:37:06 Ruokawa

1. Preface

Hello everyone , I am a If Chuan . Welcome to follow me official account : Ruokawa vision , Recently organized Source code read together Activities , Interested can add me wechat ruochuan12, Long term exchange and study .

Previously written 《 Learn the whole source architecture series 》 contain jQueryunderscorelodashvuexsentryaxiosreduxkoavue-devtoolsvuex4 Ten source articles .

Writing relatively difficult source code , Spent his time and energy , I didn't get much reading praise , It's actually a very hard thing . In terms of reading volume and reader benefit , Can't promote the author to continue to output articles .

So change your mind , Write articles that are relatively easy to understand . In fact, the source code is not as difficult as you think , At least a lot can be understood .

Recently, youyuxi released 3.2 edition . The smaller version is already 3.2.4 了 . This article is to learn how Youda releases vuejs Of , Learn the source code for your own use .

This article deals with vue-next/scripts/release.js file , The number of lines of code in the whole file is only 200 The rest of the line , But it's worth learning .

Goethe once said : Read a good book , It's talking to noble people . The same can be : Read source code , It can also be regarded as a way of learning and communication with the author .

Read this article , You will learn :

1.  be familiar with  vuejs  Publishing process 
2.  Learn to debug  nodejs  Code 
3.  Start to optimize the company's project release process 

Before environmental preparation , Let's preview vuejs The release process of .

vue  Publishing process

2. Environmental preparation

open vue-next,
Open source projects are generally available in README.md perhaps .github/contributing.md Find the contribution guide .

The contribution guide contains a lot of information about participating in project development . Like how to run , What is the project directory structure . How to put into development , What knowledge reserves are needed .

You need to make sure Node.js The version is 10+, and yarn The version is 1.x Yarn 1.x.

You installed Node.js The version is probably lower than 10. The easiest way is to go to the official website and reinstall . You can also use nvm Etc Node.js edition .

node -v
# v14.16.0
#  Global installation  yarn
#  Cloning project 
git clone https://github.com/vuejs/vue-next.git
cd vue-next

#  Or clone my project 
git clone https://github.com/lxchuan12/vue-next-analysis.git
cd vue-next-analysis/vue-next

#  install  yarn
npm install --global yarn
#  Installation dependency 
yarn # install the dependencies of the project
# yarn release

2.1 Strictly verify the use of yarn Installation dependency

Then let's take a look vue-next/package.json file .

// vue-next/package.json
{
    "private": true,
    "version": "3.2.4",
    "workspaces": [
        "packages/*"
    ],
    "scripts": {
        // --dry  I added the parameters , If you are debugging   The code also suggests adding 
        //  Do not perform testing and compilation  、 Don't execute   push git Wait for the operation 
        //  That is to say, running , It's just printing , I'll talk about it in detail later 
        "release": "node scripts/release.js --dry",
        "preinstall": "node ./scripts/checkYarn.js",
    }
}

If you try to use npm Installation dependency , It should be a mistake . Why do you report mistakes .
because package.json There's a front preinstall node ./scripts/checkYarn.js Judge that the mandatory requirement is to use yarn install .

scripts/checkYarn.js The documents are as follows , That is to say process.env Execution path found in environment variable npm_execpath, If not yarn Just output the warning , And the process ends .

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

If you want to ignore this leading hook judgment , have access to yarn --ignore-scripts command . There are also rear hooks post. For more details, you can view npm file

2.2 debugging vue-next/scripts/release.js file

Then let's learn how to debug vue-next/scripts/release.js file .

Here's my statement VSCode edition yes 1.59.0 should 1.50.0 From now on, you can debug according to the following steps .

code -v
# 1.59.0

find vue-next/package.json file open , And then in scripts upper , There will be debug( debugging ) Button , After clicking , choice release. Enter debugging mode .

debugger

At this time, the terminal will be as shown in the figure below , Yes Debugger attached. Output . Now put a picture .

terminal

more nodejs Debugging related You can view the official documentation

After learning to debug , Go through the process first , Make more breakpoints in key places and walk more times , You can guess the intention of the source code .

3 Some dependencies and function declarations at the beginning of the file

We can follow the breakpoint , First look at some dependency introductions and function declarations at the beginning of the file

3.1 The first part

// vue-next/scripts/release.js
const args = require('minimist')(process.argv.slice(2))
//  File module 
const fs = require('fs')
//  route 
const path = require('path')
//  Console 
const chalk = require('chalk')
const semver = require('semver')
const currentVersion = require('../package.json').version
const { prompt } = require('enquirer')

//  Execute subprocess commands     In short   Is to execute... On the terminal command line   command 
const execa = require('execa')

By relying on , We can do it in node_modules Find the dependency of the corresponding installation . You can also find its README and github Warehouse .

3.1.1 minimist Command line parameter parsing

minimist

In short , This library , Is to parse the command line parameters . Look at examples , It is easier for us to understand the interpretation and analysis results .

$ 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))

among process.argv The first and second elements of are Node Executable files and executable files JavaScript The fully qualified file system path of the file , Whether you enter them like this or not .

3.1.2 chalk Terminal multi-color output

chalk

In short , This is used for terminal display multi-color output .

3.1.3 semver Semantic version

semver

Semantic version of nodejs Realization , Used for version Verification and comparison . See this for the semantic version Semantic version 2.0.0 file

Version format : The major version number . Sub version number . Revision number , The rules for increasing version number are as follows :

The major version number : When you do incompatible API modify ,

Sub version number : When you do a downward compatible feature add ,

Revision number : When you do a downward compatibility problem fix .

The previous version number and version compilation information can be added to “ The major version number . Sub version number . Revision number ” Behind , As an extension .

3.1.4 enquirer Interactive inquiry CLI

Simply put, it means asking the user for input interactively .

enquirer

3.1.5 execa Carry out orders

Simply put, it's the of executing commands , Similar to entering commands at the terminal ourselves , such as echo If Chuan .

execa

//  Example 
const execa = require('execa');

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

After the first part , Then let's look at the second part .

3.2 The second part

// vue-next/scripts/release.js

//  Corresponding  yarn run release --preid=beta
// beta
const preId =
  args.preid ||
  (semver.prerelease(currentVersion) && semver.prerelease(currentVersion)[0])
//  Corresponding  yarn run release --dry
// true
const isDryRun = args.dry
//  Corresponding  yarn run release --skipTests
// true  Skip the test 
const skipTests = args.skipTests
//  Corresponding  yarn run release --skipBuild 
// true
const skipBuild = args.skipBuild

//  Read  packages  Folder , To filter out   No  .ts file   ending   And not  .  Folders at the beginning 
const packages = fs
  .readdirSync(path.resolve(__dirname, '../packages'))
  .filter(p => !p.endsWith('.ts') && !p.startsWith('.'))

The second part is relatively simple , Go on to part three .

3.3 The third part

// vue-next/scripts/release.js

//  Skipped package 
const skippedPackages = []

//  The version is incremented 
const versionIncrements = [
  'patch',
  'minor',
  'major',
  ...(preId ? ['prepatch', 'preminor', 'premajor', 'prerelease'] : [])
]

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

This one may not be well understood .inc Is to generate a version . More to see semver file

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

3.4 The fourth part

The fourth part declares some execution script functions, etc

// vue-next/scripts/release.js

//  obtain  bin  command 
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

//  Get the path of the package 
const getPkgRoot = pkg => path.resolve(__dirname, '../packages/' + pkg)

//  Console output 
const step = msg => console.log(chalk.cyan(msg))

3.4.1 bin function

obtain node_modules/.bin/ Commands in the directory , The whole file was used once .

bin('jest')

Equivalent to at the command terminal , Project root function ./node_modules/.bin/jest command .

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 Real running commands at the terminal , such as yarn build --release

dryRun You don't run , It's just console.log(); Print 'yarn build --release'

runIfNotDry If it's not an empty run, execute the command .isDryRun Parameters are entered through the console .yarn run release --dry This is the true.runIfNotDry Just print , Do not execute orders . The advantage of this design is , Yes, sometimes you don't want to submit directly , First look at the result of executing the command . Have to say , You can play .

stay main At the end of the function , You can also see a similar prompt . It can be used git diff Let's look at the file modification first .

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

After reading some dependency introductions and function declarations at the beginning of the file , Let's go on to see main Main entry function .

4 main Main process

The first 4 section , Mainly main Function disassembly analysis .

4.1 Process carding main function

const chalk = require('chalk')
const step = msg => console.log(chalk.cyan(msg))
//  The previous batch of dependency introduction and function definition 
async function main(){
  //  Version verification 

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

above main Function omits many specific function implementations . Next, let's disassemble main function .

4.2 Confirm the version to be released

The first code is long , But I understand .
The main thing is to confirm the version to be released .

During debugging , Let's look at two screenshots of this paragraph , Just understand .

 Select the version number for terminal output

 Enter the confirmation version number at the terminal

//  According to the above  mini  This code means  yarn run release 3.2.4 
//  Get the parameter  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'])
  })

//  Select from definition 
  if (release === 'custom') {
    targetVersion = (
      await prompt({
        type: 'input',
        name: 'version',
        message: 'Input custom version',
        initial: currentVersion
      })
    ).version
  } else {
    //  Take the version number in parentheses 
    targetVersion = release.match(/\((.*)\)/)[1]
  }
}

//  check   Whether the version conforms to   standard 
if (!semver.valid(targetVersion)) {
  throw new Error(`invalid target version: ${targetVersion}`)
}

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

// false  Go straight back to 
if (!yes) {
  return
}

4.3 Execute test case

// 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 Update the version number and internal of all packages vue Dependent version number

This part , Is to update the root directory package.json Version number and all packages Version number of .

// 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 Update package version number

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

There are three main modifications .

1.  Myself  package.json  Version number of 
2. packages.json  in  dependencies  in  vue  Related dependency modifications 
3. packages.json  in  peerDependencies  in  vue  Related dependency modifications 

A picture is worth a thousand words . We execute yarn release --dry after git diff Viewed git modify , Some screenshots are as follows .

 Example of updated version number

4.4.2 updateDeps Update the interior vue The version number of the dependent

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

A picture is worth a thousand words . We execute at the terminal yarn release --dry. You will see that this is the output .

 to update  Vue  Dependent terminal output

That is, the output of this code .

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

4.5 Package and compile all packages

// 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 Generate changelog

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

yarn changelog The corresponding script is conventional-changelog -p angular -i CHANGELOG.md -s.

4.7 Submission code

After updating the version number , There are file changes , therefore git diff.
Whether there are any file changes , If so, submit .

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 Release package

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

This function is long , You don't have to look at , In short, it is yarn publish Release package .
We yarn release --dry after , The output of this function at the terminal is as follows :

 Issue terminal output command

It is worth mentioning that , If it is vue By default, there is a tag by next. When Vue 3.x Delete by default .

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

That's why we now install vue3 still npm i [email protected] command .

async function publishPackage(pkgName, version, runIfNotDry) {
  //  If in   Skip the bag   Then skip 
  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 Pushed to the github

// push to GitHub
step('\nPushing to GitHub...')
//  hit  tag
await runIfNotDry('git', ['tag', `v${targetVersion}`])
//  push  tag
await runIfNotDry('git', ['push', 'origin', `refs/tags/v${targetVersion}`])
// git push  All changes to   long-range   - github
await runIfNotDry('git', ['push'])
// yarn run release --dry

//  If this parameter is passed, output   It can be used  git diff  Look at the changes 

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

//  If   Skipped package , The following packages are not published . But the code  `skippedPackages`  There is no bag in the .
//  So this code will not execute .
//  We are used to writing  arr.length !== 0  Actually  0  Namely  false . Don't write .
if (skippedPackages.length) {
  console.log(
    chalk.yellow(
      `The following packages are skipped and NOT published:\n- ${skippedPackages.join(
        '\n- '
      )}`
    )
  )
}
console.log()

We yarn release --dry after , The output of this function at the terminal is as follows :

 Publish to github

Here we'll finish the disassembly and Analysis main Function .

The whole process is clear .

1.  Confirm the version to be released 
2.  Execute test case 
3.  Update the version number and internal of all packages  vue  Dependent version number 
    3.1 updatePackage  Update package version number 
    3.2 updateDeps  Update the interior  vue  The version number of the dependent 
4.  Package and compile all packages 
5.  Generate  changelog
6.  Submission code 
7.  Release package 
8.  Pushed to the  github

To sum up with a picture is :

vue  Publishing process

After watching vue-next/scripts/release.js, Interested can also see vue-next/scripts Other codes under folder , Relatively few rows , But the benefits are greater .

5. summary

Learn from this article , We learned this .

1.  be familiar with  vuejs  Publishing process 
2.  Learn to debug  nodejs  Code 
3.  Start to optimize the company's project release process 

At the same time, it is recommended to use it yourself VSCode Multi debugging , Execute several more times at the terminal , Understand and digest more .

vuejs There are many codes in the released files. We can directly copy, paste and modify , Optimize our own release process . For example, write small programs , Relatively likely to be published frequently , You can use this code , coordination miniprogram-ci, Plus some customization , To optimize .

Of course, you can also use open source release-it.

meanwhile , We can :

introduce git flow, management git Branch . I guess a lot of people don't know windows git bash Has default support for git flow command .

introduce husky and lint-staged Submit commit Time use ESLint Check whether the code submission can pass the detection .

introduce unit testing jest, Test key tools, functions, etc .

introduce conventional-changelog

introduce git-cz Interactive git commit.

And so on, standardize the process of their own projects . If a candidate , By looking at vuejs Published source code , Proactively optimize your projects . I think the interviewer will think this candidate is better .

The advantage of looking at the source code of open source projects is : On the one hand, it can expand our horizons , On the other hand, you can use it for yourself , The benefits are relatively high .

copyright notice
author[Ruokawa],Please bring the original link to reprint, thank you.
https://en.qdmana.com/2021/08/20210827023700657j.html

Random recommended