【若川视野 x 源码共读】第37期 | create-vite
- 本文参加了由公众号@若川视野 发起的每周源码共读活动,点击了解详情一起参与。
- 这是源码共读的第37期,链接
node新手学习大佬是怎么写脚手架的
//源码地址
https://github.com/vitejs/vite/blob/HEAD/packages/create-vite/index.js
1.工具函数
格式化文件夹名
/**
* @param {string | undefined} targetDir
*/
function formatTargetDir(targetDir) {
return targetDir?.trim().replace(/\/+$/g, '')
}
formatTargetDir(' /d/ ')
// '/d'
递归复制src目录下的文件以及文件夹内容到dest目录下
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)
}
}
清空文件夹内容,对git的配置都做了保留
/**
* @param {string} dir
*/
function emptyDir(dir) {
if (!fs.existsSync(dir)) {
return
}
for (const file of fs.readdirSync(dir)) {
if (file === '.git') {
continue
}
fs.rmSync(path.resolve(dir, file), { recursive: true, force: true })
}
}
function isEmpty(path) {
const files = fs.readdirSync(path)
return files.length === 0 || (files.length === 1 && files[0] === '.git')
}
对包名做规范检查以及兼容处理
/**
* @param {string} projectName
*/
function isValidPackageName(projectName) {
return /^(?:@[a-z0-9-*~][a-z0-9-*._~]*\/)?[a-z0-9-~][a-z0-9-._~]*$/.test(
projectName
)
}
/**
* @param {string} projectName
*/
function toValidPackageName(projectName) {
return projectName
.trim()
.toLowerCase()
.replace(/\s+/g, '-')
.replace(/^[._]/, '')
.replace(/[^a-z0-9-~]+/g, '-')
}
pkgFromUserAgent函数
/**
* @param {string | undefined} userAgent process.env.npm_config_user_agent
* @returns object | undefined
*/
function pkgFromUserAgent(userAgent) {
if (!userAgent) return undefined
const pkgSpec = userAgent.split(' ')[0]
const pkgSpecArr = pkgSpec.split('/')
return {
name: pkgSpecArr[0],
version: pkgSpecArr[1]
}
}
const pkgInfo = pkgFromUserAgent(process.env.npm_config_user_agent)
const pkgManager = pkgInfo ? pkgInfo.name : 'npm'
// 检查脚本环境的包管理器类型
fileURLToPath(import.meta.url) //为了找回模板的路径
// fileURLToPath 解码百分比编码字符,并确保跨平台有效的绝对路径字符串。
// import.meta.url 当前模块文件 URL
- 从工具函数中可以看出大佬做了不少的代码防御
- 基本都用了fs的同步写法操作文件,便捷和性能的取舍
2.minimist 解析命令行参数, 便捷配置项目名和模板
// index.js
const argv = minimist(process.argv.slice(2), { string: ['_','v'] }) // _,v内的参数都转成字符串类型
// node index.js a 3 4 -v 6
{ _: [ 'a', '3', '4' ], v: '6' }
{ _: [ 'a', 3, 4 ], v: 6 } // 没配置string的效果
3.prompts 命令行交互
轻量级、美观、用户友好的交互式提示
try {
result = await prompts(
[
{
type: () =>
!fs.existsSync(targetDir) || isEmpty(targetDir) ? null : 'confirm',
name: 'overwrite',
message: () =>
(targetDir === '.'
? 'Current directory'
: `Target directory "${targetDir}"`) +
` is not empty. Remove existing files and continue?`
},
{
type: (_, { overwrite } = {}) => {
if (overwrite === false) {
throw new Error(red('✖') + ' Operation cancelled')
}
return null
},
name: 'overwriteChecker'
},
{
type: () => (isValidPackageName(getProjectName()) ? null : 'text'),
name: 'packageName',
message: reset('Package name:'),
initial: () => toValidPackageName(getProjectName()),
validate: (dir) =>
isValidPackageName(dir) || 'Invalid package.json name'
},
],
{
onCancel: () => {
throw new Error(red('✖') + ' Operation cancelled')
}
}
)
} catch (cancelled) {
console.log(cancelled.message)
return
}
- 利用type支持的函数类型动态设置类型
- overwrite覆盖提醒
- overwrite设置为否后终止交互以及利用catch里面的return终止执行后续代码
- 项目名称校验格式化
- prompts选择template的部分略过了....
4.最后就是根据输入的命令进行模板拷贝到新建项目中以及动态设置package.json
前面做的的一切都是为这步做准备
const write = (file, content) => {
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)
}
}
for (const file of files.filter((f) => f !== 'package.json')) {
write(file)
}
// 配置项目名到json文件
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))
大佬配置各种vite模板
vanillavanilla-tsvuevue-tsreactreact-tspreactpreact-tslitlit-tssveltesvelte-ts