我正在参加会员专属活动-源码共读第一期,点击参加
本期的源码共读是 《揭秘 create-vite 原理》,这篇文章记录了学习进程中遇到的问题以及解决办法。
create-vite 是什么
create-vite 是一个用于创立 Vite 项目的脚手架东西。它的原理如下:
- create-vite 是一个指令行东西,能够经过 npm 指令来装置和运用。
- 在指令行中运转 create-vite 指令时,会提示用户输入项目名称和其他参数。
- create-vite 会依据用户输入的信息,在当前目录下创立一个新的 Vite 项目。
- 创立进程中,create-vite 会调用 Vite 库中的 API,完成项目的初始化和依赖装置。
- 创立完成后,create-vite 会打印项目信息,并奉告用户怎么运转项目。
综上所述,create-vite 原理是经过指令行交互和调用 Vite 库的 API 来完成项目的创立和初始化。它是一个方便快捷的东西,能够协助开发者快速创立 Vite 项目,提高开发效率。
学习方针
- 理解
npm create vite <your project name> [--template vue]
指令生成 vite 项目的进程 - 剖析 create-vite 源码
准备工作
克隆 vitejs/vite 库房
项目克隆后能够首要查看 CONTRIBUTING.md
文件,CONTRIBUTING.md
文件中有运用 VSCode 调试源码的具体步骤。
git clone https://github.com/vitejs/vite.git
初始化环境
Vite repo
是一个运用 pnpm 工作区的 monorepo (一个git管理多个项目)。项目运用 pnpm
管理依赖,装置 pnpm
后履行指令
pnpm i
怎么调试
进入到 vite\packages\create-vite\src
目录下,履行指令:
npx tsx .\index.ts
在此期间查看控制台交互并在代码中进行断点调试
流程剖析
- 获取指令行参数
- 处理参数
- 有无指定项目名
- 有无指定项目模板
- 项目名是否重复
- 校验项目名是否合法
- 提示用户选择框架
- 读取模板写入目录
源码解读
程序进口
create-vite
程序进口
获取指令行参数
运用 minimist 包解析用户输入参数
程序履行开端,首要搜集指令行用户输入(项目名,模板),修正代码,如下:
async function init() {
// formatTargetDir 函数格式化方针文件夹的途径,使其不包括剩余的空格和斜杠。
// argv._[0] 是指令行的第一个参数
const argTargetDir = formatTargetDir(argv._[0])
console.log('argv: ', argv)
...
}
修正代码后履行程序,查看 argv 输出了什么
npx esno index.ts demo1
控制台输出:
argv: { _: [ 'demo1' ] }
用户输入交互
prompts 是一个轻量简洁用户交互指令行提示东西。
prompts 中有如下几种可能发生的交互:
- projectName:获取项目名
- overwrite:重名的情况问询用户是否掩盖
- overwriteChecker:查看用户操作
- packageName:提示用户输入包名(toValidPackageName函数会查看包名是否合法)
- framework:选择框架
- variant:选择言语变体(js,ts..)
查看包名是否合法
function isValidPackageName(projectName: string) {
return /^(?:@[a-z\d\-*~][a-z\d\-*._~]*\/)?[a-z\d\-~][a-z\d\-._~]*$/.test(
projectName
)
}
在 JavaScript 中,包名的格式一般遵循以下规矩:
- 包名能够包括字母、数字、破折号、点、星号和波涛线等字符,不能包括空格或其他特殊字符。
- 包名必须以字母或数字最初,不能以破折号或波涛线最初。
- 包名中能够包括点,表明层级联系。例如,lodash.isarray 表明 lodash 包中 isarray 子包。
- 包名中能够包括波涛线,表明包名中的单词间的分隔。例如,is-array 表明一个叫 is-array 的包。
- 包名前能够添加 @ 符号和用户名,表明这是一个用户发布的包。例如,@babel/core 表明 babel 用户发布的 core 包。
模板复制
当搜集完用户的输入后,开端依据参数进行文件创立,package.json 文件修正等操作。
const templateDir = path.resolve(
fileURLToPath(import.meta.url),
'../..',
`template-${template}`
)
const write = (file: string, content?: string) => {
const targetPath = path.join(root, renameFiles[file] ?? file)
if (content) {
fs.writeFileSync(targetPath, content)
} else {
copy(path.join(templateDir, file), targetPath)
}
}
函数的完成办法是经过 fs 模块来操作文件体系。主要流程如下:
- 计算文件的方针途径。经过 path.join 办法将文件名和方针目录的途径拼接起来,得到文件的方针途径。
- 判别文件是否存在。假如文件存在,则不履行任何操作;假如文件不存在,则进行下一步。
- 依据参数决议是否复制文件。假如 content 参数为空,则表明需求复制文件;不然,表明需求将内容写入文件。
- 履行写入操作。假如需求复制文件,则调用 copy 办法将源文件复制到方针文件;不然,调用 fs.writeFileSync 办法将内容写入文件。
其中,templateDir 变量表明模板文件的目录,renameFiles 变量表明文件重命名的映射表。两者都是经过函数的参数传递进来的。