目录
1.全局安装 pnpm
2.创建.npmrc 文件
.npmrc
设置 shamefully-hoist=true:
3.配置 workspace
4.环境搭建
5.初始化 TS
6.创建模块
7.开发环境esbuild打包
Monorepo 是管理项目代码的一个方式,指在一个项目仓库(repo)中管理多个模块/包(package)。 Vue3 源码采用 monorepo 方式进行管理,将模块拆分到 package 目录中。作为一个个包来管理,这样职责划分更加明确。
一个仓库可维护多个模块,不用到处找仓库
方便版本管理和依赖管理,模块之间的引用,调用都非常方便
Vue3 中使用pnpm
workspace
来实现monorepo
(pnpmopen in new window是快速、节省磁盘空间的包管理器。主要采用符号链接的方式管理模块)
1.全局安装 pnpm
npm install pnpm -g # 全局安装pnpm
pnpm init # 初始化配置文件
2.创建.npmrc 文件
.npmrc
.npmrc
是 npm 的配置文件,用于定义和管理 npm 的各种行为,比如:
设置私有 registry(注册源)
保存 token、认证信息
配置包的安装路径、缓存路径等
各种行为开关,如是否生成 package-lock.json
.npmrc
文件可以存在于不同级别:
位置 | 作用范围 |
---|---|
项目根目录下的 .npmrc |
仅影响当前项目 |
用户主目录(如 ~/.npmrc ) |
影响当前用户的所有项目 |
全局(如 /etc/npmrc ) |
影响所有用户 |
shamefully-hoist=true
是什么?
这是 pnpm(而非 npm) 的一个配置项,用于控制依赖的 hoisting(提升)方式。
默认行为(pnpm 默认):
pnpm 会把每个依赖安装在自己的隔离目录中,避免依赖污染。
这有助于防止 “依赖地狱”,但某些依赖可能找不到自己需要的库(尤其是老旧的依赖)。
设置 shamefully-hoist=true
:
pnpm 会将所有依赖都提升到项目的 node_modules
根目录下。
这种方式 更像 npm 和 Yarn 的行为,兼容性好,但失去 pnpm 原本的隔离优势。。
shamefully-hoist = true
这里您可以尝试一下安装
Vue3
,pnpm install vue
此时默认情况下vue3
中依赖的模块不会被提升到node_modules
下。 添加羞耻的提升可以将 Vue3,所依赖的模块提升到node_modules
3.配置 workspace
新建 pnpm-workspace.yaml
pnpm-workspace.yaml
是 pnpm 用于管理 Monorepo 项目的配置文件。
它告诉 pnpm 哪些子项目(package)属于这个 Monorepo,使它可以统一安装依赖、共享缓存、加速构建。
packages:
- "packages/*"
将 packages 下所有的目录都作为包进行管理。这样我们的 Monorepo 就搭建好了。确实比
lerna + yarn workspace
更快捷
packages/*
是子项目的路径匹配规则(glob 模式)。这些路径下的项目会被 pnpm 识别为 Monorepo 的一部分,参与联动管理。
4.环境搭建
打包项目 Vue3 采用 rollup 进行打包代码,安装打包所需要的依赖
开发依赖 | |
---|---|
typescript | 在项目中支持 Typescript |
esbuild | 构建工具,默认支持 TS |
minimist | 命令行参数解析 |
在 pnpm 中,-w
是 --workspace-root
的简写,表示将依赖安装到工作区根目录(适用于 monorepo 项目)。
适用场景:当你的项目使用 pnpm 工作区(monorepo 结构,包含多个子包)时,-w
会将依赖安装在根目录的 node_modules
中,而不是某个子包内。
用途:通常用于安装全局开发工具(如 TypeScript、esbuild),让所有子包共享这些依赖,避免重复安装。
对比:
不加 -w
:依赖会安装到当前工作目录的子包中。
加 -w
:依赖安装到 monorepo 根目录,所有子包均可访问。
pnpm install typescript minimist esbuild -D -w
5.初始化 TS
复杂的框架项目开发,使用类型语言非常有利于代码的维护,在编码期间就可以帮我们做类型检查,避免错误。所以 TS 已经是主流框架的标配~
Vue2 早期采用 Flow 来进行类型检测 (Vue2 中对 TS 支持并不友好), Vue3 源码采用 Typescript 来进行重写。同时 Vue2.7 也采用 TS 进行重写。TS 能对代码提供良好的类型检查,同时也支持复杂的类型推导。
pnpm tsc --init
先添加些常用的
ts-config
配置,后续需要其他的在继续增加
{
"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.创建模块
我们现在
packages
目录下新建两个 package,用于下一章手写响应式原理做准备
reactivity
响应式模块
shared
共享模块
所有包的入口均为src/index.ts
这样可以实现统一打包
reactivity/package.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", "esm-browser","cjs", "global"]
}
}
shared/package.json
{
"name": "@vue/shared",
"version": "1.0.0",
"main": "index.js",
"module": "dist/shared.esm-bundler.js",
"buildOptions": {
"formats": ["esm-bundler", "cjs"]
}
}
name
: 包名称,采用 Vue 官方的组织包命名方式,表示这是 Vue 的响应式系统核心模块
version
: 包版本号,遵循语义化版本控制规范
main
: 主入口文件,当通过 CommonJS (require) 方式引入时会加载这个文件
module
: ES Module 入口文件,面向现代打包工具(如 webpack, Rollup) 特点:保留 __DEV__
等编译时标志,方便进行生产环境优化
unpkg
: 专为浏览器直接使用设计的 UMD 格式文件 特点:自动挂载到全局变量,可通过 <script>
标签直接引入
buildOptions
: 构建配置
name
:UMD 构建的全局变量名称,浏览器中可通过 window.VueReactivity
访问
formats
: 指定要生成的包格式
formats
为自定义的打包格式
global
立即执行函数的格式,会暴露全局对象「 浏览器全局变量格式(UMD) 」
esm-browser
在浏览器中使用的格式,内联所有的依赖项。
esm-bundler
在构建工具中使用的格式,不提供.prod
格式,在构建应用程序时会被构建工具一起进行打包压缩。 「给打包工具使用的 ES Module(保留开发环境判断)」
cjs
在node
中使用的格式,服务端渲染。 CommonJS 格式
pnpm install @vue/shared --workspace --filter @vue/reactivity
🔹 pnpm install
这是使用 pnpm
(一个高效的包管理工具)安装依赖的命令,类似于 npm install
或 yarn add
。
🔹 @vue/shared
这是你要安装的包。@vue/shared
是 Vue 源码中的一个共享工具包,包含一些公共函数,供 Vue 的各个子模块使用。
🔹 --workspace
这个标志告诉 pnpm
:你正在 Monorepo(多包仓库)环境中操作,你希望把依赖加到某个工作空间(子包)里,而不是根目录。
🔹 --filter @vue/reactivity
这部分指定:我只对 @vue/reactivity
这个子包进行操作。换句话说,是在 @vue/reactivity
这个包里安装 @vue/shared
。
在
@vue/reactivity
这个包里安装@vue/shared
作为依赖。 并且使用本地目录
配置
ts
引用关系
"baseUrl": ".",
"paths": {
"@vue/*": ["packages/*/src"]
}
7.开发环境esbuild
打包
创建开发时执行脚本, 参数为要打包的模块
解析用户参数
"scripts": {
"dev": "node scripts/dev.js reactivity -f esm"
}
// 这个文件会帮我们打包 packages 下的模块,最终打包成 js
/**
* node dev.js 要打包的模块名 -f 要打包的格式 === process.argv.slice(2)
* 获取命令行参数 通过 process.argv获取 { 参数1: 用哪个命令执行的 node, 参数2: 执行的文件名 }
* node 中不能直接使用 esm 的模块,所以需要使用 cjs 的模块 「package.json type设置为 module」
*
* node 中的 esm 模块没有 __dirname 和 require 所以需要使用 url 模块来获取当前文件的绝对路径
* require 函数 是 node 中的一个函数,可以用来加载模块
* __dirname 是 node 中的一个全局变量,可以用来获取当前文件的目录名
* __filename 是 node 中的一个全局变量,可以用来获取当前文件的绝对路径
* resolve 拼接路径
*
*
* esbuild.context 是 esbuild 的实例,可以用来打包模块
entryPoints: [entry], // 入口文件
outfile: resolve(__dirname, `../packages/${target}/dist/${target}.js`), // 输出文件
bundle: true, // 打包 「将所有文件打包成一个文件 reactivity 依赖 shared 会打包成一个文件」
platform: "browser", // 平台 「打包后给浏览器使用」
sourcemap: true, // 源码映射
format: format, // 格式 「cjs:require, esm:import, iife:script」
globalName: pkg.buildOptions.name, // 全局变量名 「供全局使用,因为iife 是一个闭包」
*/
// 获取命令行参数
import minimist from "minimist";
// 获取当前文件的绝对路径
import { fileURLToPath } from "url";
// dirname 获取当前文件的目录名 resolve 获取绝对路径
import { dirname, resolve } from "path";
// 创建 require 函数
import { createRequire } from "module";
// 打包工具
import esbuild from "esbuild";
const __filename = fileURLToPath(import.meta.url); // 获取当前文件的绝对路径 file=>/user
const __dirname = dirname(__filename); // dirname 获取当前文件的目录名
const require = createRequire(import.meta.url); // 创建 require 函数「根据当前文件的绝对路径创建」
const args = minimist(process.argv.slice(2));
const target = args._[0] || "reactivity"; // 要打包的模块名
const format = args.f || "iife"; // 要打包的格式
// 入口文件 根据 target 获取入口文件 「resolve 拼接路径」
const entry = resolve(__dirname, `../packages/${target}/src/index.js`);
// 获取 package.json 文件
const pkg = require(`../packages/${target}/package.json`);
esbuild
.context({
entryPoints: [entry], // 入口文件
outfile: resolve(__dirname, `../packages/${target}/dist/${target}.js`), // 输出文件
bundle: true, // 打包 「将所有文件打包成一个文件 reactivity 依赖 shared 会打包成一个文件」
platform: "browser", // 平台 「打包后给浏览器使用」
sourcemap: true, // 源码映射
format: format, // 格式 「cjs:require, esm:import, iife:script」
globalName: pkg.buildOptions.name, // 全局变量名 「供全局使用,因为iife 是一个闭包」
})
.then((ctx) => {
console.log("打包成功");
return ctx.watch(); // 监听文件变化
})
.catch((err) => {
console.log(err);
});
暂无评论内容