Vue3源码解析(V3.2.45)-介绍及构建流程搭建
说明:本章内容为博主在原教程基础上添加自己的学习笔记,来源珠峰架构Vue3课程 (opens new window),版权归原作者所有。
# Vue3介绍及构建流程搭建
# 区别介绍
- 源码采用monorepo方式进行管理,将模块拆分到packages目录中
- vue3采用ts开发,增强类型检查,vue2则采用flow
- vue3的性能优化,支持tree-shaking,打包时会剔除未被使用的代码
- vue3劫持数据采用es6 Proxy,支持数组;vue2采用defineProperty,而defineProperty有性能问题和缺陷,不支持数组,需单独处理
- vue3中对模板编译进行了优化,编译时生成了Block tree,可以对子节点的动态节点进行收集,可以减少比较,并且采用了patchFlag标记动态节点
- vue3使用组合式api,vue2使用配置式api,用户提供的data,props,computed,methods,watch等属性,用户编写复杂的逻辑时会在这些属性间频繁横跳
- vue2所有的属性都通过this指向,复杂逻辑会造成指向不明确问题
- vue2使用mixins实现组件间的逻辑共享,但会有数据来源不明确,命名冲突等问题,vue3使用composition api提供公共逻辑非常方便
- vue2后期引入RFC,使每个版本改动可控rfcs
- vue3新增了Fragment,Teleport,Suspense组件
monorepo优点
- monorepo是项目管理的一个方式,在一个项目仓库中管理多个包/模块,将代码拆分到各个模块package中
- 一个仓库维护多个模块,不用到处找仓库
- 方便版本管理和依赖管理,模块之间的调用和引用都非常方便
- 模块扩展非常方便
tree-shaking介绍
- tree-shaking是一种消除死代码的性能优化理论,最初由rollup提出概念,目前支持的工具有:rollup,webpack2,closure complier等
- tree-shaking实现原理
- tree-shaking的本质是消除无用的js代码,同时它也是DCE(Dead Code Elimination)的一种新的实现
- 与传统语言不同的,JavaScript加载由于受网络影响使得DCE具有更重要的意义,毕竟JS文件越小加载时间越短
- 具体实现DCE的并不是之前提到的三个工具,而是代码压缩优化工具uglify
- tree-shaking是基于es6的模块特性,这也是该工具之前不能流行的原因
uglify特征
- 函数消除:rollup与webpack2都可以实现,rollup相对效率更高
- 类消除:rollup与webpack2都无法实现
- rollup只处理函数和顶层的import/export变量,不能把没用到的类的方法消除掉
- uglify不能跨文件消除代码
tree-shaking副作用
- side effects是指那些当import的时候会执行一些动作,但是不一定会有任何export,比如ployfill,ployfills不对外暴露方法给主程序使用
- tree-shaking不能自动的识别哪些代码属于side effects,因此手动指定这些代码显得非常重要,如果不指定可能会出现一些意想不到的问题
# 架构分析
- 采用monorepo管理项目
- 项目结构
- reactivity响应式系统
- runtime-core与平台无关的运行时核心(可以借助其他运行时创建针对特定平台的运行时)
- runtime-dom针对浏览器的运行时,包括dom api 、属性处理、事件处理等
- runtime-test用于测试
- server-renderer用于服务端渲染
- compiler-core与平台无关的编译器核心
- compiler-dom针对浏览器的编译器
- compiler-ssr针对服务端渲染的编译器模块
- compiler-sfc针对单文件解析的编译器模块
- size-check用来测试代码体积
- template-explorer用于调试编译器输出的开发工具
- shared多个包之间共享的内容
- vue完整版包括编译器和运行时

# 环境搭建
node version: v16.17.1
- Step 0 - 创建
test-vue3-src
项目根目录 - Step 1 - 根目录下执行
yarn init -y
命令初始化项目 - Step 2 - 根目录下创建
packages
目录 - Step 3 - 创建如下目录并执行命令
yarn init -y
初始化- packages/reactivity
- packages/shared
- packages/runtime-core
- packages/runtime-dom
- 其他包名
- Step 4 - 在每个
/packages/*/src/
目录下新建index.ts
文件 - Step 5 - 在根目录下执行命令
npx tsc --init
生成tsconfig.json
文件
{
"name": "@vue/reactivity",//将所有的包都统一放置在@vue下
"version": "1.0.0",
"main": "index.js",//commonjs入口
"module": "dist/reactivity.esm-bundler.js",//webpack入口
"unpkg": "dist/reactivity.global.js",//window入口
"license": "MIT",
"buildOptions": {//自定义配置项
"name": "VueReactivity",//global全局模块的名字,如果不打包global可以不起名字
"formats": [//当前包(模块)可以构建成的格式有哪些
"esm-bundler",//es6模块,es6用法 <script type='modual'>这样可以使用es6语法</script>
"cjs",//commonjs模块,node平台下使用
"global"//全局模块,浏览器直接<srcrip src='VueReactivity.global.js'>这样就直接引入了js中的东西</script>引入
]
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
{
"name": "@vue/shared",
"version": "1.0.0",
"main": "index.js",
"module": "dist/shared.esm-bundler.js",
"license": "MIT",
"buildOptions": {
"name": "VueShared",//global全局模块的名字,如果不打包global可以不起名字
"formats": [
"esm-bundler",
"cjs"
]
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
- Step 6 - 在根目录下执行命令
yarn add typescript@^4.8.0 rollup@^3.2.3 rollup-plugin-typescript2@^0.34.1 @rollup/plugin-node-resolve@^15.0.1 @rollup/plugin-json@^5.0.1 @rollup/plugin-commonjs@^23.0.2 execa@^4.0.2 minimist@^1.2.0 -D -W
安装所需依赖和工具
注意:-W参数
意思是这些依赖是给当前项目的根目录安装的,不是给下面的某个包安装
依赖工具的用途
rollup
打包工具@rollup/plugin-node-resolve
rollup和第三方模块的桥梁@rollup/plugin-json
rollup和json的桥梁execa
开启子进程方便执行命令@rollup-plugin-typescript2
rollup和typescript的桥梁@rollup/plugin-commonjs
rollup和commonjs模块的桥梁minimist
轻量级的命令行参数解析引擎
- Step 7 - 在根目录下创建
scripts
目录并创建dev.js
文件和build.js
文件
{
"private": true,//私有的
"workspaces": [//模块都在packages下统一管理
"packages/*"
],
"name": "test-vue3-src",
"version": "1.0.0",
"description": "vue3源码",
"main": "index.js",
"author": "ylx",
"license": "MIT",
"scripts": {
"dev": "node scripts/dev.js",//开发环境
"build": "node scripts/build.js"//生产环境
},
"devDependencies": {
"@rollup/plugin-commonjs": "^23.0.2",
"@rollup/plugin-json": "^5.0.1",
"@rollup/plugin-node-resolve": "^15.0.1",
"execa": "^4.0.2",
"minimist": "^1.2.0",
"rollup": "^3.2.3",
"rollup-plugin-typescript2": "^0.34.1",
"typescript": "^4.8.0"
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
- Step 8 - 实现
scripts/build.js
功能
//对packages目录下的所有模块都进行打包
const fs = require('fs')//文件操作
const execa = require('execa')//开启子进程,最终还是使用rollup打包
const targets = fs.readdirSync('packages').filter((f) => {
if (!fs.statSync(`packages/${f}`).isDirectory()) {
return false
}
return true
})//不加filter就是在根目录下的packages中找到所有的包目录和文件,filter可以过滤掉不是目录的文件
console.info(`找到的所有包名:${targets}`)
//找到目标后,我们需要依次并行打包之
//打包函数,使用rollup进行打包
async function build(target) {
console.info(`需要打包的目标有:${target}`)
// rollup -c --environment "TARGET":xxxx
await execa("rollup", ["-c", "--environment", `TARGET:${target}`], { stdio: "inherit" })//stdio:"inherit"子进程打包的信息共享给父进程
}
//并行打包函数
function runParallel(targets, iteratorFn) {//iteratorFn是迭代函数
let res = []
for (const iterator of targets) {
const p = iteratorFn(iterator)//每个打包的过程都是一个promise
res.push(p)
}
return Promise.all(res)//返回所有异步打包对象
}
//并行打包所有目标,没打包一个模块执行一次build
runParallel(targets, build)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
{
"compilerOptions": {
/* Visit https://aka.ms/tsconfig to read more about this file */
/* Projects */
// "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */
// "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */
// "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */
// "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */
// "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */
// "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
/* Language and Environment */
"target": "ESNEXT", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
// "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
// "jsx": "preserve", /* Specify what JSX code is generated. */
// "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */
// "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
// "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */
// "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
// "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */
// "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */
// "noLib": true, /* Disable including any library files, including the default lib.d.ts. */
// "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */
// "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */
/* Modules */
"module": "ESNEXT", /* Specify what module code is generated. */
// "rootDir": "./", /* Specify the root folder within your source files. */
// "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */
// "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
// "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
// "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
// "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */
// "types": [], /* Specify type package names to be included without being referenced in a source file. */
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
// "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */
// "resolveJsonModule": true, /* Enable importing .json files. */
// "noResolve": true, /* Disallow 'import's, 'require's or '<reference>'s from expanding the number of files TypeScript should add to a project. */
/* JavaScript Support */
// "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */
// "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */
// "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */
/* Emit */
// "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
// "declarationMap": true, /* Create sourcemaps for d.ts files. */
// "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
"sourceMap": true, /* Create source map files for emitted JavaScript files. */
// "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */
// "outDir": "./", /* Specify an output folder for all emitted files. */
// "removeComments": true, /* Disable emitting comments. */
// "noEmit": true, /* Disable emitting files from a compilation. */
// "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
// "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */
// "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
// "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
// "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
// "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */
// "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
// "newLine": "crlf", /* Set the newline character for emitting files. */
// "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */
// "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */
// "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */
// "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */
// "declarationDir": "./", /* Specify the output directory for generated declaration files. */
// "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */
/* Interop Constraints */
// "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */
// "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */
"esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */
// "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
"forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */
/* Type Checking */
"strict": false, /* Enable all strict type-checking options. */
// "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */
// "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */
// "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
// "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */
// "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */
// "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */
// "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */
// "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
// "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */
// "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */
// "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
// "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */
// "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */
// "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */
// "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */
// "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */
// "allowUnusedLabels": true, /* Disable error reporting for unused labels. */
// "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */
/* Completeness */
// "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
"skipLibCheck": true /* Skip type checking all .d.ts files. */
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
- Step 9 - 实现
scripts/dev.js
功能
//只针对某个具体的模块打包
const fs = require('fs')//文件操作
const execa = require('execa')//开启子进程,最终还是使用rollup打包
// const targets = fs.readdirSync('packages').filter((f) => {
// if (!fs.statSync(`packages/${f}`).isDirectory()) {
// return false
// }
// return true
// })//不加filter就是在根目录下的packages中找到所有的包目录和文件,filter可以过滤掉不是目录的文件
// console.info(`找到的所有包名-------->`, targets)
const target = 'reactivity'//打包哪个模块就写哪个模块的名称
//找到目标后,我们需要依次并行打包之
//打包函数,使用rollup进行打包
async function build(target) {
console.info(`需要打包的目标有--------->`, target)
// rollup -cw --environment "TARGET":xxxx (TARGET这个名字可以随便起,rollup.config.js里引用时对应上即可) -cw可以一直监视文件是否有变化,有变化则自动打包
await execa("rollup", ["-cw", "--environment", `TARGET:${target}`], { stdio: "inherit" })//stdio:"inherit"子进程打包的信息共享给父进程
}
build(target)
//并行打包函数
// function runParallel(targets, iteratorFn) {//iteratorFn是迭代函数
// let res = []
// for (const iterator of targets) {
// const p = iteratorFn(iterator)//每个打包的过程都是一个promise
// res.push(p)
// }
// return Promise.all(res)//返回所有异步打包对象
// }
// //并行打包所有目标,没打包一个模块执行一次build
// runParallel(targets, build)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
- Step 10 - 根目录下创建
rollup.config.mjs
文件并实现功能
//import execa from 'execa'
const fs = require('fs')//加载node模块
const execa = require('execa')
// 1 获取模块目录
//const dirs = fs.readdirSync('packages'); //使用fs核心模块读取packages下所有目录,包括文件
//console.info(dirs);
const dirs = fs.readdirSync('packages').filter(f => {
if (!fs.statSync(`packages/${f}`).isDirectory()) {
return false
}
return true
}); //使用fs核心模块读取packages下所有目录,包括文件,使用filter方法过滤掉不是目录的文件名
console.info(dirs);
// 2 并行打包
async function build(target) {
console.info(target, '<--build-->');
//rollup表示打包工具 -c表示执行rollup的配置 --environment环境变量
await execa('rollup', ['-c', '--environment', `TARGET:${target}`], { stdio: 'inherit' });
}
async function runParaller(dirs, itemfn) {
let result = [];
for (let item of dirs) {
result.push(itemfn(item));
}
return Promise.all(result);//存放打包的promise
}
runParaller(dirs, build).then(() => {
console.info("打包成功");
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
# 包之间相互引用
安装包
- 执行yarn install 就可以在根目录下的node_modules中生成@vue/xxxx每个包的软链(快捷方式)
- 在其他类中使用 import * from '@vue/xxxx',即可引入相应的包中的函数、对象、变量
依赖包安装
- yarn workspace @vue/reactivity add @vue/shared
编辑 (opens new window)
上次更新: 2025/02/15, 13:42:25