【若川视野 x 源码共读】第37期 | create-vite

263 阅读3分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第8天,点击查看活动详情

作用

创建vite项目模板

使用

npm create vite
试过在cmd上 npm create vite@lastest 结果报错了耶,上面的就可以
提问是否下载 create-vite 
修改项目名 默认vite-project
选择框架 	vue
选择variant变体  vue 、 vue-ts

流程

收集项目名、具体框架

将模板文件写入系统

源码

github1s.com/vitejs/vite…

获取输出路径/项目名

// 高版本的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}`
  )

image-20220808232451794.png

写如模板文件

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

为了保证轻量快速,源码中很多函数都是自己写的。比如删除文件和文件夹