Vite創(chuàng)建項目的實現(xiàn)步驟
前言
隨著 Vite2 的發(fā)布并日趨穩(wěn)定,現(xiàn)在越來越多的項目開始嘗試使用它。我們使用 Vite 是一般會用下面這些命令去創(chuàng)建一個項目:
// 使用 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
運行這些命令后就會生成一個項目文件夾,對于大多數(shù)人可能覺得只要能正常創(chuàng)建一個項目就夠了,但我出于好奇,為什么運行這些命令就會生成一個項目文件夾。這里以 yarn 為例創(chuàng)建項目進行說明。
yarn create 做了什么
可能很多人會疑惑,為什么很多項目的創(chuàng)建方式都是使用yarn create這個命令進行創(chuàng)建。除了這里的 Vite,我們創(chuàng)建 React 項目也是這樣:yarn create react-app my-app。那這個命令到底做了什么,它其實做了兩件事:
yarn global add create-react-app create-react-app my-app
關于yarn create的更多內容可以看這里
源碼解析
yarn create @vitejs/app命令運行后就會執(zhí)行@vitejs/create-app里的代碼。我們先看看這文件的項目結構

template 開頭的文件夾都是各個框架和對應的typescript版本的項目模板,我們不用太關心,創(chuàng)建項目的邏輯都在 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則分別是第三方依賴庫。
模版配置
接下來不同框架模版的配置文件,最后生成一個模版名稱的數(shù)組。
// 這里只寫了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 文件的特殊性,每種框架項目模版下都是先創(chuàng)建的 _gitignore 文件,在后續(xù)創(chuàng)建項目的時候再替換為 .gitignore。所以,代碼里會預先定義一個對象來存放需要重命名的文件:
const renameFiles = {
_gitignore: '.gitignore'
}
工具函數(shù)
在開始講的核心函數(shù)之前,先來看看代碼中定義的工具函數(shù)。最重要的是與文件操作相關的三個函數(shù)。
copy
function copy(src, dest) {
const stat = fs.statSync(src)
if (stat.isDirectory()) {
copyDir(src, dest)
} else {
fs.copyFileSync(src, dest)
}
}
copy函數(shù)則用于復制文件或文件夾 src 到指定文件夾 dest。它會先獲取 src 的狀態(tài) stat,如果 src 是文件夾的話,即stat.isDirectory()為 true 時,則會調用下面將介紹的copyDir函數(shù)來復制 src 文件夾下的文件到 dest 文件夾下。反之,src 是文件的話,則直接調用 fs.copyFileSync 函數(shù)復制 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函數(shù)用于將某個文件夾 srcDir 中的文件復制到指定文件夾 destDir 中。它會先調用 fs.mkdirSync函數(shù)來創(chuàng)建制定的文件夾,然后調用fs.readdirSync從 srcDir 文件夾下獲取的文件并遍歷逐個復制;最后在調用copy函數(shù)進行復制,這里用到了遞歸,因為可能存在文件夾里的文件還是文件夾。
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函數(shù)用于清空 dir 文件夾下的代碼。它會先判斷 dir 文件夾是否存在,存在則遍歷該問文件夾下的文件,構造該文件的路徑 abs,當 abs 為文件夾時,會遞歸調用 emptyDir 函數(shù)刪除該文件夾下的文件,然后再調用fs.rmdirSync刪除該文件夾;當 abs 是文件時,則調用fs.unlinkSync函數(shù)來刪除該文件。
核心函數(shù)
接下來就是核心功能實現(xiàn)的init函數(shù)。
命令行交互并創(chuàng)建文件夾
首先是獲取命令行參數(shù)
let targetDir = argv._[0] let template = argv.template || argv.t const defaultProjectName = !targetDir ? 'vite-project' : targetDir
argv._[0] 代表 @vitejs/app 后的第一個參數(shù)
template則是要使用的模版名稱
defaultProjectName則是我們創(chuàng)建的項目名稱。
接下來就是使用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函數(shù)構建的完整文件路徑
overwrite是針對已存在我們要創(chuàng)建的同名文件時,是否要重寫,如果重寫,則調用前面的emptyDir函數(shù)清空該文件夾,如果不存在該文件夾,則調用fs.mkdirSync創(chuàng)建文件夾
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'
// 輸出一些提示告訴你項目已經創(chuàng)建結束,以及告訴你接下來啟動項目需要運行的命令
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函數(shù)則接受兩個參數(shù) file 和 content,它有兩個功能:
- 對指定的文件 file 寫入指定的內容 content,調用fs.writeFileSync函數(shù)來實現(xiàn)將內容寫入文件。
- 復制模版文件夾下的文件到指定文件夾下,調用前面介紹的copy函數(shù)來實現(xiàn)文件的復制。
然后調用fs.readdirSync讀取模版文件夾里的文件,遍歷逐一復制到項目文件夾(其中要過濾的 package.json 文件,因為其中的 name 字段要修改);最后再寫入 package.json 文件。
小結
Vite 的create-app包的實現(xiàn)只有320行左右的代碼,但它考慮到各種場景的兼容處理;在學習完之后,自己去實現(xiàn)一個這樣的CLI工具也不是什么難事。
到此這篇關于Vite創(chuàng)建項目的實現(xiàn)步驟的文章就介紹到這了,更多相關Vite創(chuàng)建項目內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
Vue3通過hooks方式封裝節(jié)流和防抖的代碼詳解
vue3 中的 hooks 就是函數(shù)的一種寫法,就是將文件的一些單獨功能的js代碼進行抽離出來,放到單獨的js文件中,或者說是一些可以復用的公共方法/功能,本文給大家介紹了Vue3通過hooks方式封裝節(jié)流和防抖,需要的朋友可以參考下2024-10-10
在vue3中使用el-tree-select實現(xiàn)樹形下拉選擇器效果
el-tree-select是一個含有下拉菜單的樹形選擇器,結合了?el-tree?和?el-select?兩個組件的功能,這篇文章主要介紹了在vue3中使用el-tree-select做一個樹形下拉選擇器,需要的朋友可以參考下2024-03-03

