Vue3源码解读之首次渲染DOM树 - Vue3 整体架构

本文介绍了Vue3的架构和开发环境搭建

1.Vue3 架构介绍

1.1 Monorepo 管理项目

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

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

1.2 Vue3 项目结构

模块拆分说明:

  • 编译时模块(compiler-core、compiler-dom、compiler-sfc、compiler-ssr
  • 运行时模块(runtime-core、runtime-dom
  • 响应式系统(reactivity-transform、reactivity)
  • vue2兼容(vue-compat
  • 共享模块(shared
  • 服务端渲染(server-renderer
  • 模版编译(template-explorer
  • Vue的运行时和编译器(vue)

1.3 Vue3 采用 Typescript 作为类型系统

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

2. Vue3 开发环境搭建

2.1 搭建 Monorepo 环境

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

2.1.1 全局安装 pnpm

bash 复制代码
npm install pnpm -g # 全局安装pnpm
bash 复制代码
pnpm init -y # 初始化配置文件

2.1.2 创建.npmrc文件

ini 复制代码
shamefully-hoist = true

这里您可以尝试一下安装Vue3, pnpm install vue@next此时默认情况下vue3中依赖的模块不会被提升到node_modules下。 添加shamefully-hoist 可以将 Vue3,所依赖的模块提升到node_modules

2.1.3 配置 workspace

新建 pnpm-workspace.yaml

yaml 复制代码
packages:
  - "packages/*"

将 packages 下所有的目录都作为包进行管理。这样我们的 Monorepo 就搭建好了。确实比lerna + yarn workspace更快捷

2.2 环境搭建

打包项目,Vue3 是采用rollup进行打包代码,因此需要安装打包所需要的依赖

依赖
typescript 在项目中支持 Typescript
rollup JavaScript 模块打包器
rollup-plugin-typescript2 rollup 和 ts 的 桥梁
@rollup/plugin-json 支持引入 json
@rollup/plugin-node-resolve 解析 node 第三方模块
@rollup/plugin-commonjs 将 CommonJS 转化为 ES6Module
minimist 命令行参数解析
execa@4 开启子进程
bash 复制代码
pnpm init -y
bash 复制代码
pnpm install typescript rollup rollup-plugin-typescript2 @rollup/plugin-json @rollup/plugin-node-resolve @rollup/plugin-commonjs minimist execa@4 esbuild   -D -w

2.2.1 初始化 TS 配置

bash 复制代码
pnpm tsc --init

先添加些常用的ts-config配置,后续需要其他的在继续增加

json 复制代码
{
  "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
  }
}

2.2.2 创建模块

我们先创建 packages 目录,然后在packages目录下新建两个package

  • reactivity 响应式模块
  • shared 共享模块

所有包的入口均为src/index.ts 这样可以实现统一打包

初始化两个模块的包管理配置文件 package.json:

  • reactivity/package.json

    json 复制代码
    {
      "name": "@vue/reactivity",
      "version": "1.0.0",
      "main": "index.js",
      "module": "dist/reactivity.esm-bundler.js",
      "unpkg": "dist/reactivity.global.js",
      "buildOptions": {
        "name": "VueReactivity",
        "formats": ["esm-bundler", "cjs", "global"]
      }
    }
  • reactivity/src/index.ts

ts 复制代码
export const str = 'reactivity';
console.log(str);
  • shared/package.json

shared是vue3内部共享的模块包,不需要暴露出去使用,因此不需要打包成 立即执行函数(IIFE) 的格式

json 复制代码
{
  "name": "@vue/shared",
  "version": "1.0.0",
  "main": "index.js",
  "module": "dist/shared.esm-bundler.js",
  "buildOptions": {
    "formats": ["esm-bundler", "cjs"]
  }
}
  • shared/src/index.ts
ts 复制代码
export const str = 'shared';
console.log(str);

formats为自定义的打包格式

  • esm-bundler: 构建工具中使用的格式
  • esm-browser: 浏览器中使用的格式
  • cjs: node 中使用的格式
  • global: 立即执行函数(IIFE)的格式
bash 复制代码
pnpm install @vue/shared@workspace --filter @vue/reactivity

tsconfig.json 配置ts引用关系

json 复制代码
"baseUrl": ".",
"paths": {
  "@vue/*": ["packages/*/src"]
}

2.2.3 开发环境esbuild打包

创建开发时执行脚本,参数为要打包的模块

解析用户参数

json 复制代码
"scripts": {
  "dev": "node scripts/dev.js reactivity -f global"
}

创建打包文件:scripts/dev.js

js 复制代码
const { build } = require("esbuild");
const { resolve } = require("path");
const args = require("minimist")(process.argv.slice(2));

const target = args._[0] || "reactivity";
const format = args.f || "global";

const pkg = require(resolve(__dirname, `../packages/${target}/package.json`));

const outputFormat = format.startsWith("global") // 输出的格式
  ? "iife"
  : format === "cjs"
  ? "cjs"
  : "esm";

const outfile = resolve(
  // 输出的文件
  __dirname,
  `../packages/${target}/dist/${target}.${format}.js`
);

build({
  entryPoints: [resolve(__dirname, `../packages/${target}/src/index.ts`)],
  outfile,
  bundle: true,
  sourcemap: true,
  format: outputFormat,
  globalName: pkg.buildOptions?.name,
  platform: format === "cjs" ? "node" : "browser",
  watch: {
    // 监控文件变化执行重新构建
    onRebuild(error) {
      if (!error) console.log(`rebuilt~~~~`);
    },
  },
}).then(() => {
  console.log("watching~~~");
});

命令行执行 pnpm run dev or yarn dev

然后再更改下reactivity/src/index.ts文件内容,看看终端效果:

再看下我们打包后的代码:

2.2.4 生产环境rollup打包

2.2.4.1 新建 rollup 打包配置 📦 文件

rollup.config.js

js 复制代码
import path from "path";
// 获取packages目录
const packagesDir = path.resolve(__dirname, "packages");
// 获取对应的模块
const packageDir = path.resolve(packagesDir, process.env.TARGET);
// 全部以打包目录来解析文件
const resolve = (p) => path.resolve(packageDir, p);
const pkg = require(resolve("package.json"));
const name = path.basename(packageDir); // 获取包的名字

// 配置打包信息
const outputConfigs = {
  "esm-bundler": {
    file: resolve(`dist/${name}.esm-bundler.js`),
    format: "es",
  },
  cjs: {
    file: resolve(`dist/${name}.cjs.js`),
    format: "cjs",
  },
  global: {
    file: resolve(`dist/${name}.global.js`),
    format: "iife",
  },
};

// 获取formats
const packageFormats = process.env.FORMATS && process.env.FORMATS.split(",");
const packageConfigs = packageFormats || pkg.buildOptions.formats;

import json from "@rollup/plugin-json";
import commonjs from "@rollup/plugin-commonjs";
import { nodeResolve } from "@rollup/plugin-node-resolve";
import tsPlugin from "rollup-plugin-typescript2";

function createConfig(format, output) {
  output.sourcemap = process.env.SOURCE_MAP;
  output.exports = "named";
  let external = [];
  if (format === "global") {
    output.name = pkg.buildOptions.name;
  } else {
    // cjs/esm 不需要打包依赖文件
    external = [...Object.keys(pkg.dependencies || {})];
  }
  return {
    input: resolve("src/index.ts"),
    output,
    external,
    plugins: [json(), tsPlugin(), commonjs(), nodeResolve()],
  };
}
// 开始打包...
export default packageConfigs.map((format) =>
  createConfig(format, outputConfigs[format])
);
2.2.4.2 新建自动打包脚本 build.js

scripts/build.js

js 复制代码
const fs = require("fs");
const execa = require("execa");
const targets = fs.readdirSync("packages").filter((f) => {
  if (!fs.statSync(`packages/${f}`).isDirectory()) {
    return false;
  }
  return true;
});
async function runParallel(source, iteratorFn) {
  const ret = [];
  for (const item of source) {
    const p = Promise.resolve().then(() => iteratorFn(item));
    ret.push(p);
  }
  return Promise.all(ret);
}
async function build(target) {
  await execa("rollup", ["-c", "--environment", `TARGET:${target}`], {
    stdio: "inherit",
  });
}
runParallel(targets, build);
2.2.4.3 添加打包命令
json 复制代码
"scripts": {
  "build": "node scripts/build.js"
}

命令行执行 pnpm run build or yarn build

打包 📦 结果如下:

案例代码

总结

Vue3采用了Monorepo管理项目,将模块拆分到package目录中,主要包括:

  • 编译时模块(compiler-core、compiler-dom、compiler-sfc、compiler-ssr
  • 运行时模块(runtime-core、runtime-dom
  • 响应式系统(reactivity-transform、reactivity)
  • vue2兼容(vue-compat
  • 共享模块(shared
  • 服务端渲染(server-renderer
  • 模版编译(template-explorer
  • ...

这样是方便版本管理和依赖管理。

同时,Vue3采用Typescript作为类型系统,对TS的支持更加友好。 在开发环境搭建方面,Vue3使用pnpm workspace实现Monorepo,使用esbuild进行开发环境打包,使用rollup进行生产环境打包。

相关推荐
成都被卷死的程序员25 分钟前
响应式网页设计--html
前端·html
mon_star°44 分钟前
将答题成绩排行榜数据通过前端生成excel的方式实现导出下载功能
前端·excel
Zrf21913184551 小时前
前端笔试中oj算法题的解法模版
前端·readline·oj算法
文军的烹饪实验室2 小时前
ValueError: Circular reference detected
开发语言·前端·javascript
Martin -Tang3 小时前
vite和webpack的区别
前端·webpack·node.js·vite
迷途小码农零零发3 小时前
解锁微前端的优秀库
前端
王解4 小时前
webpack loader全解析,从入门到精通(10)
前端·webpack·node.js
我不当帕鲁谁当帕鲁4 小时前
arcgis for js实现FeatureLayer图层弹窗展示所有field字段
前端·javascript·arcgis
那一抹阳光多灿烂4 小时前
工程化实战内功修炼测试题
前端·javascript
放逐者-保持本心,方可放逐5 小时前
微信小程序=》基础=》常见问题=》性能总结
前端·微信小程序·小程序·前端框架