create-vite是如何工作的?

239 阅读3分钟

如果用过vite的小伙伴肯定知道,要想起一个基于vite的应用,我们可以使用pnpm create vite,然后根据提示选择一个模板就可以在本地生成一个基本的project。那么这个东西怎么实现的呢?下面就来解读一下源码,实现代码还是挺简单的。

Repo:

github.com/vitejs/vite…

Version:

3.2.1

目录结构

image.png

解析:

程序所有的代码都在github.com/vitejs/vite… 这个文件里,一共443行,因为构建场景比较简单,所以代码量也不是很多。在开始介绍这个index.ts之前,我们先来分析下create-vite都依赖了哪些基础依赖库。

kolorist: 一个轻量级的让命令行输出带色彩的工具。相比较 chalk 而言,kolorist大小为57.7k,chalk则为43.8k

minimist: 一个解析命令行参数的工具。相比 commander ,minimist更轻量。

cross-spawn: 一个可以支持跨平台运行spawn函数的库

prompts: 一个轻量级的用户输入交互工具。

index.ts代码:

image.png

主要分析一下init函数:

  1. 通过minimist读取用户的命令行参数,比如当执行pnpm create vite时有没有指定工程名,如果没有指定那就用默认的vite-project
let targetDir = argTargetDir || defaultTargetDir
const getProjectName = () =>
targetDir === '.' ? path.basename(path.resolve()) : targetDir
  1. 提供一系列指令传给prompts,比如让user提供project的名字,然后选择要基于哪个模板生成。
try {
    result = await prompts(
      [
        {
          type: argTargetDir ? null : 'text',
          name: 'projectName',
          message: reset('Project name:'),
          initial: defaultTargetDir,
          onState: (state) => {
            targetDir = formatTargetDir(state.value) || defaultTargetDir
          }
        },
        {
          type: () =>
            !fs.existsSync(targetDir) || isEmpty(targetDir) ? null : 'confirm',
          name: 'overwrite',
          message: () =>
            (targetDir === '.'
              ? 'Current directory'
              : `Target directory "${targetDir}"`) +
            ` is not empty. Remove existing files and continue?`
        },
        {
          type: (_, { overwrite }: { overwrite?: boolean }) => {
            if (overwrite === false) {
              throw new Error(red('✖') + ' Operation cancelled')
            }
            return null
          },
          name: 'overwriteChecker'
        },
        {
          type: () => (isValidPackageName(getProjectName()) ? null : 'text'),
          name: 'packageName',
          message: reset('Package name:'),
          initial: () => toValidPackageName(getProjectName()),
          validate: (dir) =>
            isValidPackageName(dir) || 'Invalid package.json name'
        },
        {
          type:
            argTemplate && TEMPLATES.includes(argTemplate) ? null : 'select',
          name: 'framework',
          message:
            typeof argTemplate === 'string' && !TEMPLATES.includes(argTemplate)
              ? reset(
                  `"${argTemplate}" isn't a valid template. Please choose from below: `
                )
              : reset('Select a framework:'),
          initial: 0,
          choices: FRAMEWORKS.map((framework) => {
            const frameworkColor = framework.color
            return {
              title: frameworkColor(framework.display || framework.name),
              value: framework
            }
          })
        },
        {
          type: (framework: Framework) =>
            framework && framework.variants ? 'select' : null,
          name: 'variant',
          message: reset('Select a variant:'),
          choices: (framework: Framework) =>
            framework.variants.map((variant) => {
              const variantColor = variant.color
              return {
                title: variantColor(variant.display || variant.name),
                value: variant.name
              }
            })
        }
      ],
      {
        onCancel: () => {
          throw new Error(red('✖') + ' Operation cancelled')
        }
      }
    )
  } catch (cancelled: any) {
    console.log(cancelled.message)
    return
  }
  1. 拿到第2步user选择的结果后,就去create-vite文件夹中找到对应的模板的文件夹目录,将它copy到用户提供的project名字的目录下。

    举个🌰,framework我选了React,variant我选了typescript,那么程序就会去找template-react-ts这个文件夹并将它copy到我们提供的project文件夹下。当然这里面少不了文件夹是否存在,或者需要override的检查。

于此,整个过程就结束了,接下来就是需要我们developer自己进去这个create-vite给我们生成的那个文件夹里面执行pnpm install 安装好包之后就可以启动服务了。

整个流程是不是很简单?哈哈哈。

打包

接着咱们再来看一下create-vite是如何打包输出dist目录的,首先查看下package.json文件。

{
  "name": "create-vite",
  "version": "3.2.1",
  "type": "module", // 开启ESM支持
  "license": "MIT",
  "author": "Evan You",
  "bin": {
    "create-vite": "index.js", // 我们执行的create-vite其实就是取找当前目录下的index.js文件
    "cva": "index.js"
  },
  "files": [
    "index.js",
    "template-*",
    "dist"
  ],
  "main": "index.js", // 主入口
  "scripts": {
    "dev": "unbuild --stub",
    "build": "unbuild", // unbuild是一个基于rollup打包的工具,可以生成esm和commonjs的代码
    "prepublishOnly": "npm run build"
  },
  "engines": {
    "node": "^14.18.0 || >=16.0.0"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/vitejs/vite.git",
    "directory": "packages/create-vite"
  },
  "bugs": {
    "url": "https://github.com/vitejs/vite/issues"
  },
  "homepage": "https://github.com/vitejs/vite/tree/main/packages/create-vite#readme",
  "devDependencies": {
    "cross-spawn": "^7.0.3",
    "kolorist": "^1.6.0",
    "minimist": "^1.2.7",
    "prompts": "^2.4.2"
  }
}

当执行npm run build的时候,unbuild会读取src/index.ts文件将其编译输出到dist目录下的index.mjs文件,之后当pnpm create vite的时候其实就是去执行npm包的那个bin命令,然后去执行index.mjs文件,至此,大功告成!