如果用过vite的小伙伴肯定知道,要想起一个基于vite的应用,我们可以使用pnpm create vite,然后根据提示选择一个模板就可以在本地生成一个基本的project。那么这个东西怎么实现的呢?下面就来解读一下源码,实现代码还是挺简单的。
Repo:
Version:
3.2.1
目录结构
解析:
程序所有的代码都在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代码:
主要分析一下init函数:
- 通过
minimist读取用户的命令行参数,比如当执行pnpm create vite时有没有指定工程名,如果没有指定那就用默认的vite-project
let targetDir = argTargetDir || defaultTargetDir
const getProjectName = () =>
targetDir === '.' ? path.basename(path.resolve()) : targetDir
- 提供一系列指令传给
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
}
-
拿到第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文件,至此,大功告成!