一、vue-relase是什么?
vue-release即vue-next/scripts/release.js,主要执行vue版本发布前的一些操作,包括确认版本、执行测试用例、更新依赖、构建、提交git、发布包和推送Github等。
二、前置
1、克隆代码
git clone https://github.com/vuejs/vue-next
2、安装依赖、构建代码
$ npm install --global yarn
$ yarn install
$ yarn build
三、vscode调试
1、js打断点
2、开启调试
打开package.json,然后搜索scripts,在它的上方有个“调试”(或是Debug)按钮,点击它,然后选择scripts里需要调试的命令,此处需要调试的是release命令。
{
"scripts": {
"dev": "bide scripts/dev.js",
"build": "node scripts/build.js",
"release": "node scripts/release.js"
......
}
......
}
3、控制台输出
Running tests
Updating cross dependencies
@vue/compiler-core -> dependencies -> @vue/shared@3.2.7
@vue/compiler-dom -> dependencies -> @vue/shared@3.2.7
@vue/compiler-dom -> dependencies -> @vue/compiler-core@3.2.7
@vue/compiler-sfc -> dependencies -> @vue/compiler-core@3.2.7
@vue/compiler-sfc -> dependencies -> @vue/compiler-dom@3.2.7
@vue/compiler-sfc -> dependencies -> @vue/compiler-ssr@3.2.7
@vue/compiler-sfc -> dependencies -> @vue/ref-transform@3.2.7
@vue/compiler-sfc -> dependencies -> @vue/shared@3.2.7
@vue/compiler-ssr -> dependencies -> @vue/shared@3.2.7
@vue/compiler-ssr -> dependencies -> @vue/compiler-dom@3.2.7
@vue/reactivity -> dependencies -> @vue/shared@3.2.7
@vue/ref-transform -> dependencies -> @vue/compiler-core@3.2.7
@vue/ref-transform -> dependencies -> @vue/shared@3.2.7
@vue/runtime-core -> dependencies -> @vue/shared@3.2.7
@vue/runtime-core -> dependencies -> @vue/reactivity@3.2.7
@vue/runtime-dom -> dependencies -> @vue/shared@3.2.7
@vue/runtime-dom -> dependencies -> @vue/runtime-core@3.2.7
@vue/runtime-test -> dependencies -> @vue/shared@3.2.7
@vue/runtime-test -> dependencies -> @vue/runtime-core@3.2.7
@vue/server-renderer -> dependencies -> @vue/shared@3.2.7
@vue/server-renderer -> dependencies -> @vue/compiler-ssr@3.2.7
@vue/server-renderer -> peerDependencies -> vue@3.2.7
vue -> dependencies -> @vue/shared@3.2.7
vue -> dependencies -> @vue/compiler-dom@3.2.7
vue -> dependencies -> @vue/runtime-dom@3.2.7
@vue/compat -> peerDependencies -> vue@3.2.7
Building all packages
Committing changes
Publishing packages
Publishing compiler-core
Publishing compiler-dom
Publishing compiler-sfc
Publishing compiler-ssr
Publishing reactivity
Publishing ref-transform
Publishing runtime-core
Publishing runtime-dom
Publishing server-renderer
Publishing shared
Publishing vue
Publishing vue-compat
Pushing to GitHub
四、源码阅读
1、main方法
main方法是release.js中的入口方法。通过调用js中的其他方法,完成以下操作。
async function main() {
......
}
main().catch(err => {
console.error(err);
})
(1)确认目标版本
第一步,判断targetVersion是否存在,如果没有值,则提示选择,并增加一个自定义的选项。
let targetVersion = [];
if (!targetVersion) {
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]
}
}
第二步,使用semver.valid校验版本的合法性。
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)执行测试用例
const bin = name => path.resolve(__dirname, '../node_modules/.bin/' + name);
const run = (bin, args, opts = {}) =>
execa(bin, args, { stdio: 'inherit', ...opts});
if (!skipTests && !isDryRun) {
await run(bin('jest'), ['--clearCache']);
await run('yarn', ['test', '--bail']);
} else {
console.log(`(skipped)`);
}
(3)更新依赖
updateVersion(targetVersion);
(4)构建
if (!skipBuild && !isDryRun) {
await run('yarn', ['build', '--release']);
await run('yarn', ['test-dts-only']);
} else {
console.log(`(skipped)`);
}
(5)提交git
await run(`yarn`, ['changelog']);
const { stdout } = await run('git', ['diff'], { stdio: 'pipe' });
if (stdout) {
await runIfNotDry('git', ['add', '-A']);
await runIfNotDry('git', ['commit', '-m', `release:v${targetVersion}`]);
} else {
console.log('No changes to commit');
}
(6)发布包
for (const pkg of packages) {
await publishPackage(pkg, targetVersion, runIfNotDry);
}
(7)推送到Github
await runIfNotDry('git', `v${targetVersion}`);
await runIfNotDry('git', ['push', 'origin', `refs/tags/v${targetVersion}`]);
await runIfNotDry('git', ['push']);
2、updateVersion方法
作用: main方法中调用,传入目标版本,用于更新package.json中的版本。
第一步,调用updatePackage方法,更新package.json;
updatePackage(path.resolve(__dirname, '..'), version);
注意: path.resolve(__dirname, '..')传入的是根目录。
第二步,遍历所有packages,更新它们的package.json。
const fs = require('fs')
const packages = fs
.readdirSync(path.resolve(__dirname), '../packages')
.filter(p => !p.endsWith('.ts') && !p.startsWith('.'))
.....
packages.forEach(p => updatePackage(getPkRoot(p), version));
注意: 此处获取packages用到了fs.readdirSync方法。
3、updatePackage方法
作用: updateVersion方法中调用,传入路径pkgRoot和版本version,用于更新包。
第一步,获取package的路径。
const pkgPath = path.resolve(pkgRoot, 'package.json');
第二步,使用fs.readFileSync方法读取package中的内容,然后更新pkg的version值。
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
pkg.version = version;
第三步,调用updateDeps方法更新依赖。
updateDeps(pkg, 'dependencies', version);
updateDeps(pkg, 'peerDependencies', version);
第四步,使用fs.writeFileSync方法将更新后的内容写入文件。
fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + '\n');
4、updateDeps方法
作用: updatePackage方法中调用,传入包pkg、依赖类型depType和版本version,用于更新依赖。
第一步,获取依赖。
const deps = pkg[depType];
// 如果没有依赖,则直接返回
if (!deps) return;
第二步,遍历依赖,判断依赖满足以下条件时更新依赖的版本号。
(1)是vue;
(2)以@vue开头;
(3)包含@vue;
// 遍历依赖
Object.keys(deps).forEach(dep => {
if (dep === 'vue' ||
(dep.startWith('@vue') && packages.includes(dep.replace(/^@vue\//, '')))
) {
console.log(
chalk.yellow(`${pkg.name} -> ${depType} -> ${dep}@${version}`)
deps[dep] = version;
)
}
})
5、publishPackage方法
作用: 在main方法中调用,传入包pkgName,版本version,是否执行更新操作runIfNotDry,用于发布包。
第一步,判断是否是需要忽略更新的包。
if (skippedPackage.includes(pkgName)) {
return;
}
第二步,获取包的信息。
const pkgRoot = getPkRoot(pkgName);
const pkgPath = path.resolve(pkgRoot, 'package.json');
const pkg = JSON.parse(fs.readFileSync(pkgPathm 'utf-8'));
// 判断包是否是私有的
if (pkg.private) {
return;
}
第三步,判断包的releaseTag。
(1)如果传入的tag参数有值,则releaseTag取tag的值;
(2)如果传入的版本号中带有alpha,则releaseTag的值为alpha;
(3)如果传入的版本号中带有beta,则releaseTag的值为beta;
(4)如果传入的版本号中带有rc,则releaseTag的值为rc;
(4)如果包名为vue,则releaseTag的值为next;
let releaseTag = null;
if (arg.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') {
releaseTag = 'next';
}
第四步,根据传入的runIfNotDry,执行更新操作或只做控制台输出。
// 获取是否执行更新操作的标识
const idDryRun = args.dry;
// 执行更新操作
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;
async function publishPackage(pkgName, version, runIfNotDry) {
......
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;
}
}
}
五、常用类库
1、args
作用: 用于解析参数
const args = require('minimist')(process.argv.slice(2))
2、fs
作用: 用于文件读写
const fs = require('fs')
// 示例
const packages = fs.readdirSync(path.resolve(__dirname), '../packages');
fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2));
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
3、path
作用: 用于获取路径
const path = require('path')
4、chalk
作用: 用于控制台高亮输出
const chalk = require('chalk')
// 示例
chalk.blue(`[dryRun] ${bin} ${args.join(' ')}`), opts)
chalk.cyan(msg);
chalk.yellow(
`The following packages ar skipped and NOT published:\n- ${skippedPackages.join('\n- ')}`
)
chalk.green(`Successfully published ${pkgName}@${version}`);
chalk.red(`Skipping already published:${pkgName}`);
5、semver
作用: 用于版本号处理
const semver = require('semver')
// 示例
// 解析版本号
semver.prerelease('1.2.3-alpha.1') -> ['alpha', 1]
// 版本号加1
semver.inc('1.2.3', 'prerelease', 'beta') -> '1.2.4-beta.0'
// 校验版本号
semver.valid(targetVersion);
6、enquirer
作用: 用于询问确认
const { prompt } = require('enquirer')
// 示例
// 选择
prompt({
type: 'select',
name: 'release',
message: 'Select release type',
choices: versionIncrements.map(i =>
`${i} (${inc(i)})`
).concat(['custom'])
})
// 输入
prompt({
type: 'input',
name: 'version',
message: 'Input custom version',
initial: currentVersion
})
// 确认
prompt({
type: 'confirm',
name: 'yes',
message: `Releasing v${targetVersion}. Confirm?`
})
7、execa
作用: 用于执行脚本命令
const execa = require('execa')
六、收获
(1)熟悉了vue-release的大致发布流程;
(2)在release.js中支持传入不同的参数,对可能出现的几种情况都进行了处理,包括是否执行更新操作,是否跳过测试,是否跳过构建等等。
(3)在release.js中用到了ES6语法中的async和await,使代码更加简洁易懂。