跟尤雨溪对话:我从vuejs/core发布中学到了什么?

简介: 代码规范: EditorConfig+Prettier + ESLint(除了vue3.0没有使用EditorConfig,其他三个仓库都用了)

image.png


读开源作者的源代码,就犹如跟作者一起对话一样,学到其中源码的精髓、思想,犹如大师在黑板上讲解项目的实现思路,收益匪浅,就说这么多,如果你有兴趣也一起来读源码吧。


1、找到开源项目






2、查看文件发现他们的大致共性


  • 管理依赖: pnpm(全部)


  • 实现仓库的monorepo: pnpm的workspace(全部)


  • typescript:使用ts进行编写代码(全部)


  • github yml: 工作流(全部)


  • Git Hook 工具:husky + lint-staged(element-plus和vant)


  • 代码规范: EditorConfig+Prettier + ESLint(除了vue3.0没有使用EditorConfig,其他三个仓库都用了)


  • 提交规范:Commitizen + Commitlint (element-plus)


  • 打包工具: Rollup(vue3.0和element-plus) 、esbuild(vant和vite)


  • 单元测试: vitest(element-plus和vite)、jest(vue3.0和vant)


3、接下来有时间我会学习一下以下知识


  • pnpm和monorepo下的项目库、二次封装组件库、工具库


  • 创建工程化项目


  • 代码规范: EditorConfig+Prettier + ESLint


  • 提交规范:Commitizen + Commitlint


  • Git Hook 工具:husky + lint-staged


  • github yml: 工作流


  • typescript:现在的vue3项目中零零散散的也使用了一下ts,但还不够深入,有时间继续深入学习一下


  • 打包工具: Rollup,通过Rollup开发一个组件库


  • 单元测试: vitest和jest要学习一个,来练练手,工具库的单元测试还是比较好处理,现在是组件库的单元测试要学习一下


4、本文主要重点知识


  • 上一篇博文通过google/zx写了自己公司15个项目的编译打包上传过程


  • 接下来就来看看vue3.0源码中是如何通过脚本或者自动化的手段去处理打包发布的过程


5、严格校验使用pnpm 安装依赖


  • 在项目根目录下使用yarn命令的话会有提示


yarn
    // 提示如下:
    yarn install v1.22.17
    info No lockfile found.
    $ node ./scripts/preinstall.js
    This repository requires using pnpm as the package manager  for scripts to work properly.
    error Command failed with exit code 1.
    info Visit https://yarnpkg.com/en/docs/cli/install for documentation about this command.


  • 如果使用pnpm就不会有上述提示了,不过如果确实有要求要使用yarn命令


// 会忽略相应的前置钩子 prexxxx,和后置钩子 postxxxx。
    yarn --ignore-scripts, 


  • 通过package.json中scripts脚本列表中的preinstall,做了判断处理


"preinstall": "node ./scripts/preinstall.js",
    // 查看scripts/preinstall.js文件
    if (!/pnpm/.test(process.env.npm_execpath || '')) {
        console.warn(
            `\u001b[33mThis repository requires using pnpm as the package manager ` +
            ` for scripts to work properly.\u001b[39m\n`
        )
        process.exit(1)
    }


查看vite脚手架 github.com/vitejs/vite…


// 一个命令行便可以限定只能使用pnpm
 "preinstall": "npx only-allow pnpm",


  • 其实这两者干了同样一件事情,都是只允许使用pnpm进行执行scripts


6、调试script/release.js


  • 先到package.json中找到scripts的 release


"release": "node scripts/release.js",


  • 开启调试的方式


  • 将鼠标悬浮于 release上,可以看到[运行脚本]和[调试脚本] 点击调试脚本即可调试,当然要提前设置断点
  • 或者可以看到scripts上方,会有一个调试按钮,点击选择 release即可进入调试状态


  • 开启后终端有会如下显示


pnpm run release
    Debugger attached.
    > @3.2.36 release H:\github\sourceCode\core
    > node scripts/release.js
    Debugger attached.
    ? Select release type ... 
    > patch (3.2.37)
    minor (3.3.0)
    major (4.0.0)
    custom



7、release.js中引用的依赖说明


  • 依赖minimist:解析命令行中的参数


// 安装依赖
npm i minimist
// 引入依赖
import minimist from 'minimist'
console.log(process.argv, 'process')
const argv = minimist(process.argv.slice(2))
console.log(argv, '打印参数列表')
//通过node环境直接执行 
node ./other/minimist.js -a aa -b bb -c cc
// [
// 'C:\\Program Files\\nodejs\\node.exe',
// 'H:\\github\\2022\\zx-ts\\other\\minimist.js',
// '-a',
// 'aa',
// '-b',
// 'bb',
// '-c',
// 'cc'
// ] process
// { _: [], a: 'aa', b: 'bb', c: 'cc' } 打印参数列表


可以发现其中process.argv的第一和第二个元素是Node可执行文件路径和被执行js文件的路径。


  • chalk终端多色彩输出


npm i chalk
import chalk from 'chalk'
console.log(chalk.blue('打印参数列表'))



// 举个简单的例子版本号 2.0.1
// 版本号格式: 主版本号(major).次版本号(minor).修订号(patch)
// 则 2为主版本号  0为次版本号  1为修订号
// major: 变化意味着本地变更发生了巨大的变化(当你做了不兼容的 API 修改)
// minor: 通常只反映了一些较大的更改(当你做了向下兼容的功能性新增)
// patch 通常称之为补丁版本(当你做了向下兼容的问题修正)
// 再举个简单的例子: 2.0.1-beta.1 
// 这个就相当于先行版本号
//release.js中涉及到的api
//验证版本号
console.log(semver.valid('0.0.3'), 'valid验证版本号')   // 0.0.3 ✔
console.log(semver.valid('0.0.3-beta.1'), 'valid验证版本号')   // 0.0.3-beta.1 ✔
console.log(semver.valid('0.0.3.44'),'验证版本号0.0.3.44')  // null ❌
// 获取先行版本号后的标识和版本号
console.log(semver.prerelease('0.0.3-beta.1'), 'prerelease1')  // beta  1 ✔
console.log(semver.prerelease('1.0.0-alpha+001'), 'prerelease2')  // alpha ❌
console.log(semver.prerelease('1.0.0-beta+exp.sha.5114f85'), 'prerelease3')   // beta❌
console.log(semver.prerelease('1.0.0+b11111'), 'prerelease4')  // null  错误❌
// 现有版本号为0.0.3,通过inc获取新的版本号
console.log(semver.inc(currentVersion, 'major'), 'inc-major')  // 1.0.0
console.log(semver.inc(currentVersion, 'minor'), 'inc-minor')  // 0.1.0
console.log(semver.inc(currentVersion, 'patch'), 'inc-patch')  // 0.0.4


npm i semver


  • enquirer 交互式询问CLI 简单说就是交互式询问用户输入。


npm i enquirer
import enquirer from 'enquirer'
let tempArray = ['major(1.0.0)','minor(0.1.0)', 'patch(0.0.4)', 'customer' ]
const { release } = await enquirer.prompt({
    type: 'select',
    name: 'release',
    message: 'Select release type',
    choices: tempArray
})
if(release === 'custom') {
    console.log(release, 'customer')
} else {
    const targetVersion = release.match(/\((.*)\)/)[1]
    console.log(targetVersion, 'targetVersion')
}


执行命令后可以看到四个选项 major(1.0.0) minor(0.1.0) patch(0.0.4) customer 选择不同的选项,则根据不同的选项进行判断处理不同的逻辑


  • execa 执行命令行的


import { execa } from 'execa'
import {$} from 'zx'
const arr = ['aaa', 'bbbb']
const { stdout } = await execa('echo', arr)
console.log(stdout, 'stdout')
// 这个是通过google/zx的神器调用的命令行,我自己感觉灰常好用
await $`echo -e  ${arr}  google/zx仓库`


  • 在package.json中发现 run-s查了半天没找到太多资料,原来是npm-run-all的缩写,虽然我对npm-run-all也不了解,但这个关键字的搜索信息就海量了。


  • 串行执行 clean、 lint build命令


npm-run-all clean lint build
// 同样可以使用缩写命令
run-s clean lint build


以前也可以使用 && 进行串行执行命令


"XXX": "npm run clean && npm run lint && npm run build" 


或者说是顺序执行三个命令,如果某个脚本退出时返回值为空值,那么后续脚本默认是不会执行的,不过你可以使用参数--continue-on-error 来规避这种行为。


  • 并行执行 三个命令


npm-run-all --parallel clean lint build
// 同样可以使用缩写命令
run-p clean lint build


以前也可以使用 & 进行并行执行命令


"XXX": "npm run clean & npm run lint & npm run build" 


同时执行这三个任务,需要注意如果脚本退出时返回空值,所有其它子进程都会被 SIGTERM 信号中断,同样可以用 --continue-on-error 参数禁用行为。


8、vue3.0 release整个过程


  • 1、选择要发布的版本:


  • major
  • minor
  • patch
  • custom


  • 2、执行测试用例 执行测试用例分为了两个部分


await run(bin('jest'), ['--clearCache'])
await run('pnpm', ['test', '--bail'])


  • 第一行是执行jest测试用例第二行是执行命令行中的测试用例


"test": "run-s \"test-unit {@}\" \"test-e2e {@}\"",
"test-unit": "jest --filter ./scripts/filter-unit.js",
"test-e2e": "node scripts/build.js vue -f global -d && jest --filter ./scripts/filter-e2e.js --runInBand",


顺便来看看run方法的实现


// 读取node_modules下.bin目录下传递进来的name
const bin = name => path.resolve(__dirname, '../node_modules/.bin/' + name)
// 通过execa来执行bin下的命名
const run = (bin, args, opts = {}) =>
    execa(bin, args, { stdio: 'inherit', ...opts })


  • 3、更新根目录package.json版本号


// const path = require('path') 
// 引用path模块,通过path模块读取并拼接路径、读取当前release.js文件所在路径,并返回上一级 
let pkgRoot = path.resolve(__dirname, '..'), 
// 拼接根目录package.json所在路径
const pkgPath = path.resolve(pkgRoot, 'package.json')
//const fs = require('fs') 
// 引用fs模块,通过fs模块读取package.json文件内容
// 再通过JSON.parse对读取的字符串内容进行转换,转换为JSON对象
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'))
// 将第一步选择的新版本号version赋值给JSON对象的version
pkg.version = version
//因为在根目录下的package.json中不存在dependencies和peerDependencies节点,所以一下两行代码执行了也没有什么效果
//updateDeps(pkg, 'dependencies', version)
//updateDeps(pkg, 'peerDependencies', version)
// 根目录版本号设置完毕后,重新写会package.json文件
fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + '\n')


  • 4、更新packages文件夹下内部vue相关依赖的版本号


// 读取release.js的当前路径,并返回上一级目录,再拼接packages
//读取此目录下的文件,并过滤掉以.ts结尾的文件和以'.'开头的文件
const packages = fs
.readdirSync(path.resolve(__dirname, '../packages'))
.filter(p => !p.endsWith('.ts') && !p.startsWith('.'))
//通过循环去更新packages文件夹下单独包的版本号
packages.forEach(p => updatePackage(getPkgRoot(p), version))
// 传递pkg 文件夹名称(也就是包名)然后拼接到路径后面
const getPkgRoot = pkg => path.resolve(__dirname, '../packages/' + pkg)
//然后根据路径和版本号,跟第三部修改根目录下版本号代码师一致的


  • 5、打包编译所有包 run方法上面有提到过,可以参看


await run('pnpm', ['run', 'build', '--release'])
// test generated dts files
step('\nVerifying type declarations...')
await run('pnpm', ['run', 'test-dts-only'])
复制代码
  • 6、生成changelog
await run(`pnpm`, ['run', 'changelog'])
// 实际执行的是package.json中的scripts脚本
"changelog": "conventional-changelog -p angular -i CHANGELOG.md -s",
// 可以发现实际使用的模块依赖是conventional-changelog-cli
// 看到命令行中有一个angular很奇怪,查阅发现
// 如果你的所有commit都符合Angular commit规范,那么发布新版本时,就可以通过脚本自动生成changelog。


在conventional-changelog仓库下有一个推荐,standard-version


// 安装依赖
npm install -D standard-version
// 通常使用前要先git add .,git commit -m ...,提交完成后再执行 如下命令
// 更新主版本号 1.0.0 => 2.0.0
standard-version -- --release-as major
//更新次版本号1.0.0 => 1.1.0
standard-version -- --release-as minor
// 更新修订版本号 1.0.0 =>1.0.1
standard-version -- --release-as patch


执行完上述命令时,如果提交存在feat和fix类型的话,就会自动生成CHANGELOG.md,当然你可以手动设置要更新到CHANGELOG.md中的类型。


  • 7、更新pnpm-lock.yaml


await run(`pnpm`, ['install', '--prefer-offline'])


  • 8、提交代码 通过git diff 命令来判断是否有文件变更,如果有则通过git add .  git commit -m ...进行提交


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


顺便说一下runIfNotDry函数, run命名是真正的通过execa执行命名,而dryRun只是进行console.log打印,并没有真正的执行命令。然后isDryRun 读取命令行中是否存在dry参数,存在的话则不进行执行命令


const isDryRun = args.dry
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


  • 9、将packages包发布到npmjs上 关于packages变量可以参考第四部分


// 循环对每个模块包进行yarn publish
for (const pkg of packages) {
    await publishPackage(pkg, targetVersion, runIfNotDry)
}
//单个publish 核心代码(runIfNotDry可以参考第八部分中的说明)
await runIfNotDry(
  'yarn',
  [
    'publish',
    '--new-version',
    version,
    ...(releaseTag ? ['--tag', releaseTag] : []),
    '--access',
    'public'
  ],
  {
    cwd: pkgRoot,
    stdio: 'pipe'
  }
)


在npmjs.com中搜索你可以发现,packages文件夹下的所有包都单独存在的,说明都可以单独引用,也就是在某些项目中,可以单独下载引用某些库,这里可以说是一个发现。


  • 10、将tag标签和commit进行push


await runIfNotDry('git', ['tag', `v${targetVersion}`])
await runIfNotDry('git', ['push', 'origin', `refs/tags/v${targetVersion}`])
await runIfNotDry('git', ['push'])


第一行通过git tag打tag标签


第二行对tag标签,进行推送push


第三行对commit进行推送push


9、总结


  • 了解到版本号可以自动设置
  • changelog.md可以根据commit自动生成
  • commit 规范 要搞起来,方便自动化
  • 命令行的串行执行和并行执行
  • node_modules下的.bin目录存放了各种命令工具
  • 限制某个命令的运行,通过钩子函数去限制
目录
相关文章
|
4月前
|
Rust JavaScript 前端开发
Rust! 无VDom! 尤雨溪解析 Vue.js 2024 新特性
Rust! 无VDom! 尤雨溪解析 Vue.js 2024 新特性
|
4月前
|
JavaScript 前端开发 API
尤雨溪分享 Vue.js 10 年的发展历程,谈谈我看完后的启发和感受!!
尤雨溪分享 Vue.js 10 年的发展历程,谈谈我看完后的启发和感受!!
|
6月前
|
JavaScript Java 测试技术
基于ssm+vue.js+uniapp小程序的景海中学教学管理系统附带文章和源代码部署视频讲解等
基于ssm+vue.js+uniapp小程序的景海中学教学管理系统附带文章和源代码部署视频讲解等
41 5
|
5月前
|
JavaScript Java 测试技术
基于springboot+vue.js+uniapp小程序的槐荫中学教学管理系统附带文章源码部署视频讲解等
基于springboot+vue.js+uniapp小程序的槐荫中学教学管理系统附带文章源码部署视频讲解等
24 0
|
7月前
|
JavaScript Java 测试技术
基于ssm+vue.js的中学课内小说阅读与学习系统附带文章和源代码设计说明文档ppt
基于ssm+vue.js的中学课内小说阅读与学习系统附带文章和源代码设计说明文档ppt
35 1
|
6月前
|
JavaScript Java 测试技术
基于ssm+vue.js+uniapp小程序的职业高中学情成绩系统附带文章和源代码部署视频讲解等
基于ssm+vue.js+uniapp小程序的职业高中学情成绩系统附带文章和源代码部署视频讲解等
32 0
|
7月前
|
JavaScript 前端开发 编译器
【VueConf 2022】尤雨溪:Vue的进化历程
【VueConf 2022】尤雨溪:Vue的进化历程
104 0
|
Web App开发 JavaScript IDE
尤雨溪官宣:Vue 3 将于 2022 年 2 月 7 日成为新的默认版本!
尤雨溪官宣:Vue 3 将于 2022 年 2 月 7 日成为新的默认版本!
453 0
尤雨溪官宣:Vue 3 将于 2022 年 2 月 7 日成为新的默认版本!
|
编解码 移动开发 JavaScript
Vite 与 Vue Cli 对比 - 尤雨溪: Vite 会取代 vue-cli 吗?
尤雨溪在发布 Vite 之后写了一条推,感觉不会在用 webpack 了(Vue-cli 基于 Webpack 开发,并配置了 Webpack 打包规则),Sean 是 Webpack 核心开发者,会一些中文,在尤大这条下面直接感叹。Vue-cli 一直是 Vue 2 默认官方脚手架工具,Vue-cli 基于 Webpack 开发。Vue 3 发布后,尤大同时发布了 Vite ,那么 Vue 3 同时有两个前端打包工具 Vite 和 Vue CLI,那么他们俩应该怎么选呢?
3455 0
|
JavaScript 安全 前端开发
Vue.js 创始人尤雨溪回应“Vue涉及国家安全漏洞”相关传闻
Vue.js 创始人尤雨溪回应“Vue涉及国家安全漏洞”相关传闻
1364 0
Vue.js 创始人尤雨溪回应“Vue涉及国家安全漏洞”相关传闻