前言
在vite源碼中,一共有三個(gè)工程,其中create-vite是一個(gè)輔助工具,用于快速創(chuàng)建模板工程,本小節(jié),一起來(lái)看下它是怎么實(shí)現(xiàn)的
更新進(jìn)度
公眾號(hào):更新至第5節(jié)
博客:更新至第2節(jié)
源碼獲取
源碼分析
在開(kāi)始分析源碼前,有一個(gè)前置知識(shí)點(diǎn)需要進(jìn)行下簡(jiǎn)要補(bǔ)充,即npm init、 npm create和npm exec都是什么
1-npm init等價(jià)于npm create,它們的完整寫(xiě)法為npm init <initializer>
2-npm init最終會(huì)以npm exec create-<initializer>的形式調(diào)用npm exec
3-npm exec的執(zhí)行流程
首先,從本地查找是否存在與create-<initializer>同名的包
如果找到包,則判斷package.json文件中設(shè)置的bin字段,當(dāng)為多入口時(shí),嘗試在key中查找與包名同名的key值并調(diào)用key對(duì)應(yīng)的value值指向的文件,否則對(duì)bin字段指向的文件直接進(jìn)行調(diào)用
如果找不到包,則嘗試從遠(yuǎn)程查找與create-<initializer>同名的包并下載到本地后重復(fù)上一步驟
現(xiàn)在,來(lái)看源碼
已知,vite是monorepo架構(gòu)的,因此其packages文件夾下的create-vite將會(huì)被單獨(dú)發(fā)布,故一定能命中該包,并且,該包的package.json的bin字段中存在與包name同名的create-vite,其對(duì)應(yīng)的值最終指向的是packages\create-vite\src\index.ts文件
{
"name": "create-vite",
...
"bin": {
"create-vite": "index.js",
"cva": "index.js"
}
...
}
進(jìn)入packages\create-vite\src\index.ts
首先獲取用戶傳遞的參數(shù)
const argv = minimist<{
t?: string;
template?: string;
}>(process.argv.slice(2), { string: ["_"] });
接著借助prompts通過(guò)控制臺(tái)收集用戶反饋
result = await prompts(...)
然后根據(jù)用戶輸入的反饋信息,到本地模板中進(jìn)行匹配,比如選擇了vue和typescript,最終拼接的結(jié)果就是:C:...\vite\packages\create-vite\template-vue-ts
const templateDir = path.resolve(
fileURLToPath(import.meta.url),
"../..",
`template-${template}`
);
獲取的這個(gè)路徑是指向create-vite包內(nèi)模版文件的絕對(duì)路徑

只需要使用node的文件讀取命令,將匹配的本地模板copy到用戶的目錄中就可以了
function copy(src: string, dest: string) {
const stat = fs.statSync(src);
if (stat.isDirectory()) {
/**
如果是目錄,則進(jìn)行遞歸
即遍歷目錄,重復(fù)調(diào)用write函數(shù)
*/
copyDir(src, dest);
} else {
fs.copyFileSync(src, dest);
}
}
const write = (file: string, ...) => {
const targetPath = path.join(root, renameFiles[file] ?? file);
...
copy(path.join(templateDir, file), targetPath);
};
// 讀取目錄
const files = fs.readdirSync(templateDir);
for (const file of files.filter((f) => f !== "package.json")) {
// 將目標(biāo)路徑下的資源寫(xiě)入到用戶目錄
write(file);
}
代碼實(shí)現(xiàn)
首先,我們?cè)?code>packages文件夾下新建create-vite目錄,并進(jìn)行初始化。如下,只保留三個(gè)參數(shù),這里就不再通過(guò)構(gòu)建工具進(jìn)行打包了
{
"name": "create-vite",
"version": "0.0.0",
"bin": "index.js"
}
接著,在packages\create-vite下新建index.js文件,它應(yīng)當(dāng)是一個(gè)立即執(zhí)行函數(shù)
(function () {
// TODO
})();
現(xiàn)在,來(lái)考慮參數(shù)部分,我并不打算提供太多客制化的內(nèi)容,只需要把倉(cāng)庫(kù)名稱作為一個(gè)變量交給用戶定義即可,這里使用--projectName來(lái)承載
(function () {
const args = process.argv.slice(2);
if (args.length && args.length === 2 && args[0] === "--projectName") {
return;
}
console.error("projectName缺失,請(qǐng)使用--projectName yourname");
})();
對(duì)于用戶傳遞的項(xiàng)目名稱,得將其拼接成絕對(duì)路徑
const projectName = require("path").join(process.cwd(), args[1]);
在create-vite中是將模板事先定義到本地并通過(guò)node的文件操作讀寫(xiě)到用戶本地的,我們這里換種方式,直接使用git將模板clone到用戶本地
為此,需要用到子進(jìn)程的exec函數(shù)執(zhí)行git clone,這里把筆者之前已經(jīng)配置好的vue3工程宕下來(lái)
check();
const exec = require("child_process").exec;
// 這里也可以通過(guò)控制臺(tái)交互的形式將要下載的地址外部化
exec(
"git clone https://github.com/supanpanCn/vue-blob.git",
{ clone: true },
(err) => {
if (err) {
console.error("模板下載失敗,請(qǐng)稍后重試", err);
} else {
adjustTemplate();
console.log("模板下載成功");
}
}
);
不過(guò)目前clone下來(lái)的文件夾名稱是倉(cāng)庫(kù)名,還需要按照收集到的信息對(duì)其進(jìn)行重命名,不過(guò)在此之前,得先判斷下目標(biāo)倉(cāng)庫(kù)是否已經(jīng)存在,如果存在,就對(duì)其重新進(jìn)行下命名并做相應(yīng)的提示
function check(projectName) {
const fs = require("fs");
const path = require("path");
const prefix = process.cwd();
const targetPath = path.resolve(prefix, projectName);
if (fs.existsSync(targetPath)) {
console.error("倉(cāng)庫(kù)已存在,重新命名為:projectName_1");
adjustTemplate(projectName, "projectName_1");
}
}
最后,在clone執(zhí)行成功后,調(diào)用adjustTemplate對(duì)文件名稱做修改就可以了
function adjustTemplate(oldName, newName) {
const fs = require("fs");
const path = require("path");
const prefix = process.cwd();
const oldPath = path.resolve(prefix, oldName);
const newPath = path.resolve(prefix, newName);
if (fs.existsSync(oldPath)) {
fs.renameSync(oldPath, newPath);
}
}
調(diào)試
在packages/create-vite/package.json下新建scripts
"scripts": {
"dev": "node ./index.js --projectName vite-project"
}
運(yùn)行dev,模板被從遠(yuǎn)程下載,并且,其名稱也從默認(rèn)的vue-blob修改成了指定的vite-project

總結(jié)
create-vite其實(shí)就是單純的在玩node的文件操作命令,將文件從位置A移動(dòng)到位置B
由于其并不清楚用戶想要?jiǎng)?chuàng)建的到底是哪一個(gè)模板,因此需要借助prompts來(lái)創(chuàng)建表單收集用戶信息以使其更準(zhǔn)確、更智能的工作