Vite创建项目的实现步骤

作者:辣比老章 时间:2024-04-30 10:38:16 

目录
  • 前言

  • yarn create 做了什么

  • 源码解析

    • 项目依赖

    • 模版配置

    • 工具函数

      • copy

      • copyDir

      • emptyDir

    • 核心函数

      • 命令行交互并创建文件夹

      • 写入文件

  • 小结

    前言

    随着 Vite2 的发布并日趋稳定,现在越来越多的项目开始尝试使用它。我们使用 Vite 是一般会用下面这些命令去创建一个项目:


    // 使用 npm
    npm init @vitejs/app
    // 使用 yarn
    yarn create @vitejs/app

    // 想指定项目名称和使用某个特定框架的模版时,可以像下面这样
    // npm
    npm init @vitejs/app my-vue-app --template vue
    // yarn
    yarn create @vitejs/app my-vue-app --template vue

    运行这些命令后就会生成一个项目文件夹,对于大多数人可能觉得只要能正常创建一个项目就够了,但我出于好奇,为什么运行这些命令就会生成一个项目文件夹。这里以 yarn 为例创建项目进行说明。

    yarn create 做了什么

    可能很多人会疑惑,为什么很多项目的创建方式都是使用yarn create这个命令进行创建。除了这里的 Vite,我们创建 React 项目也是这样:yarn create react-app my-app
    那这个命令到底做了什么,它其实做了两件事:


    yarn global add create-react-app
    create-react-app my-app

    关于yarn create的更多内容可以看这里

    源码解析

    yarn create @vitejs/app命令运行后就会执行@vitejs/create-app里的代码。我们先看看这文件的项目结构

    Vite创建项目的实现步骤

    template 开头的文件夹都是各个框架和对应的typescript版本的项目模板,我们不用太关心,创建项目的逻辑都在 index.js 文件里。下面就来看看这里面都做了什么

    项目依赖

    首先是依赖的引入


    const fs = require('fs')
    const path = require('path')
    const argv = require('minimist')(process.argv.slice(2))
    const prompts = require('prompts')
    const {
     yellow,
     green,
     cyan,
     blue,
     magenta,
     lightRed,
     red
    } = require('kolorist')

    fs、path是Nodejs内置模块,minimist、prompts、kolorist则分别是第三方依赖库。

    • minimist:是一个用于解析命令行参数的工具。文档

    • prompts:是一个命令行交互的工具。文档

    • kolorist:是一个使命令行输出带有色彩的工具。文档

    模版配置

    接下来不同框架模版的配置文件,最后生成一个模版名称的数组。


    // 这里只写了vue和react框架的配置,其他的都是差的不多,感兴趣可以去看源码。
    const FRAMEWORKS = [
     ......

    {
       name: 'vue',
       color: green,
       variants: [
         {
           name: 'vue',
           display: 'JavaScript',
           color: yellow
         },
         {
           name: 'vue-ts',
           display: 'TypeScript',
           color: blue
         }
       ]
     },
     {
       name: 'react',
       color: cyan,
       variants: [
         {
           name: 'react',
           display: 'JavaScript',
           color: yellow
         },
         {
           name: 'react-ts',
           display: 'TypeScript',
           color: blue
         }
       ]
     },

    ......
    ]

    // 输出模版名称列表
    const TEMPLATES = FRAMEWORKS.map(
     (f) => (f.variants && f.variants.map((v) => v.name)) || [f.name]
    ).reduce((a, b) => a.concat(b), [])

    其次,由于 .gitignore 文件的特殊性,每种框架项目模版下都是先创建的 _gitignore 文件,在后续创建项目的时候再替换为 .gitignore。所以,代码里会预先定义一个对象来存放需要重命名的文件:


    const renameFiles = {
     _gitignore: '.gitignore'
    }

    工具函数

    在开始讲的核心函数之前,先来看看代码中定义的工具函数。最重要的是与文件操作相关的三个函数。

    copy


    function copy(src, dest) {
     const stat = fs.statSync(src)
     if (stat.isDirectory()) {
       copyDir(src, dest)
     } else {
       fs.copyFileSync(src, dest)
     }
    }

    copy函数则用于复制文件或文件夹 src 到指定文件夹 dest。它会先获取 src 的状态 stat,如果 src 是文件夹的话,即stat.isDirectory()为 true 时,则会调用下面将介绍的copyDir函数来复制 src 文件夹下的文件到 dest 文件夹下。反之,src 是文件的话,则直接调用 fs.copyFileSync 函数复制 src 文件到 dest 文件夹下。

    copyDir


    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)
     }
    }

    copyDir函数用于将某个文件夹 srcDir 中的文件复制到指定文件夹 destDir 中。它会先调用 fs.mkdirSync函数来创建制定的文件夹,然后调用fs.readdirSync从 srcDir 文件夹下获取的文件并遍历逐个复制;最后在调用copy函数进行复制,这里用到了递归,因为可能存在文件夹里的文件还是文件夹。

    emptyDir


    function emptyDir(dir) {
     if (!fs.existsSync(dir)) {
       return
     }
     for (const file of fs.readdirSync(dir)) {
       const abs = path.resolve(dir, file)
       if (fs.lstatSync(abs).isDirectory()) {
         emptyDir(abs)
         fs.rmdirSync(abs)
       } else {
         fs.unlinkSync(abs)
       }
     }
    }

    emptyDir函数用于清空 dir 文件夹下的代码。它会先判断 dir 文件夹是否存在,存在则遍历该问文件夹下的文件,构造该文件的路径 abs,当 abs 为文件夹时,会递归调用 emptyDir 函数删除该文件夹下的文件,然后再调用fs.rmdirSync删除该文件夹;当 abs 是文件时,则调用fs.unlinkSync函数来删除该文件。

    核心函数

    接下来就是核心功能实现的init函数。

    命令行交互并创建文件夹

    首先是获取命令行参数


    let targetDir = argv._[0]
    let template = argv.template || argv.t

    const defaultProjectName = !targetDir ? 'vite-project' : targetDir

    argv._[0] 代表 @vitejs/app 后的第一个参数
    template则是要使用的模版名称
    defaultProjectName则是我们创建的项目名称。
    接下来就是使用prompts包来在命令行中输出询问,像下面这样:

    Vite创建项目的实现步骤

    具体代码如下:


    // 关于命令行交互的部分代码没有全部放在这里,感兴趣的可以去看源码
    let result = {}

    result = await prompts(
     [
       {
         type: targetDir ? null : 'text',
         name: 'projectName',
         message: 'Project name:',
         initial: defaultProjectName,
         onState: (state) =>
           (targetDir = state.value.trim() || defaultProjectName)
       },
       ......

    ]
    )

    const { framework, overwrite, packageName, variant } = result

    const root = path.join(cwd, targetDir)

    if (overwrite) {
     emptyDir(root)
    } else if (!fs.existsSync(root)) {
     fs.mkdirSync(root)
    }

    template = variant || framework || template

    // 输出项目文件夹路径
    console.log(`\nScaffolding project in ${root}...`)

    const templateDir = path.join(__dirname, `template-${template}`)

    选择完成后会返回我们选择的结果result
    root是通过path.join函数构建的完整文件路径
    overwrite是针对已存在我们要创建的同名文件时,是否要重写,如果重写,则调用前面的emptyDir函数清空该文件夹,如果不存在该文件夹,则调用fs.mkdirSync创建文件夹
    templateDir选择的模版文件夹名称

    写入文件


    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)
     }
    }

    const files = fs.readdirSync(templateDir)
    for (const file of files.filter((f) => f !== 'package.json')) {
     write(file)
    }

    const pkg = require(path.join(templateDir, `package.json`))

    pkg.name = packageName

    write('package.json', JSON.stringify(pkg, null, 2))

    const pkgManager = /yarn/.test(process.env.npm_execpath) ? 'yarn' : 'npm'

    // 输出一些提示告诉你项目已经创建结束,以及告诉你接下来启动项目需要运行的命令
    console.log(`\nDone. Now run:\n`)
    if (root !== cwd) {
    console.log(`  cd ${path.relative(cwd, root)}`)
    }
    console.log(`  ${pkgManager === 'yarn' ? `yarn` : `npm install`}`)
    console.log(`  ${pkgManager === 'yarn' ? `yarn dev` : `npm run dev`}`)
    console.log()

    write函数则接受两个参数 file 和 content,它有两个功能:

    • 对指定的文件 file 写入指定的内容 content,调用fs.writeFileSync函数来实现将内容写入文件。

    • 复制模版文件夹下的文件到指定文件夹下,调用前面介绍的copy函数来实现文件的复制。

    然后调用fs.readdirSync读取模版文件夹里的文件,遍历逐一复制到项目文件夹(其中要过滤的 package.json 文件,因为其中的 name 字段要修改);最后再写入 package.json 文件。

    小结

    Vite 的create-app包的实现只有320行左右的代码,但它考虑到各种场景的兼容处理;在学习完之后,自己去实现一个这样的CLI工具也不是什么难事。

    来源:https://juejin.cn/post/6983573067188404260

    标签:Vite,创建,项目
    0
    投稿

    猜你喜欢

  • 在golang中操作mysql数据库的实现代码

    2024-01-15 14:41:51
  • Django框架模型简单介绍与使用分析

    2021-04-06 02:59:19
  • Python使用Beautiful Soup(BS4)库解析HTML和XML

    2023-01-04 18:11:43
  • mysql提示[Warning] Invalid (old?) table or database name问题的解决方法

    2024-01-14 18:18:54
  • 用JS实现轮播图效果(二)

    2024-06-05 09:11:49
  • 用基于python的appium爬取b站直播消费记录

    2021-08-03 21:57:23
  • 详解Open Folder as PyCharm Project怎么添加的方法

    2021-06-25 05:56:53
  • python打开windows应用程序的实例

    2021-08-22 09:49:40
  • python3连接MySQL8.0的两种方式

    2024-01-20 20:16:14
  • Golang 内存管理简单技巧详解

    2023-06-24 22:38:18
  • PDO::setAttribute讲解

    2023-06-05 18:04:23
  • python中decimal模块的用法

    2021-01-20 06:54:57
  • 30分钟快速实现小程序语音识别功能

    2024-04-28 09:46:27
  • mysql 5.5.27 winx64安装配置方法图文教程

    2024-01-14 22:03:36
  • python中list*n生成多维数组与for循环生成多维数组的区别说明

    2022-01-10 08:57:33
  • Python3转换html到pdf的不同解决方案

    2021-10-03 19:50:03
  • 浮动元素对浏览器的支持

    2008-02-02 10:05:00
  • opencv实现矿石图片检测矿石数量

    2021-08-26 02:17:39
  • python同步windows和linux文件

    2023-12-11 11:44:35
  • 自己用python做的一款超炫酷音乐播放器

    2021-05-26 04:47:35
  • asp之家 网络编程 m.aspxhome.com