Appearance
Vue3 环境搭建
Vue3 中使用pnpm workspace来实现monorepo (pnpm是快速、节省磁盘空间的包管理器。主要采用符号链接的方式管理模块)
1. 全局安装 pnpm
bash
npm i -g pnpm # 全局安装pnpm
pnpm init # 初始化配置文件2. 创建 .npmrc 配置文件
bash
shamefully-hoist = true可以尝试使用 pnpm install vue 命令安装一个 vue 来对比它们的区别。默认情况下,vue3 中所依赖的模块不会被提升到 node_modules中,添加 羞耻的提升(shamefully-hoist) 可以将vue3所依赖的模块添加到 node_modules中。
没有配置 shamefully-hoist

配置了 shamefully-hoist

3. 配置 workspace
配置 pnpm 的工作空间,新建 pnpm-workspace.yaml
yaml
package:
- "packages/*"将 packages 下所有的目录都作为包进行管理,这样我们的 Monorepo 就搭建好了,确实比 lerna + yarn workspace 更快捷
4. 安装依赖
首先介绍下 pnpm 的 -w 参数,它是 --workspace-root 的缩写,Monorepo 仓库在根目录也是会有一个 node_modules 目录,加上该参数,表示在工作空间的根目录进行安装或启动,而不是当前的工作目录。
vue3 开发环境采用的是 esbuild ,打包 vue3 项目采用的是 rollup 进行打包代码
| 依赖 | |
|---|---|
| typescript | 在项目中支持 typescript |
| esbuild | 构建工具,默认支持 ts |
| minimist | 命令行解析工具 |
bash
pnpm install typescript minimist esbuild -D -w5. 初始化 TS
bash
pnpm tsc --init初始化,配置一些常用的配置,后续需要其他的再继续添加
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
}
}6. 创建初始模块
在 package 目录下新建两个包,用于手写响应式原来做准备
- reactivity:响应式模块
- share:共享模块
所有包的入口均为 **src/index.ts** 这样可以实现统一打包,并在包信息中增添打包格式 formats 属性,用于最终打包后的格式。
- 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-browser", "esm-bundler", "cjs", "global"]
}
}- share/package.json
json
{
"name": "@vue/shared",
"version": "1.0.0",
"main": "index.js",
"module": "dist/shared.esm-bundler.js",
"buildOptions": {
"formats": ["esm-bundler", "cjs"]
}
}6.1. formats 配置打包格式
global立即执行函数的格式,会暴露全局对象esm-browser在浏览器中使用的格式,内联所有的依赖项。esm-bundler在构建工具中使用的格式,不提供.prod 格式,在构建应用程序时会被构建工具一起进行打包压缩。cjs在 node 中使用的格式,服务端渲染。

6.2. monorepo 中包的互相引用
在 reactivity 包中想要引用我们自己写的 shared 包,可以使用以下命令进行安装
bash
pnpm install @vue/shared@workspace --filter @vue/reactivity在使用时,我们还需要配置 ts 的引用关系,否则导入会从 node_modules 中去找,而我们需要的是它从我们写的 package 目录中去找
json
{
// 添加以下配置,我们引入@vue/***的话,他就会从我们自己的 package 中去找
"baseUrl": ".",
"paths": {
"@vue/*": ["packages/*/src"]
}
}7. 开发环境的搭建

创建开发时执行脚本,参数是要打包的模块
json
{
"script": {
// 我们需要对 reactivity 模块进行打包,并且使用 esm 模式
"dev": "node scripts/dev.js reactivity -f esm"
}
}编写 dev 脚本
javascript
// 使用 esbuild 加快开发构建速度。
// 仍使用 Rollup 进行生产构建,因为它会生成更小的文件,并提供更好的 tree-shaking 效果。
const { resolve } = require('path')
const { context } = require('esbuild')
const minimist = require('minimist')
// 解析用户执行命令行的参数
const args = minimist(process.argv.slice(2))
// 打包的格式,如果没传,默认用 global 模式
const format = args.f || 'global'
// 确定打包的模块
const target = args._[0] || 'reactivity'
// 读取对应的 package.json 内容
const pkg = require(resolve(__dirname, `../packages/${target}/package.json`))
// 打包输出的格式, 如果为 global 则使用 iife 自执行函数的方式
const outputFormat = format.startsWith('global') ? 'iife' : format === 'cjs' ? 'cjs' : 'esm'
// 输出文件
const outfile = resolve(__dirname, `../packages/${target}/dist/${target}.${format}.js`)
// esbuild 打包
context({
entryPoints: [resolve(__dirname, `../packages/${target}/src/index.ts`)],
outfile,
bundle: true,
sourcemap: true,
format: outputFormat,
globalName: pkg.buildOptions?.name,
platform: format === "cjs" ? "node" : "browser",
}).then(ctx => {
ctx.watch()
console.log("watching~~~~")
})我们可以对比一下 global 和 esm 两种模式下打包出来是什么样子的
在 shared/index.ts 中编写一个函数,用于判断是否为对象
typescript
export function isObject(value: any) {
return value !== null && typeof value === 'object'
}在 reactivity/index.ts 中引入并使用一下
typescript
import { isObject } from "@vue/shared"
console.log(isObject({}))修改 dev 脚本
json
"scripts": {
"dev:global": "node scripts/dev.js reactivity -f global"
"dev:esm": "node scripts/dev.js reactivity -f esm"
}global

esm

