Vue3源码之Monorepo的构建

183 阅读6分钟

前言

最近在学习Vue3的源码,接下来想着会在掘金上做个记录,自己实现一个Vue3, 希望自己能够坚持,并且能够说的清楚些,欢迎交流指正!

Vue3整体架构

接下来先从整体架构入手,介绍一下Vue3

Monorepo管理项目

Monorepo是管理代码的一个方式,其实就是在一个项目中管理多个模块/包,Vue3就是采用这种方式进行的管理,将对应的模块拆分到对应的package目录中

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

vue3项目结构

Vue3结构.png

有需要拿图

Vue3采用Typescript

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

Vue3开发环境搭建

搭建monorepo环境

vue3使用pnpm workspace来实现monorepopnpm是快速,节省磁盘空间的包管理器,主要采用符号连接的方式管理模块)

全局安装pnpm

npm install pnpm -g  # 全局安装
pnpm init -y  # 初始化配置文件

创建.npmrc文件

shamefully-hoist = true

配置workspace

新建文件pnpm-workspace.yaml

packages:
- 'packages/*'

接下里我们新建文件夹packages文件夹,将packages下的所有目录都作为包进行管理,这样我们的monorepo的基本环境就搭建起来了,是不是很快捷,基本文件结构如下


vue3-plain
├── packages
│   ├── reactivity  # 响应式模块 上面的脑图中对应的模块都会在这里
│   └── shared      # 共享文件 
├── .npmrc
├── pageage.json
└── pnpm-workspace.yaml

环境搭建

接下来我们的开发中涉及到三个依赖Typescrip minimist ESbulid,因为Vue3是用TS重构的,所以TS是毋庸置疑的minimist是命令行解析工具,ESbuild 是一个类似webpack构建工具。它的构建速度是 webpack 的几十倍,看下图

esbuild.webp

终端执行下列代码,下载我们所需要的依赖

pnpm install typescript minimist esbuild -w -D

这里如果不加-w的话会报错,提示会让你放在根目录,-w的意思就是workspace-root-D的意思就是开发依赖

为各个模块生成配置信息

因为Vue中有很多模块,我们上面新建了两个模块reactivity shared模块,接下来我们就按照这两个模块进行开发前的相应的配置

cd packages/reactivity

pnpn init

输入name:@vue/reactivity  // 这里名字与官方保持一致

接着我们在packages/reacvivity下就得到了package.json文件,如下

{
  "name": "@vue/reactivity",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC"
}

但是这还是不够的,比如当引入了我们的包的时候,肯定会有一个名字,还有就是打包后的运行环境,接下来我们需要定义一下属于自己的配置

{
   "buildOption":{
    "name":"VueReactivity",
    "formats":[
      "global",
      "cjs",
      "esm-bundler"
    ]
  }
}

这里global表示打包后可以在浏览器中运行,cjs表示可以在Node中运行,esm-bundler支持Es6,整体配置如下,依次类推到我们的shared文件中,但是略有不同

// reactivity/package.json

{
  "name": "@vue/reactivity",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "buildOption":{
    "name":"VueReactivity",
    "formats":[
      "global",
      "cjs",
      "esm-bundler"
    ]
  }
}

因为shared文件放入我们需要的公共方法,不需要在浏览器中运行,只需要在node中即可,所以不需要打包成global

// shared/package.json

{
  "name": "@vue/reactivity",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "buildOption":{
    "formats":[
      "cjs",
      "esm-bundler"
    ]
  }
}

打包

统一打包我们需要一个统一的出口,在packages/reacvivity下新建src/index.ts,同理shared文件也是一样,那么我们写的这两个模块需要互通,可以相互引入,我们在shared/src/index.ts中新建一个判断是否为对象的方法,我们引入试下


// shared/src/index.ts
export const isObject = (value) => {
  return typeof value === 'object' && value ! == null
}

// eacvivity`下新建`src/index.ts

import { isObject } from '@vue/shered'

没错!!! 会报错

报错.jpeg

这样是肯定找不到的,所以我们需要通过TS让这两个模块关联起来,OK,加入TS的配置文件,回到根目录执行下列代码

pnpm tsc --init

于是我们就得到了tsconfig.json文件,下面我们配置路径,解决无法引用的问题

// tsconfig.json
{
    "baseUrl": ".", // 依当前路径为基准
        "paths": {
          "@vue/*": ["packages/*/src"], // 当依@vue为基准时就去当前路径下的packages下去找
        },
}

全部配置

// tsconfig.json

{
  "compilerOptions": {
    "target": "esnext", // 目标语法
    "module": "esnext", // 模块格式
    "moduleResolution": "Node", // 模块解析方式
    "strict": false, // 严格模式
    "jsx": "preserve", // jsx 不转义
    "esModuleInterop": true, // 允许es6语法引入commonjs模块
    "sourceMap": true, // 采用sourceMap
    "resolveJsonModule": true, // 解析json模块
    "lib": ["dom", "esnext"], //不支持类库dom exnext
    "baseUrl": ".", // 依当前路径为基准
    "paths": {
      "@vue/*": ["packages/*/src"], // 当依@vue为基准时就去当前路径下的packages下去找
    },
  },
}

这样我们就可以进行引入了!!!
现在我们的环境搭建基本就完成了,接下来我们就打包

打包配置

我们新建一个script/dev.js文件夹用于存放打包的脚本,然后我们在package.json里配置命令行

// 根目录的package.json

  "scripts": {
    "dev": "node script/dev.js reactivity -f global"
  },

node script/dev.js reactivity -f global意思是执行script下的dev.js脚本文件,打包reactivity文件,-f代表打包的格式 global为全局模式

编写脚本文件

我们刚刚下载的minimist现在可以用到了

// dev.js

const args = require('minimist')(process.argv.slice(2)) // 只需要将进程中的参数传入

console.log(agrs)

当我们运行node script/dev.js reactivity -f global 就回把reactivity -f global传入,那么我们就可以接受到了,看下打印结果(下图)

WechatIMG60.png

这个数组中的的参数就是我们定义的,也就是reactivity为需要打包的文件,global为打包的格式,这也就是minimist干的事情,接下来我们拿到参数后就可以做处理了

// dev.js

const agrs = require('minimist')(process.argv.slice(2)) // 获取传入的参数
const { build } = require('esbuild')  // 引入esbuild用于打包
const { resolve }  = require('path')  // node 内置模块

const target = agrs._[0] || 'reactivity'  // 获取打包的文件
const formate = agrs.f || 'global'        // 获取打包的格式

const pkg = resolve(__dirname,`../packages/${target}/package.json`) // 获取需要打包文件下的package.json文件


// iife 为立即执行函数
// cjs  为node中的模块
// esm  为浏览器的esModule模块
const outputFormat = formate.startsWith('global') ? 'iife' : formate === 'cjs' ? 'cjs' : 'esm'


// 获取输出文件,并取名
const outfile = resolve(__dirname,`../packages/${target}/dist/${target}.${formate}.js`)

// esbuild 的语法 天生支持TS
build({
  enteryPoints:[resolve(__dirname,`../packages/${target}/src/index.ts`)], // 打包的入口文件
  outfile, // 输出文件
  bundle,  // 将所有包打包到一起
  sourcemap:true,
  format:outputFormat, // 输出的格式
  globalName:pkg.buildOptions?.name, // 打包的全局的名字
  platform:formate === 'cjs' ? 'node' : 'browser',  // 平台
  watch:{ // 监听文件的变化
    onRebuild(error) {
      if(!error) console.log('rebuild')
    }
  }
}).then(() => {
  console.log('watching.....')
})

上面的一坨代码是我们执行pnpm run dev的脚本文件,主要是用于打包的,已经写了注释,后面会有esbuild的介绍使用,这里大家先看下是什么意思即可!

运行

OK!!! 终于到了可以验证成果的时候了,在packages/reactivity/src/index.ts加入一行代码我们测试下效果

// index.ts

import { isObject } from "@vue/shared";

console.log(isObject(true))
console.log(isObject({}))

接下来我们只需要执行pnpm run dev即可,这里给大家贴张图看下ESbuild打包的傲人速度!!! esbuild.gif

这速度可以说的毫秒级别的了~~~~
我们看下成功了没有,当我们打包后在我们的reactivity下会新增一个dist文件夹,里面会有reactivity.globalreactivity.global.js 我们在dist文件夹下手动新增一个html文件,将reactivity.global.js引入就可以在浏览器上运行了

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <script src="./reactivity.global.js"></script>
</body>
</html>

打开浏览器

WechatIMG61.png 至此,开发一个属于自己的Vue3的开发环境我们就搭建好了,后面会实现Vue的核心模块,响应式,也就是我们的reactivity模块,后续会进行持续更新,望点赞支持下!!!!

夫学须静也,才须学也,非学无以广才,非志无以成学。淫慢则不能励精,险躁则不能治性。年与时驰,意与日去,遂成枯落