Vue 源码项目搭建
Monorepo
使用Monorepo的方式来组织我们的项目。
Monorepo的优点:
- 代码共享和复用
- 抽取公共代码方便复用
- 依赖管理
- 统一依赖版本
- 工程化管理
- 统一构建
- 统一测试
- 统一代码规范
目录结构
packages/
├── compiler-core/ # 编译器核心代码
├── compiler-dom/ # 浏览器平台编译器
├── compiler-sfc/ # 单文件组件编译器
├── compiler-ssr/ # 服务端渲染编译器
├── reactivity/ # 响应式系统
├── runtime-core/ # 运行时核心代码
├── runtime-dom/ # 浏览器运行时
├── runtime-test/ # 测试相关运行时
├── server-renderer/ # 服务端渲染
├── shared/ # 共享工具代码
└── vue/ # 完整版本入口搭建流程
mkdir vue3
cd vue3pnpm init // 项目初始化在vue3根目录下创建pnpm-wprkspace.yaml文件:
pnpm-workspace.yaml的作用是是 pnpm 的工作区配置文件,用于定义 monorepo 中的包结构。
packages:
- 'packages/*' # 所有子包放在 packages 目录下创建子包和响应式、完整vue入口目录:
vue3/
├── package.json
├── pnpm-workspace.yaml
├── packages/
│ └── reactivity/
│ └── vue/安装 typescript 依赖:
pnpm install typescript -D配置 typescript:
{
"compilerOptions": {
"target": "ESNext", // 指定 ECMAScript 目标版本
"module": "ESNext", // 指定模块代码生成规范
"moduleResolution": "node", // 指定模块解析策略
"outDir": "dist", // 指定编译输出的目录
"resolveJsonModule": true, // 允许导入 JSON 文件
"strict": false, // 关闭严格模式
"lib": ["ESNext", "DOM"], // 指定要使用的库文件
"paths": {
"@vue/*": ["packages/*/src"]
}, // 路径别名映射,简化模块导入路径
"baseUrl": "./" // 模块解析的基础路径
}
}在 packages/reactivity 目录下创建 package.json 文件:
{
"name": "@vue/reactivity",
"version": "1.0.0",
"description": "响应式模块",
"main": "dist/reactivity.cjs.js",
"module": "dist/reactivity.esm.js",
"files": ["index.js", "dist"],
"sideEffects": false,
"buildOptions": {
"name": "VueReactivity",
"formats": ["esm-bundler", "esm-browser", "cjs", "global"]
}
}在 packages/reactivity 目录下创建 src/index.ts 文件:
console.log("vue reactivity");再在 packages/vue 目录下创建 package.json 文件:
{
"name": "vue",
"version": "1.0.0",
"description": "vue核心包",
"main": "dist/vue.cjs.js",
"module": "dist/vue.esm.js",
"files": ["dist"],
"sideEffects": false,
"buildOptions": {
"name": "Vue",
"formats": ["esm-bundler", "esm-browser", "cjs", "global"]
}
}在 packages/vue 目录下创建 src/index.ts 文件:
console.log("vue");配置 esbuild 打包:
我们使用 esbuild 来打包我们的项目。
安装 esbuild 依赖:
pnpm install esbuild -D在package.json文件下增加一个dev脚本:
{
"type": "module", // 开启 esm 模块规范
"scripts": {
"dev": "node scripts/dev.js --format esm"
}
}这样我们执行 pnpm run dev 的时候,就会执行 scripts/dev.js 这个文件。
/**
* 打包开发环境
*
* node scripts/dev.js --format esm
*/
import { parseArgs } from "node:util";
import { resolve, dirname } from "node:path";
import { fileURLToPath } from "node:url";
import esbuild from "esbuild";
import { createRequire } from "node:module";
/**
* 解析命令行参数
*/
const {
values: { format },
positionals,
} = parseArgs({
allowPositionals: true,
options: {
format: {
type: "string",
short: "f",
default: "esm",
},
},
});
// 创建 esm 的 __filename
const __filename = fileURLToPath(import.meta.url);
// 创建 esm 的 __dirname
const __dirname = dirname(__filename);
const require = createRequire(import.meta.url);
const target = positionals.length ? positionals[0] : "vue";
const entry = resolve(__dirname, `../packages/${target}/src/index.ts`);
/**
* --format cjs or esm
* cjs => reactive.cjs.js
* esm => reactive.esm.js
* @type {string}
*/
const outfile = resolve(
__dirname,
`../packages/${target}/dist/${target}.${format}.js`,
);
const pkg = require(`../packages/${target}/package.json`);
esbuild
.context({
entryPoints: [entry], // 入口文件
outfile, // 输出文件
format, // 打包格式 cjs esm iife
platform: format === "cjs" ? "node" : "browser", // 打包平台 node browser
sourcemap: true, // 开启 sourcemap 方便调试
bundle: true, // 把所有的依赖,打包到一个文件中
globalName: pkg.buildOptions.name,
})
.then((ctx) => {
// 监听文件变更重新打包
return ctx.watch();
});打包时执行如下命令:
pnpm run dev [--format <type>] [package]如:
pnpm run dev --format cjs reactivity为什么要手动创建
__filename和__dirname?在 CommonJS 中,Node.js 会提供这两个变量,但是在 ESModule 中,为了兼容浏览器环境没有提供,需要手动创建。
可以看一下打包后的iife格式的代码,会发现里面有一个立即执行函数。这种格式的包主要为了CDN使用。
创建一个 packages/shared 来放公共代码。
export function isObject(value) {
return typeof value === "object" && value !== null;
}{
"name": "@vue/shared",
"version": "1.0.0",
"description": "工具函数",
"main": "dist/shared.cjs.js",
"module": "dist/shared.esm.js",
"files": ["dist"],
"sideEffects": false,
"buildOptions": {
"name": "VueShared",
"formats": ["esm-bundler", "esm-browser", "cjs", "global"]
}
}此时我们可以通过使用 --filter 命令来在其它子包中安装@vue/shared包。
pnpm install @vue/shared --workspace --filter @vue/reactivity如果不加 --workspace 命令将会安装 npm 仓库里面的
@vue/shared包。
此时在packages/reactivity/package.json文件中的依赖:
{
"dependencies": {
"@vue/shared": "workspace:^"
}
}
package.json中版本号符合:*表示允许更新到任意版本号、^表示允许更新到兼容版本、~表示允许更新到补丁版本。
