- 本文参加了由公众号@若川视野 发起的每周源码共读活动,点击了解详情一起参与。
- 这是源码共读的第37期,链接:juejin.cn/post/712519…
create-vite
create-vite是什么呢,它是一个快速创建流行框架的工具。 主要是一些命令:(readme文件中获取的)
# npm 6.x
npm create vite@latest my-vue-app --template vue
# npm 7+, extra double-dash is needed:
npm create vite@latest my-vue-app -- --template vue
# yarn
yarn create vite my-vue-app --template vue
# pnpm
pnpm create vite my-vue-app --template vue
在读源码之前,我需要了解一些关于node的package.json知识,否则我很难阅读懂这个代码(拿到这个代码我怎么获取一些基础信息,代码的入口文件是什么等等),首先我们在生成一个node工程的时候,我们会使用npm init命令进行项目的初始化,填写一些必要信息,这个很重要,此过程会生成一个package.json文件,记录你的初始化信息。
package.json文件的作用:对项目或者模块包的描述,里面包含许多元信息。比如项目名称,项目版本,项目执行入口文件,项目贡献者等等。npm install 命令会根据这个文件下载所有依赖模块
我打开一个工程第一步就是看package.json文件,通过这个文件我可以知道工程的入口文件(main),工程的依赖dependencies(生产),devDependencies(开发),内部命令对应的可执行文件的路径(bin)。等等很多信息。 参考blog.csdn.net/song_6666/a…
那么通过分析package.json,入口文件为index.js,好家伙一看内容
import './dist/index.mjs'
这么简单吗,一看还没有dist文件夹,咋办,看package.json吧,看到了dev命令
"dev": "unbuild --stub",
一个新东西,就在www.npmjs.com/package/unb… 看了一下,有一个build.config.ts文件里面告诉了我们入口的文件是如下的这个文件:
entries: ['src/index'],
打包出来的文件如下:
好吧正式开始撕它。
简单的进行分类:
- 引入工具类(重点:minimist(解析命令参数) prompts(询问选择) kolorist(终端颜色打印))
- 变量定义
- 内置工具函数
- init函数(重点)
- 启动init
文章主要围绕内置的工具函数和init函数进行解读:
-
内置工具函数:
formatTargetDir:将字符串中末尾的反斜杠
/替换为空字符串(传入的参数是命令行第一个参数),最主要的是此函数里面用了trim,当年写代码没有意识,被很多空字符串坑惨了,看到了这里想起很多教训,才体会得到trim的重要性。copy:同步获取文件信息,判断是文件夹还是文件,文件直接复制,文件夹调用copyDir。
isValidPackageName:正则校验包名是否有效。
toValidPackageName:使用正则将包名变的有效
copyDir:此函数先创建一个文件夹,然后将传入的文件夹路径中的信息读取再次调用copy函数,和递归很类似。
isEmpty:判断文件是否为空,当文件存在一个且只为git文件时,为空时返回true。
emptyDir:清除文件夹,但是不清除git文件。
pkgFromUserAgent:通过用户代理判断返回对应的命令(npm yarn pnpm)
-
init函数流程梳理:
首先使用解析一下命令行参数, 将初始值读取到:如图示例就将对应的创建文件名称读取到了
然后使用prompts库函数进行对用户询问,没有的就进行选择,有的设置默认值让你进行确认,
const { framework, overwrite, packageName, variant } = result;
将用户填写的值取出来,然后合成路径
const root = _nodePath.default.join(cwd, targetDir);
根据选项是否进行文件夹清除或者递归创建文件夹:
if (overwrite) {
emptyDir(root);
} else if (!_nodeFs.default.existsSync(root)) {
_nodeFs.default.mkdirSync(root, { recursive: true });
}
确认用户要创建的js模板(vue等):
const template = variant || framework || argTemplate;
根据环境创建命令,就是根据你的电脑环境决定使用npm, yarn 或者pnpm来创建环境:
const pkgInfo = pkgFromUserAgent(process.env.npm_config_user_agent)
const pkgManager = pkgInfo ? pkgInfo.name : 'npm'
const isYarn1 = pkgManager === 'yarn' && pkgInfo?.version.startsWith('1.')
const { customCommand } =
FRAMEWORKS.flatMap((f) => f.variants).find((v) => v.name === template) ?? {}
if (customCommand) {
const fullCustomCommand = customCommand
.replace('TARGET_DIR', targetDir)
.replace(/^npm create/, `${pkgManager} create`)
// Only Yarn 1.x doesn't support `@version` in the `create` command
.replace('@latest', () => (isYarn1 ? '' : '@latest'))
.replace(/^npm exec/, () => {
// Prefer `pnpm dlx` or `yarn dlx`
if (pkgManager === 'pnpm') {
return 'pnpm dlx'
}
if (pkgManager === 'yarn' && !isYarn1) {
return 'yarn dlx'
}
// Use `npm exec` in all other cases,
// including Yarn 1.x and other custom npm clients.
return 'npm exec'
})
const [command, ...args] = fullCustomCommand.split(' ')
const { status } = spawn.sync(command, args, {
stdio: 'inherit'
})
process.exit(status ?? 0)
}
调试的时候发现了这样的情况,源代码为:
const templateDir = path.resolve(
fileURLToPath(import.meta.url),
'../..',
`template-${template}`
)
编译完成后:
const templateDir = _nodePath.default.resolve(
(0, _nodeUrl.fileURLToPath)("file:///D:/%E6%BA%90%E7%A0%81%E9%98%85%E8%AF%BB/vite-main/packages/create-vite/src/index.ts"),
'../..',
`template-${template}`);
其实就是获取对应的模板路径。
然后使用如下的代码,读取对应文件夹下的template-xxx,然后将其写到你填写的路径下去:
const write = (file, content) => {
const targetPath = _nodePath.default.join(root, renameFiles[file] ?? file);
if (content) {
_nodeFs.default.writeFileSync(targetPath, content);
} else {
copy(_nodePath.default.join(templateDir, file), targetPath);
}
};
const files = _nodeFs.default.readdirSync(templateDir);
for (const file of files.filter((f) => f !== 'package.json')) {
write(file);
}
单独处理了一下package.json文件:
const pkg = JSON.parse(
_nodeFs.default.readFileSync(_nodePath.default.join(templateDir, `package.json`), 'utf-8'));
pkg.name = packageName || getProjectName();
write('package.json', JSON.stringify(pkg, null, 2));
最后就是将信息打印一下,告诉你完成了:
if (root !== cwd) {
console.log(` cd ${_nodePath.default.relative(cwd, root)}`);
}
switch (pkgManager) {
case 'yarn':
console.log(' yarn');
console.log(' yarn dev');
break;
default:
console.log(` ${pkgManager} install`);
console.log(` ${pkgManager} run dev`);
break;}
-
总结:大佬还是牛,用了很少的库就完成了一份在外人看起来很复杂的工作,其实自己一读也没有那么复制,总之多看牛人的东西才可以促进自己进步,之前我一直觉得他到底时怎么把工程给创建的,想了很多,什么机器学习啥的都想过,天马行空的想法都有,但是其实大佬用了一个最简单的方式,就是我把所有的模板全部实现一下,让你们选择后copy一下就行,让我想到了一句话:想得多,困难会越来越多,做的多,困难会越来越少。