[Vue源码系列-1]vue3源码架构分析

1,302 阅读3分钟

1. vue2和vue3的区别

  • vue3的源码采用monorepo管理方式,实现了从模块管理包管理的转变
  • vue2采用的是Flow来做静态类型检测,而vue3使用typescript重构代码,增强类型检测。
  • vue2的方法都放在实例对象上,而vue3中都是函数形式,所以vue3支持tree-shaking,不使用就不会被打包
  • vue2的数据劫持是通过defineProperty,而这也是vue2最大的性能问题,所以vue3中使用Proxy实现数据劫持
  • vue3对模板编译进行了优化,编译时生成Block tree可以收集动态节点,减少比较
  • vue3采用compositionApi进行组织功能,优化复用逻辑,相较于optionApi类型推断更加便捷
  • 增加了 Fragment,TeleportSuspense组件

2. Vue架构分析

2.1 Monorepo

Monorepo是管理代码的一种方式,它是指在一个项目仓库(repo)下管理多个包

  • 一个仓库中维护多个包
  • 便于版本管理、依赖管理,模块间的引用,调用都非常的方便
  • 缺点就是仓库的体积会变大

2.2 Vue3的项目结构

  • 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:完整版本,包括运行时和编译器

3. 基于rollup搭建vue3环境

3.1 安装依赖

  • typescript:支持typescript
  • rollup:打包工具
  • rollup-plugin-typescript2:rollup 和 ts的 桥梁
  • @rollup/plugin-node-resolve:解析node第三方模块
  • @rollup/plugin-json:支持引入json
  • execa:开启子进程方便执行命令
npm install typescript rollup rollup-plugin-typescript2 @rollup/plugin-node-resolve @rollup/plugin-json execa -D

3.2 workspace配置

初始化配置文件

nmp iniy -y		// 初始化 package.json 文件
npx tsc --init	// 初始化 ts 配置文件

在根目录的package.json文件中配置workspace

{	// 在package.json 中添加以下字段
  "private":true,
  "workspaces":[
    "packages/*"
  ],
  // ...
}

目录结构,以及包配置

  • 创建packages文件夹用于管理包模块,文件夹下就是vue的各个包模块了

  • 创建reactivity文件夹,其中src中是核心代码,package.json则是管理当前包的配置文件

{
  "name": "@vue/reactivity",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "module": "dist/reactivity.esm-bundler.js",
  "author": "",
  "license": "ISC",
  "buildOptions":{	// 自定义配置,rollup打包时使用
    "name":"VueReactivity",
    "formats":[
      "esm-bundler",
      "cjs",
      "global"
    ]
  }
}

创建软链yarn install

3.3 配置scripts脚本

在根目录的packages.json中配置scripts字段

{
	"scripts": {
    	"dev":"node script/dev.js",			// 打包单个包
    	"build":"node script/build.js"		// 打包所有包
  	}
}

根目录下,创建scripts文件夹,用于区分打包环境

> script
  - build.js
  - dev.js

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;
})

// 对我们目标进行一次打包,并行打包
async function build(target){
    // rollup -c --environment TARGET:reactivity
    await execa('rollup',['-c','--environment',`TARGET:${target}`],{stdio:'inherit'});  // 将子进程打包的信息共享给父进程
}

function runParallel(targets,iteratorFn){
    const res = [];
    for(const item of targets){
        const p = iteratorFn(item);
        res.push(p);
    }
    return Promise.all(res);
}

runParallel(targets,build)

dev.js

// 只针对具体的某个包打包
const fs = require('fs');
const execa = require('execa'); // 开启子进程 进行打包, 最终还是使用rollup来进行打包

const target = 'reactivity'		// 后期可根据传入的target做打包

async function build(target){ 
    await execa('rollup',['-cw','--environment',`TARGET:${target}`],{stdio:'inherit'}); // 当子进程打包的信息共享给父进程
}
build(target)

3.4 rollup配置

rollup.config.js

import path from 'path';
import ts from 'rollup-plugin-typescript2'
import json from '@rollup/plugin-json'
import resolvePlugin from '@rollup/plugin-node-resolve'

// 根据环境变量中的target属性 获取对应模块中的 pakcage.json
const packagesDir = path.resolve(__dirname, 'packages'); 
// packageDir 打包的基准目录
const packageDir = path.resolve(packagesDir, process.env.TARGET); // 获取要打包的目标目录
const name = path.basename(packageDir); // 获取打包的名字
const resolve = p => path.resolve(packageDir, p);	// 永远针对的是某个模块
const pkg = require(resolve(`package.json`)) // 获取目标对应的package.json

const outputConfigs = {
    'esm-bundler': {
        file: resolve(`dist/${name}.esm-bundler.js`), // webpack打包用的
        format: `es`
    },
    'cjs': {
        file: resolve(`dist/${name}.cjs.js`), // node使用的
        format: 'cjs'
    },
    'global': {
        file: resolve(`dist/${name}.global.js`), // 全局的
        format: 'iife'
    }
}

const packageOptions = pkg.buildOptions; // 打包的选项
function createConfig(format, output) {
    output.name = packageOptions.name;
    output.sourcemap = true;
    return {
        input: resolve(`src/index.ts`), // 入口
        output,
        plugins:[
            json(),
            ts({
                tsconfig:path.resolve(__dirname,'tsconfig.json')
            }),
            resolvePlugin(),
        ]
    }
}
// rollup 最终需要到出配置
export default packageOptions.formats.map(format => createConfig(format, outputConfigs[format]));

自此就搭建好了vue3最基本的环境