我正在参加会员专属活动-源码共读第一期,点击参加

本期的源码共读是 《揭秘 create-vite 原理》,这篇文章记录了学习进程中遇到的问题以及解决办法。

create-vite 是什么

create-vite 是一个用于创立 Vite 项目的脚手架东西。它的原理如下:

  1. create-vite 是一个指令行东西,能够经过 npm 指令来装置和运用。
  2. 在指令行中运转 create-vite 指令时,会提示用户输入项目名称和其他参数。
  3. create-vite 会依据用户输入的信息,在当前目录下创立一个新的 Vite 项目。
  4. 创立进程中,create-vite 会调用 Vite 库中的 API,完成项目的初始化和依赖装置。
  5. 创立完成后,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 程序进口

源码共读-揭秘 create-vite 原理

获取指令行参数

运用 minimist 包解析用户输入参数

源码共读-揭秘 create-vite 原理

程序履行开端,首要搜集指令行用户输入(项目名,模板),修正代码,如下:

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 模块来操作文件体系。主要流程如下:

  1. 计算文件的方针途径。经过 path.join 办法将文件名和方针目录的途径拼接起来,得到文件的方针途径。
  2. 判别文件是否存在。假如文件存在,则不履行任何操作;假如文件不存在,则进行下一步。
  3. 依据参数决议是否复制文件。假如 content 参数为空,则表明需求复制文件;不然,表明需求将内容写入文件。
  4. 履行写入操作。假如需求复制文件,则调用 copy 办法将源文件复制到方针文件;不然,调用 fs.writeFileSync 办法将内容写入文件。

其中,templateDir 变量表明模板文件的目录,renameFiles 变量表明文件重命名的映射表。两者都是经过函数的参数传递进来的。