Vite创建项目的实现步骤
作者:辣比老章 发布时间:2024-04-30 10:38:16
目录
前言
yarn create 做了什么
源码解析
项目依赖
模版配置
工具函数
copy
copyDir
emptyDir
核心函数
命令行交互并创建文件夹
写入文件
小结
前言
随着 Vite2 的发布并日趋稳定,现在越来越多的项目开始尝试使用它。我们使用 Vite 是一般会用下面这些命令去创建一个项目:
// 使用 npm
npm init @vitejs/app
// 使用 yarn
yarn create @vitejs/app
// 想指定项目名称和使用某个特定框架的模版时,可以像下面这样
// npm
npm init @vitejs/app my-vue-app --template vue
// yarn
yarn create @vitejs/app my-vue-app --template vue
运行这些命令后就会生成一个项目文件夹,对于大多数人可能觉得只要能正常创建一个项目就够了,但我出于好奇,为什么运行这些命令就会生成一个项目文件夹。这里以 yarn 为例创建项目进行说明。
yarn create 做了什么
可能很多人会疑惑,为什么很多项目的创建方式都是使用yarn create这个命令进行创建。除了这里的 Vite,我们创建 React 项目也是这样:yarn create react-app my-app
。
那这个命令到底做了什么,它其实做了两件事:
yarn global add create-react-app
create-react-app my-app
关于yarn create的更多内容可以看这里
源码解析
yarn create @vitejs/app
命令运行后就会执行@vitejs/create-app里的代码。我们先看看这文件的项目结构
template 开头的文件夹都是各个框架和对应的typescript版本的项目模板,我们不用太关心,创建项目的逻辑都在 index.js 文件里。下面就来看看这里面都做了什么
项目依赖
首先是依赖的引入
const fs = require('fs')
const path = require('path')
const argv = require('minimist')(process.argv.slice(2))
const prompts = require('prompts')
const {
yellow,
green,
cyan,
blue,
magenta,
lightRed,
red
} = require('kolorist')
fs、path是Nodejs内置模块,minimist、prompts、kolorist则分别是第三方依赖库。
minimist:是一个用于解析命令行参数的工具。文档
prompts:是一个命令行交互的工具。文档
kolorist:是一个使命令行输出带有色彩的工具。文档
模版配置
接下来不同框架模版的配置文件,最后生成一个模版名称的数组。
// 这里只写了vue和react框架的配置,其他的都是差的不多,感兴趣可以去看源码。
const FRAMEWORKS = [
......
{
name: 'vue',
color: green,
variants: [
{
name: 'vue',
display: 'JavaScript',
color: yellow
},
{
name: 'vue-ts',
display: 'TypeScript',
color: blue
}
]
},
{
name: 'react',
color: cyan,
variants: [
{
name: 'react',
display: 'JavaScript',
color: yellow
},
{
name: 'react-ts',
display: 'TypeScript',
color: blue
}
]
},
......
]
// 输出模版名称列表
const TEMPLATES = FRAMEWORKS.map(
(f) => (f.variants && f.variants.map((v) => v.name)) || [f.name]
).reduce((a, b) => a.concat(b), [])
其次,由于 .gitignore 文件的特殊性,每种框架项目模版下都是先创建的 _gitignore 文件,在后续创建项目的时候再替换为 .gitignore。所以,代码里会预先定义一个对象来存放需要重命名的文件:
const renameFiles = {
_gitignore: '.gitignore'
}
工具函数
在开始讲的核心函数之前,先来看看代码中定义的工具函数。最重要的是与文件操作相关的三个函数。
copy
function copy(src, dest) {
const stat = fs.statSync(src)
if (stat.isDirectory()) {
copyDir(src, dest)
} else {
fs.copyFileSync(src, dest)
}
}
copy函数则用于复制文件或文件夹 src 到指定文件夹 dest。它会先获取 src 的状态 stat,如果 src 是文件夹的话,即stat.isDirectory()为 true 时,则会调用下面将介绍的copyDir函数来复制 src 文件夹下的文件到 dest 文件夹下。反之,src 是文件的话,则直接调用 fs.copyFileSync 函数复制 src 文件到 dest 文件夹下。
copyDir
function copyDir(srcDir, destDir) {
fs.mkdirSync(destDir, { recursive: true })
for (const file of fs.readdirSync(srcDir)) {
const srcFile = path.resolve(srcDir, file)
const destFile = path.resolve(destDir, file)
copy(srcFile, destFile)
}
}
copyDir函数用于将某个文件夹 srcDir 中的文件复制到指定文件夹 destDir 中。它会先调用 fs.mkdirSync函数来创建制定的文件夹,然后调用fs.readdirSync从 srcDir 文件夹下获取的文件并遍历逐个复制;最后在调用copy函数进行复制,这里用到了递归,因为可能存在文件夹里的文件还是文件夹。
emptyDir
function emptyDir(dir) {
if (!fs.existsSync(dir)) {
return
}
for (const file of fs.readdirSync(dir)) {
const abs = path.resolve(dir, file)
if (fs.lstatSync(abs).isDirectory()) {
emptyDir(abs)
fs.rmdirSync(abs)
} else {
fs.unlinkSync(abs)
}
}
}
emptyDir函数用于清空 dir 文件夹下的代码。它会先判断 dir 文件夹是否存在,存在则遍历该问文件夹下的文件,构造该文件的路径 abs,当 abs 为文件夹时,会递归调用 emptyDir 函数删除该文件夹下的文件,然后再调用fs.rmdirSync删除该文件夹;当 abs 是文件时,则调用fs.unlinkSync函数来删除该文件。
核心函数
接下来就是核心功能实现的init函数。
命令行交互并创建文件夹
首先是获取命令行参数
let targetDir = argv._[0]
let template = argv.template || argv.t
const defaultProjectName = !targetDir ? 'vite-project' : targetDir
argv._[0] 代表 @vitejs/app 后的第一个参数
template则是要使用的模版名称
defaultProjectName则是我们创建的项目名称。
接下来就是使用prompts包来在命令行中输出询问,像下面这样:
具体代码如下:
// 关于命令行交互的部分代码没有全部放在这里,感兴趣的可以去看源码
let result = {}
result = await prompts(
[
{
type: targetDir ? null : 'text',
name: 'projectName',
message: 'Project name:',
initial: defaultProjectName,
onState: (state) =>
(targetDir = state.value.trim() || defaultProjectName)
},
......
]
)
const { framework, overwrite, packageName, variant } = result
const root = path.join(cwd, targetDir)
if (overwrite) {
emptyDir(root)
} else if (!fs.existsSync(root)) {
fs.mkdirSync(root)
}
template = variant || framework || template
// 输出项目文件夹路径
console.log(`\nScaffolding project in ${root}...`)
const templateDir = path.join(__dirname, `template-${template}`)
选择完成后会返回我们选择的结果result
root是通过path.join函数构建的完整文件路径
overwrite是针对已存在我们要创建的同名文件时,是否要重写,如果重写,则调用前面的emptyDir函数清空该文件夹,如果不存在该文件夹,则调用fs.mkdirSync创建文件夹
templateDir选择的模版文件夹名称
写入文件
const write = (file, content) => {
const targetPath = renameFiles[file]
? path.join(root, renameFiles[file])
: path.join(root, file)
if (content) {
fs.writeFileSync(targetPath, content)
} else {
copy(path.join(templateDir, file), targetPath)
}
}
const files = fs.readdirSync(templateDir)
for (const file of files.filter((f) => f !== 'package.json')) {
write(file)
}
const pkg = require(path.join(templateDir, `package.json`))
pkg.name = packageName
write('package.json', JSON.stringify(pkg, null, 2))
const pkgManager = /yarn/.test(process.env.npm_execpath) ? 'yarn' : 'npm'
// 输出一些提示告诉你项目已经创建结束,以及告诉你接下来启动项目需要运行的命令
console.log(`\nDone. Now run:\n`)
if (root !== cwd) {
console.log(` cd ${path.relative(cwd, root)}`)
}
console.log(` ${pkgManager === 'yarn' ? `yarn` : `npm install`}`)
console.log(` ${pkgManager === 'yarn' ? `yarn dev` : `npm run dev`}`)
console.log()
write函数则接受两个参数 file 和 content,它有两个功能:
对指定的文件 file 写入指定的内容 content,调用fs.writeFileSync函数来实现将内容写入文件。
复制模版文件夹下的文件到指定文件夹下,调用前面介绍的copy函数来实现文件的复制。
然后调用fs.readdirSync读取模版文件夹里的文件,遍历逐一复制到项目文件夹(其中要过滤的 package.json 文件,因为其中的 name 字段要修改);最后再写入 package.json 文件。
小结
Vite 的create-app包的实现只有320行左右的代码,但它考虑到各种场景的兼容处理;在学习完之后,自己去实现一个这样的CLI工具也不是什么难事。
来源:https://juejin.cn/post/6983573067188404260


猜你喜欢
- 先放关键代码:i = tf.train.range_input_producer(NUM_EXPOCHES, num_epochs=1, s
- 本文实例为大家分享了python实现简单图片物体标注工具的具体代码,供大家参考,具体内容如下# coding: utf-8"&qu
- 零、配置Tomcat默认情况下Tomcat是没有配置用户角色权限的但是,后续Jenkins部署项目到Tomcat服务器,需要用到Tomcat
- defineExpose要在变量和方法声明定义之后再使用,否则浏览器的控制台会输出很多警告,并且最终将该页面卡死。[Vue3] define
- 在进行Web的交互设计中,颜色信息的传达也是不可或缺的一部分。我们常会发现许多“灰色”的应用,他们的出现总是不动声色而又恰如其分,维持了整个
- 一直对asyncio这个库比较感兴趣,毕竟这是官网也非常推荐的一个实现高并发的一个模块,python也是在python 3.4中引入了协程的
- 全文检索里的组件简介1. 什么是haystack?1. haystack是django的开源搜索框架,该框架支持Solr,Elasticse
- 概要贝叶斯网络是一种基于概率的图模型,可用于建立变量之间的条件概率关系。在拼写检查器中,贝叶斯网络可以通过建立一个隐含状态、错误观察值和正确
- 相对于Java方式的聊天室,Python同样可以做得到。而且可以做的更加的优雅。想必少了那么多的各种流的Python Socket,你一定会
- gettext 是GNU 提供的一套 国际化与本地化 处理的相关函数库。大多数语言都有对应的gettext实现。本文主要使用jed 来实现g
- 一、find_in_set() 函数详解示例:select FIND_IN_SET('1', '1,2,3'
- 本文实例讲述了Python实现html转换为pdf报告(生成pdf报告)功能。分享给大家供大家参考,具体如下:1、先说下html转换为pdf
- 这篇文章主要介绍了python如何实现不可变字典inmutabledict,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参
- 一、背景:近期在利用 pycharm 安装第三方库时会提示 pip 不是最新版本, 因此对 pip 进行更新,但是生成最新版本之后, pi
- 网上的关于django-scrapy的介绍比较少,该博客只在本人查资料的过程中学习的,如果不对之处,希望指出改正;以后的博客可能不会再出关于
- 前言本篇文章主要讲解vue响应式原理的逻辑,也就是vue怎么从最开始一步步推导出响应式的结构框架。 先从头构建一个简单函数推导出Vue3的R
- 当在函数中定义默认值时,值初始化只会进行一次,就是执行到def methodname时执行。看下面代码:from datetime impo
- MySQL编译参数多而复杂,让新手感到很头大,如果是正式生成环境安装MySQL,没有充足的时间去研究每一个参数代表的意义,个人建议使用余洪春
- 1. TokuFT file system space is really low and access is restricte
- 本文实例为大家分享了python实现飞机大战游戏的具体代码,供大家参考,具体内容如下1、准备环境下载python(这里建议不需要安装最新的,