我报名参加金石计划1期挑战——瓜分10万奖池,这是我的第2篇文章,点击查看活动详情
前言
在上一篇我们主要分析了 vue3 的目录结构划分及 package.json 文件几个主要属性,了解到 Vue3 通过 ”npm scripts“ 生命周期钩子函数,在 ”preinstall“ 阶段,来检测用户是否使用的 pnpm 作为包管理器,并通过 pnpm 来实现 monorepo 架构。这个章节我们将搭建一个简易的开发环境,方便后续工作的进行。来,撸起袖子,加油干吧😁
初始化pnpm环境
npm install -g pnpm
mkdir mini-vue && cd mini-vue && pnpm init -y
构建monorepo环境
在项目根目录新建 pnpm-workspace.yaml 文件
packages
- 'packages/*'
必需的几个npm包
- minimist 解析node命令参数;
- esbuild 用于开发环境的打包;
- typescript
pnpm install -w minimist esbuild typescript // -w 是--workspace-root的缩写 代表根目录中启动 pnpm ,而不是当前的工作目录
这里简单的讲一下 esbuild:
Esbuild 是由 Figma 的 CTO 「Evan Wallace」基于 Golang 开发的一款打包工具,相比传统的打包工具,主打性能优势,在构建速度上可以快 10~100 倍。
支持:
- ES6 和 CommonJS 模块
- ES6 模块的 Tree Shaking
- JavaScript 和 Go的API
- TypeScript和JSX语法
划分目录结构
这里我们与源码保持一致:
├── package.json
├── packages
├── scripts
初始化ts环境
tsc init
在生成的tsconfig.json文件中添加
{
"baseUrl": ".",
"paths": {
"@mini-vue/*": ["packages/*/src/"] // 这里的@mini-vue是我们定义在 package.json 文件中的 name 属性
}
}
这样配置的目的是防止我们在引入模块文件时,ts 提示异常。比如
完善 package.json
{
"name": "mini-vue",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"dev": "node scripts/dev.js shared -f global"
},
"buildOptions": {
"name": "VueShared",
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"esbuild": "^0.14.42",
"minimist": "^1.2.6",
"typescript": "^4.7.2"
}
}
这里需要注意两个地方:
- 新增 dev 脚本命令 shared 表示需要打包的目录;-f 表示打包输入的文件格式(支持三种:cjs、esm、iife);
- 添加自定义属性:buildOptions ,在后续打包环节中会读取这个属性,因为每个子包的打包产物可能不同,因此在这里可以定义一些个性化的参数。
编写开发环境脚本
在项目根目录创建scripts目录,专门存放脚本文件
|-scripts -- 专门存放脚本文件
|--dev.js -- 开发环境脚本
// scripts/dev.js
const minimist = require("minimist");
const esbuild = require("esbuild");
const path = require("path");
const args = minimist(process.argv.slice(2));
const pkgName = args._[0]; // 获取命令行中指定的包名
// 这里的 global 其实就是 iife(自执行函数) 的输出格式
const f = args.f || "global"; // 获取打包输出格式
const pkg = require(path.resolve(
__dirname,
`../packages/${pkgName}/package.json`
));
const entry = path.resolve(__dirname, `../packages/${pkgName}/src/index.ts`);
// 打包输出路径
const outfile = path.resolve(
__dirname,
`../packages/${pkgName}/dist/${pkgName}.${f}.js`
);
const format = f.startsWith("global") ? "iife" : f;
esbuild
.build({
entryPoints: [entry],
outfile,
bundle: true,
sourcemap: true,
format, // 打包输出的文件格式
globalName: pkg.buildOptions.name, // iife格式的文件定义的全局变量
platform: f.startsWith("cjs") ? "node" : "browser",
watch: { // 这里监听文件变化后再进行重构代码
onRebuild(error, result) {
if (error) console.error("watch build failed:", error);
else console.log("watch build succeeded:", result);
},
},
})
.then(() => {
console.log("watch...");
})
.catch(() => process.exit(1));
测试一下
在 packages 目录下新建 shared 子包,主要存放一些共享模块。
packages
└── shared
├── package.json
└── src
└── index.ts
在 shared/index.ts 文件中定义几个工具方法。
// packages/shared/index.ts
export const isObject = (obj) => {
return typeof obj === "object" && obj !== null;
};
export const isFunction = (obj) => {
return typeof obj === "function";
};
export const isArray = Array.isArray;
接下来运行
此时在 shared 目录下会生成一个 dist 文件夹,这个就是打包后的产物
加下来在 packages/shared/dist 文件夹下新建一个 index.html
<!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="./shared.global.js"></script>
<script>
// vueShared 其实就是 esbuild 的 globalName 属性
const { isFunction } = vueShared;
console.log(
"shared",
isFunction(() => {})
);
</script>
</body>
</html>
运行结果:
总结
工欲善其事必先利其器,这个章节我们搭建了一个简易的开发环境,完善了 ts.config.json 与 package.json 文件,并编写了基于 esbuild 的开发环境构建脚本。麻雀虽小,但五脏俱全,到目前为止尽量还原了 Vue3 源码的主干部分。下一章节进入 Vue3 响应式系统💪🏻