前言
在vite
源码中,一共有三个工程,其中create-vite
是一个辅助工具,用于快速创建模板工程,本小节,一起来看下它是怎么实现的
更新进度
公众号:更新至第5
节
博客:更新至第2
节
源码获取
源码分析
在开始分析源码前,有一个前置知识点需要进行下简要补充,即npm init
、 npm create
和npm 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>
同名的包并下载到本地后重复上一步骤
现在,来看源码
已知,vite
是monorepo
架构的,因此其packages
文件夹下的create-vite
将会被单独发布,故一定能命中该包,并且,该包的package.json
的bin
字段中存在与包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(...)
然后根据用户输入的反馈信息,到本地模板中进行匹配,比如选择了vue
和typescript
,最终拼接的结果就是:C:...\vite\packages\create-vite\template-vue-ts
const templateDir = path.resolve(
fileURLToPath(import.meta.url),
"../..",
`template-${template}`
);
获取的这个路径是指向create-vite
包内模版文件的绝对路径
只需要使用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
总结
create-vite
其实就是单纯的在玩node
的文件操作命令,将文件从位置A
移动到位置B
由于其并不清楚用户想要创建的到底是哪一个模板,因此需要借助prompts
来创建表单收集用户信息以使其更准确、更智能的工作
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。