携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第8天,点击查看活动详情
- 本文参加了由公众号@若川视野 发起的每周源码共读活动, 点击了解详情一起参与。
- 这是源码共读的第37期,链接:juejin.cn/post/712519…
作用
创建vite项目模板
使用
npm create vite
试过在cmd上 npm create vite@lastest 结果报错了耶,上面的就可以
提问是否下载 create-vite
修改项目名 默认vite-project
选择框架 vue
选择variant变体 vue 、 vue-ts
流程
收集项目名、具体框架
将模板文件写入系统
源码
获取输出路径/项目名
// 高版本的node支持,node 前缀
import fs from 'node:fs'
import path from 'node:path'
import { fileURLToPath } from 'node:url'
// 解析命令行的参数 链接:https://npm.im/minimist
import minimist from 'minimist'
// 询问选择之类的 链接:https://npm.im/prompts
import prompts from 'prompts'
// 拿到 输入在 create-vite 后的 参数
const argv = minimist(process.argv.slice(2), { string: ['_'] })
const cwd = process.cwd()
let targetDir = formatTargetDir(argv._[0])
// 意思是参数输入. 则就是当前目录为项目名,不然就是输入的
const getProjectName = () =>
//path.resolve() 为当前工作目录的绝对路径 https://blog.csdn.net/qq_40963664/article/details/124420729
//path.basename() 为最后一截路径 https://vimsky.com/examples/usage/node-js-path-basename-method.html
//合起来就是当前目录
targetDir === '.' ? path.basename(path.resolve()) : targetDir
询问项目名、选择框架,选择框架变体等
//问询,如果没有在参数输入项目名,则在问询区输入
result = await prompts(
[
{
type: targetDir ? null : 'text',
name: 'projectName',
message: reset('Project name:'),
initial: defaultTargetDir,
onState: (state) => {
targetDir = formatTargetDir(state.value) || defaultTargetDir
}
},
// framework 框架
// overwrite 已有目录,是否重写
// packageName 输入的项目名
// variant 变体, 比如 react => react-ts
const { framework, overwrite, packageName, variant } = result
清空、创建输出目录
const root = path.join(cwd, targetDir)
// 是否覆盖,如果当前目录出现相同名字的输出目录,前面会问询是否要覆盖,若要覆盖
if (overwrite) {
//清空文件夹里的文件
emptyDir(root)
} else if (!fs.existsSync(root)) {
// 不存在输出目录,则创建一个
fs.mkdirSync(root, { recursive: true })
}
// 递归删除文件夹,相当于 rm -rf xxx。
function emptyDir(dir) {
if (!fs.existsSync(dir)) {
return
}
for (const file of fs.readdirSync(dir)) {
fs.rmSync(path.resolve(dir, file), { recursive: true, force: true })
}
}
获取模板文件
// 参数有用参数 、 不然用问询的
template = variant || framework || template
// 当前路径 上级 template-${template}文件夹
const templateDir = path.resolve(
fileURLToPath(import.meta.url),
'..',
`template-${template}`
)
写如模板文件
const renameFiles = {
//具体看上图,因为.前缀文件会被隐藏,有些系统是读取不到的,所以这里改成_,但是写入的时候要改回来
_gitignore: '.gitignore'
}
const write = (file, content) => {
// renameFile
const targetPath = renameFiles[file]
? path.join(root, renameFiles[file])
: path.join(root, file)
if (content) {
fs.writeFileSync(targetPath, content)
} else {
copy(path.join(templateDir, file), targetPath)
}
}
function copy(src, dest) {
const stat = fs.statSync(src)
if (stat.isDirectory()) {
copyDir(src, dest)
} else {
fs.copyFileSync(src, dest)
}
}
/**
* @param {string} srcDir
* @param {string} destDir
*/
function copyDir(srcDir, destDir) {
fs.mkdirSync(destDir, { recursive: true })
for (const file of fs.readdirSync(srcDir)) {
const srcFile = path.resolve(srcDir, file)
const destFile = path.resolve(destDir, file)
copy(srcFile, destFile)
}
}
项目名写入package.json
package.json 文件单独处理。 它的名字为输入的 packageName 或者获取。
const files = fs.readdirSync(templateDir)
for (const file of files.filter((f) => f !== 'package.json')) {
write(file)
}
const pkg = JSON.parse(
fs.readFileSync(path.join(templateDir, `package.json`), 'utf-8')
)
pkg.name = packageName || getProjectName()
write('package.json', JSON.stringify(pkg, null, 2))
安装完成庆祝
提示安装依赖
//使用了什么包管理器创建项目
const pkgInfo = pkgFromUserAgent(process.env.npm_config_user_agent)
const pkgManager = pkgInfo ? pkgInfo.name : 'npm'
console.log(`\nDone. Now run:\n`)
if (root !== cwd) {
console.log(` cd ${path.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
}
console.log()
function pkgFromUserAgent(userAgent) {
if (!userAgent) return undefined
const pkgSpec = userAgent.split(' ')[0]
const pkgSpecArr = pkgSpec.split('/')
return {
name: pkgSpecArr[0],
version: pkgSpecArr[1]
}
}
总结
类似第九期create-vue@next
为了保证轻量快速,源码中很多函数都是自己写的。比如删除文件和文件夹