前言
最近在学习Vue3的源码,接下来想着会在掘金上做个记录,自己实现一个Vue3, 希望自己能够坚持,并且能够说的清楚些,欢迎交流指正!
Vue3整体架构
接下来先从整体架构入手,介绍一下Vue3
Monorepo管理项目
Monorepo是管理代码的一个方式,其实就是在一个项目中管理多个模块/包,Vue3就是采用这种方式进行的管理,将对应的模块拆分到对应的package目录中
- 一个仓库可维护多个模块,不用到处找仓库
- 方便版本管理和依赖管理,模块之间的引入与调用都非常方便
vue3项目结构
有需要拿图
Vue3采用Typescript
vue2采用Flow来进行类型检测(Vue2中对TS的支持并不友好),Vue3源码采用Typescript来进行重写,对TS的支持更加友好
Vue3开发环境搭建
搭建monorepo环境
vue3使用pnpm workspace
来实现monorepo
(pnpm
是快速,节省磁盘空间的包管理器,主要采用符号连接的方式管理模块)
全局安装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
的几十倍,看下图
终端执行下列代码,下载我们所需要的依赖
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'
没错!!! 会报错
这样是肯定找不到的,所以我们需要通过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
传入,那么我们就可以接受到了,看下打印结果(下图)
这个数组中的的参数就是我们定义的,也就是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
打包的傲人速度!!!
这速度可以说的毫秒级别的了~~~~
我们看下成功了没有,当我们打包后在我们的reactivity
下会新增一个dist
文件夹,里面会有reactivity.global
和reactivity.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>
打开浏览器
至此,开发一个属于自己的
Vue3
的开发环境我们就搭建好了,后面会实现Vue
的核心模块,响应式,也就是我们的reactivity
模块,后续会进行持续更新,望点赞支持下!!!!
夫学须静也,才须学也,非学无以广才,非志无以成学。淫慢则不能励精,险躁则不能治性。年与时驰,意与日去,遂成枯落