头图

前言

vite源码中,一共有三个工程,其中create-vite是一个辅助工具,用于快速创建模板工程,本小节,一起来看下它是怎么实现的

更新进度

公众号:更新至第5

博客:更新至第2

源码获取

传送门

源码分析

在开始分析源码前,有一个前置知识点需要进行下简要补充,即npm init、 npm createnpm exec都是什么

1-npm init等价于npm create,它们的完整写法为npm init <initializer>

2-npm init最终会以npm exec create-<initializer>的形式调用npm exec

3-npm exec的执行流程

首先,从本地查找是否存在与create-<initializer>同名的包

如果找到包,则判断package.json文件中设置的bin字段,当为多入口时,尝试在key中查找与包名同名的key值并调用key对应的value值指向的文件,否则对bin字段指向的文件直接进行调用

如果找不到包,则尝试从远程查找与create-<initializer>同名的包并下载到本地后重复上一步骤

现在,来看源码

已知,vitemonorepo架构的,因此其packages文件夹下的create-vite将会被单独发布,故一定能命中该包,并且,该包的package.jsonbin字段中存在与包name同名的create-vite,其对应的值最终指向的是packages\create-vite\src\index.ts文件

{
  "name": "create-vite",
  ...
  "bin": {
    "create-vite": "index.js",
    "cva": "index.js"
  }
  ...
}

进入packages\create-vite\src\index.ts

首先获取用户传递的参数

const argv = minimist<{
  t?: string;
  template?: string;
}>(process.argv.slice(2), { string: ["_"] });

接着借助prompts通过控制台收集用户反馈

result = await prompts(...)

然后根据用户输入的反馈信息,到本地模板中进行匹配,比如选择了vuetypescript,最终拼接的结果就是:C:...\vite\packages\create-vite\template-vue-ts

const templateDir = path.resolve(
  fileURLToPath(import.meta.url),
  "../..",
  `template-${template}`
);

获取的这个路径是指向create-vite包内模版文件的绝对路径

image.png

只需要使用node的文件读取命令,将匹配的本地模板copy到用户的目录中就可以了

function copy(src: string, dest: string) {
  const stat = fs.statSync(src);
  if (stat.isDirectory()) {
    /**
      如果是目录,则进行递归
      即遍历目录,重复调用write函数
    */
    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")) {
  // 将目标路径下的资源写入到用户目录
  write(file);
}

代码实现

首先,我们在packages文件夹下新建create-vite目录,并进行初始化。如下,只保留三个参数,这里就不再通过构建工具进行打包了

{
  "name": "create-vite",
  "version": "0.0.0",
  "bin": "index.js"
}

接着,在packages\create-vite下新建index.js文件,它应当是一个立即执行函数

(function () {
  // TODO
})();

现在,来考虑参数部分,我并不打算提供太多客制化的内容,只需要把仓库名称作为一个变量交给用户定义即可,这里使用--projectName来承载

(function () {
  const args = process.argv.slice(2);
  if (args.length && args.length === 2 && args[0] === "--projectName") {
    return;
  }
  console.error("projectName缺失,请使用--projectName yourname");
})();

对于用户传递的项目名称,得将其拼接成绝对路径

const projectName = require("path").join(process.cwd(), args[1]);

create-vite中是将模板事先定义到本地并通过node的文件操作读写到用户本地的,我们这里换种方式,直接使用git将模板clone到用户本地

为此,需要用到子进程的exec函数执行git clone,这里把笔者之前已经配置好的vue3工程宕下来

check();
const exec = require("child_process").exec;
// 这里也可以通过控制台交互的形式将要下载的地址外部化
exec(
  "git clone https://github.com/supanpanCn/vue-blob.git",
  { clone: true },
  (err) => {
    if (err) {
      console.error("模板下载失败,请稍后重试", err);
    } else {
      adjustTemplate();
      console.log("模板下载成功");
    }
  }
);

不过目前clone下来的文件夹名称是仓库名,还需要按照收集到的信息对其进行重命名,不过在此之前,得先判断下目标仓库是否已经存在,如果存在,就对其重新进行下命名并做相应的提示

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("仓库已存在,重新命名为:projectName_1");
    adjustTemplate(projectName, "projectName_1");
  }
}

最后,在clone执行成功后,调用adjustTemplate对文件名称做修改就可以了

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);
  }
}

调试

packages/create-vite/package.json下新建scripts

"scripts": {
    "dev": "node ./index.js --projectName vite-project"
}

运行dev,模板被从远程下载,并且,其名称也从默认的vue-blob修改成了指定的vite-project

image.png

总结

create-vite其实就是单纯的在玩node的文件操作命令,将文件从位置A移动到位置B

由于其并不清楚用户想要创建的到底是哪一个模板,因此需要借助prompts来创建表单收集用户信息以使其更准确、更智能的工作


Sir_苏
4 声望2 粉丝