Vue3源码I-环境搭建

Vue3 I

Vue3设计思想

  • 响应式系统的重写 Vue 3 重新设计了响应式系统,采用了 Proxy API 代替了 Vue 2 中的 Object.defineProperty。这样可以提供更好的性能,并且支持更多的响应式特性。
  • 更好的性能 Vue 3 引入了一些性能优化措施,例如编译器的优化、虚拟 DOM 的改进以及更高效的更新机制。这些改进使得 Vue 3 在性能方面比 Vue 2 更出色。
  • Composition API Vue 3 引入了 Composition API,它是一种基于函数的 API 风格,可以更好地组织和重用组件逻辑。Composition API 提供了更灵活的组合方式,使得代码更具可读性和可维护性。
  • 更好的 TypeScript 支持 Vue 3 对 TypeScript 的支持更加完善,提供了更准确的类型推断和类型检查。这使得在使用 TypeScript 开发 Vue 应用时更加方便和可靠。
  • 更小的包体积 Vue 3 在设计时考虑了包体积的问题,并采取了一些措施来减小包的大小。这使得 Vue 3 在加载和运行时更加高效。

声明式框架

Vue3依旧是声明式的框架,用起来简单

命令式和声明式的区别

  • jq时代编写的代码都是命令式的,命令式框架重要特点就是关注过程
  • 声明式框架更加关注结果。命令式的代码封装到了Vuejs中,过程靠vuejs来实现

声明式代码更加简单,不需要关注实现,按照要求填代码就可以(给上原材料就出结果)

1
2
3
4
5
6
7
8
9
10
11
// 命令式编程
let numbers = [1,2,3,4,5]
let total = 0
for(let i = 0 ; i < numbers.length; i++){
total += numbers[i] // 关注过程
}

// 声明式编程
let total2 = number.reduce(function (memo,current){
return memo + current
},0)

采用虚拟DOM

传统更新页面,拼接一个完整的字符串innerHTML全部重新渲染,添加虚拟DOM后,可以比较新旧虚拟节点,找到变化在进行更新。虚拟DOM就是一个对象,用来描述真实DOM的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const vnode = {
__v_isVNode: true,
__v_skip: true,
type,
props,
key: props && normalizeKey(props),
ref: props && normalizeRef(props),
children,
component: null,
el: null,
patchFlag,
dynamicProps,
dynamicChildren: null,
appContext: null
}

区分编译时和运行时

  • 我们需要有一个虚拟DOM,调用渲染方法将虚拟DOM渲染成真实DOM (缺点就是虚拟DOM编写麻烦)
  • 专门写个编译时可以将模板编译成虚拟DOM (在构建的时候进行编译性能更高,不需要再运行的时候进行编译,而且vue3在编译中做了很多优化)

Vue3整体架构

monorepo-管理项目

Monorepo 是管理项目代码的一个方式,指在一个项目仓库(repo)中管理多个模块/包(package)。 Vue3源码采用 monorepo 方式进行管理,将模块拆分到package目录中。

  • 一个仓库可维护多个模块,不用到处找仓库
  • 方便版本管理和依赖管理,模块之间的引用,调用都非常方便

Vue3项目结构

Vue3采用Typescript

Vue2 采用Flow来进行类型检测 (Vue2中对TS支持并不友好), Vue3源码采用Typescript来进行重写 , 对Ts的支持更加友好。

Vue3 开发环境搭建

搭建Monorepo环境

Vue3中使用pnpm workspace来实现monorepo (pnpm是快速、节省磁盘空间的包管理器。主要采用符号链接的方式管理模块)

全局安装pnpm

1
2
3
npm install pnpm -g

pnpm init

如果不做发布可在初始化的package.json中添加 “private” : true

1
2
3
4
5
"name": "vue3",
"private": true, // 表示是一个基座,不做发布
"version": "1.0.0",
"description": "",
// ...

pnpm搭建monorepo环境

只需在前项目下创建pnpm-workspace.yaml

将packages下所有的目录都作为包进行管理。这样我们的Monorepo就搭建好了

1
2
packages:
- "packages/*"
根目录下创建所有需要管理的包的总目录packages
  • 创建响应式包packages/reactivity, pnpm init初始化, 修改package.json中name为@vue/reactivity
1
2
3
4
5
{
"name": "@vue/reactivity",
"version": "1.0.0",
//...
}
  • 创建共享方法包packages/shared, pnpm init初始化, 修改package.json中name为@vue/shared
1
2
3
4
5
{
"name": "@vue/shared",
"version": "1.0.0",
//...
}
安装共享模块
1
pnpm install vue -w

这时候发现共享的模块不在node_modules根目录下; 添加羞耻的提升可以将Vue3,所依赖的模块提升到node_modules中

羞耻的提升前

  • 新建.npmrc
1
shamefully-hoist = true
  • 重新执行pnpm install

羞耻的提升后

搭建编译环境

新建文件

  • 在 reactivity 新建src/index.ts
1
2
3
import { isObject } from  "@vue/shared"  // 如何这样引入

export { isObject }
  • 在 shared 新建src/index.ts
1
2
3
export const isObject = (value) => {
return value !== null && typeof value === "object"
}

在 reactivity 中 如何通过 @vue/shared 引入shared包中的方法

  • npm install typescript -g

  • tsc —init 生成 tsconfig.json

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
{
"compilerOptions": {
"outDir": "dist", // 输出目录
"sourceMap": true, // 采用sourcemap
"target": "es2016", // 目标语法
"module": "ESNext", // 模块格式
"moduleResolution": "node", // 模块解析方式
"strict": false, // 严格模式
"resolveJsonModule": true, // 解析json模块
"esModuleInterop": true, // 允许通过es6语法引入commonjs模块
"jsx":"preserve", // jsx 不转义
"lib": ["ESNext","DOM"], // 支持的类库 esnext及dom

"baseUrl": "./", // 以当前项目为根目录
"paths": {
"@vue/*":["packages/*/src"] // 模块之间先找自己定义模块
}
}
}

这是就能正确的指向我们自己的包了

配置打包

  • 根目录新建 scripts/dev.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
const { build } = require('esbuild')
const {resolve} = require('path')

const target = 'reactivity'

build({
entryPoints: [resolve(__dirname,`../packages/${target}/src/index.ts`)],
outfile: resolve(__dirname,`../packages/${target}/dist/${target}.js`),
bundle: true, // 将依赖的模块全部打包
sourcemap: true, // 支持调试
format: 'esm', // 打包出来的模块是es6模块
platform: 'browser', // 打包的结果给浏览器来使用
watch:{
onRebuild(error,result){
if (error) console.error('watch build failed:', error)
else console.log('watch build succeeded:', result)
}
}
}).then(()=>{
console.log('watching~~~')
})

开发环境使用 esbuild 版本为 0.15.17,新版本 为 0.19.2 配置已经发生了变化, 具体可看esbuild官方文档

  • 根目录package.json配置dev
1
2
3
"scripts": {
"dev": "node scripts/dev.js"
},
  • reactivity/dist/index.html 引入打包文件,在浏览器打开看看时候能执行
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script type="module">
import { isObject } from './reactivity.js'
console.log('---',isObject({a:1}))
</script>
</body>
</html>