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完整版包括编译器和运行时
**vue3架构图**

# 环境搭建

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
reactivity包package.json
  {
      "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
shared包package.json
  • 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-resolverollup和第三方模块的桥梁
  • @rollup/plugin-jsonrollup和json的桥梁
  • execa开启子进程方便执行命令
  • @rollup-plugin-typescript2rollup和typescript的桥梁
  • @rollup/plugin-commonjsrollup和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
此时的根目录下包package.json
  • 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
build.js代码
  {
    "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
此时的tsconfig.json配置
  • 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
dev.js打包代码
  • 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
rollup.config.mjs代码

# 包之间相互引用

安装包

  • 执行yarn install 就可以在根目录下的node_modules中生成@vue/xxxx每个包的软链(快捷方式)
  • 在其他类中使用 import * from '@vue/xxxx',即可引入相应的包中的函数、对象、变量

依赖包安装

  • yarn workspace @vue/reactivity add @vue/shared
上次更新: 2025/02/15, 13:42:25
最近更新
01
Git问题集合
01-29
02
安装 Nginx 服务器
01-25
03
安装 Docker 容器
01-25
更多文章>
×
×