《vite技術(shù)揭秘、還原與實(shí)戰(zhàn)》第2節(jié)--揭秘yarn create vite

前言

vite源碼中,一共有三個(gè)工程,其中create-vite是一個(gè)輔助工具,用于快速創(chuàng)建模板工程,本小節(jié),一起來(lái)看下它是怎么實(shí)現(xiàn)的

更新進(jìn)度

公眾號(hào):更新至第5節(jié)

博客:更新至第2節(jié)

源碼獲取

傳送門(mén)

源碼分析

在開(kāi)始分析源碼前,有一個(gè)前置知識(shí)點(diǎn)需要進(jìn)行下簡(jiǎn)要補(bǔ)充,即npm init、 npm createnpm 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)看源碼

已知,vitemonorepo架構(gòu)的,因此其packages文件夾下的create-vite將會(huì)被單獨(dú)發(fā)布,故一定能命中該包,并且,該包的package.jsonbin字段中存在與包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)行匹配,比如選擇了vuetypescript,最終拼接的結(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ì)路徑

image.png

只需要使用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

image.png

總結(jié)

create-vite其實(shí)就是單純的在玩node的文件操作命令,將文件從位置A移動(dòng)到位置B

由于其并不清楚用戶想要?jiǎng)?chuàng)建的到底是哪一個(gè)模板,因此需要借助prompts來(lái)創(chuàng)建表單收集用戶信息以使其更準(zhǔn)確、更智能的工作

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容