最近尤雨溪发布了3.2版本。小版本已经是3.2.4了。本文来学习下尤大是怎么发布vuejs的,学习源码为自己所用。
参考
打开 vue-next, 开源项目一般都能在 README.md 或者 .github/contributing.md 找到贡献指南。
只允许使用yarn安装依赖
npm生命周期 ,原来有这么多,大开眼界,感谢川哥
preinstall:install 前要执行的周期命令
//package.json
{
"private": true,
"version": "3.2.4",
"workspaces": [
"packages/*"
],
"scripts": {
// --dry 参数是我加的,如果你是调试 代码也建议加
// 不执行测试和编译 、不执行 推送git等操作
// 也就是说空跑,只是打印,后文再详细讲述
"release": "node scripts/release.js --dry",
"preinstall": "node ./scripts/checkYarn.js",
}
}
// 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)
}
进入调试
找到 vue-next/package.json 文件打开,然后在 scripts 上方,会有debug(调试)按钮,点击后,选择 release。
进入 scripts/release.js
认识发布所需的依赖
minimist:解析命令行参数的
chalk:这个是用于终端显示多色彩输出。
semver:语义化版本
版本格式:主版本号.次版本号.修订号,版本号递增规则如下: 主版本号:当你做了不兼容的 API 修改, 次版本号:当你做了向下兼容的功能性新增, 修订号:当你做了向下兼容的问题修正。 先行版本号及版本编译信息可以加到“主版本号.次版本号.修订号”的后面,作为延伸。
enquirer:交互式询问cli
execa:执行终端命令,在第一期用的是 childProcess去执行
代码解析
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 { prompt } = require('enquirer')
const execa = require('execa')
const currentVersion = require('../package.json').version
// 对应 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('.'))
// 跳过的包
const skippedPackages = []
// 版本递增
const versionIncrements = [
'patch',
'minor',
'major',
...(preId ? ['prepatch', 'preminor', 'premajor', 'prerelease'] : [])
]
const inc = i => semver.inc(currentVersion, i, preId)
// semver.inc('3.2.4', 'prerelease', 'beta') inc是生成一个版本
// 3.2.5-beta.0
// 获取 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)
//是否 --dry
const runIfNotDry = isDryRun ? dryRun : run
// 获取包的路径
const getPkgRoot = pkg => path.resolve(__dirname, '../packages/' + pkg)
// 控制台输出
const step = msg => console.log(chalk.cyan(msg))
async function main() {
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}`)
}
//确认发布
const { yes } = await prompt({
type: 'confirm',
name: 'yes',
message: `Releasing v${targetVersion}. Confirm?`
})
if (!yes) {
return
}
//跑测试例子 bin('jest')拿到命令,run去执行
// run tests before release
step('\nRunning tests...')
if (!skipTests && !isDryRun) {
await run(bin('jest'), ['--clearCache'])
await run('yarn', ['test', '--bail'])
} else {
console.log(`(skipped)`)
}
//更新依赖
// update all package versions and inter-dependencies
step('\nUpdating cross dependencies...')
updateVersions(targetVersion)
//打包,有空再看看build.js
// 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
// generate changelog
await run(`yarn`, ['changelog'])
//diff判断有无文件改动,有就提交
const { stdout } = await run('git', ['diff'], { stdio: 'pipe' })
if (stdout) {
step('\nCommitting changes...')
//还可以这么玩呢,我感觉这里可以挖到自己项目里, 然后script写个push,npm run push description就给我提交~ 不过提升不大哎
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,打tab,推tag,push
// push to GitHub
step('\nPushing to GitHub...')
await runIfNotDry('git', ['tag', `v${targetVersion}`])
await runIfNotDry('git', ['push', 'origin', `refs/tags/v${targetVersion}`])
await runIfNotDry('git', ['push'])
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- '
)}`
)
)
}
console.log()
}
function updateVersions(version) {
// 1. update root package.json 传的应该是父目录vue/next
updatePackage(path.resolve(__dirname, '..'), version)
// 2. update all packages
packages.forEach(p => updatePackage(getPkgRoot(p), version))
}
//更新package.json里的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')
}
//这边的 `packages` 指的是 packages/* 下面的包
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
}
})
}
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 (alpha/beta phase), every package except "vue" can be published as
// `latest`, whereas "vue" will be published under the "next" tag.
const releaseTag = args.tag || (pkgName === 'vue' ? 'next' : null)
// 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
}
}
}
main().catch(err => {
console.error(err)
})
总结流程
1. 确认要发布的版本
2. 执行测试用例
3. 更新依赖版本
3.1 updatePackage 更新包 package.json的version
3.2 updateDeps 更新依赖版本号
4. 打包编译所有包
5. 生成 changelog
6. 提交代码 add,commit
7. 发布包 publish
8. 推送到 github tag,push tag,push
其他川话
vuejs发布的文件很多代码我们可以直接复制粘贴修改,优化我们自己发布的流程。比如写小程序,相对可能发布频繁,完全可以使用这套代码,配合miniprogram-ci,再加上一些自定义,加以优化。
当然也可以用开源的release-it。
同时,我们可以:
引入git flow,管理git分支。估计很多人不知道windows git bash已经默认支持 git flow命令。
引入husky和lint-staged 提交commit时用ESLint等校验代码提交是否能够通过检测。
引入 单元测试 jest,测试关键的工具函数等。
引入 git-cz 交互式git commit。
等等规范自己项目的流程。如果一个候选人,通过看vuejs发布的源码,积极主动优化自己项目。我觉得面试官会认为这个候选人比较加分。
关于调试
如何调试代码看这篇,动手练习可以学会,Node.js 也类似。 mp.weixin.qq.com/s/VOoDHqIo4…
慕课网调试课程:www.imooc.com/learn/1164
掘金chrome免费小册:juejin.cn/book/684473…
慕课网 nodejs调试入门 www.imooc.com/learn/1093
vscode nodejs 调试 code.visualstudio.com/docs/nodejs…
vscode还有很多调试文档,都推荐看看