preface

With the release and stability of Vite2, more and more projects are now experimenting with it. We use Vite to create a project with the following commands:

NPM init @vitejs/app // use yarn yarn create @vitejs/app // If you want to specify the project name and use a template for a particular framework, NPM NPM init @vitejs/app my-vue-app --template vue // yarn yarn create @vitejs/app my-vue-app --template vueCopy the code

Running these commands generates a project folder, which most people would think is enough to create a project properly, but I’m curious why running these commands generates a project folder. The following uses YARN as an example to describe how to create a project.

What does YARN Create do

Many people may wonder why many projects are created using the YARN create command. In addition to the Vite here, we create the React project as follows: yarn create react-app my-app.

So what does this command do? It does two things:

yarn global add create-react-app
create-react-app my-app
Copy the code

More about YARN Create can be found here

The source code parsing

Yarn create @vitejs/app After the yarn create @vitejs/app command is run, the code in @vitejs/create-app is executed. Let’s look at the project structure of this file first

The folder that starts with template is a template for each framework and typescript version of the project. The logic to create the project is in the index.js file. So what’s going on here

Project depend on

The first is the introduction of dependencies

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')
Copy the code

Fs, Path are Nodejs built-in modules, while Minimist, Prompts, and Kolorist are third-party libraries, respectively.

  • Minimist: is a tool for parsing command-line arguments. The document
  • Prompts. They are a command line interaction tool. The document
  • Kolorist: a tool to color command line output. The document

The template configuration

Next, the configuration files for the different framework templates, and finally an array of template names.

// The react framework and vue framework are configured as follows: 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 } ] }, ...... ] / / output template name list const TEMPLATES = FRAMEWORKS. The map ((f) = > (f.v ariants && f.v ariants. The map ((v) = > v.n ame)) | | [motor ame] ).reduce((a, b) => a.concat(b), [])Copy the code

Second, due to the special nature of the.gitignore file, each framework project template is created under the _gitignore file, which is replaced with.gitignore during subsequent project creation. Therefore, the code defines an object in advance to hold the file to be renamed:

const renameFiles = {
  _gitignore: '.gitignore'
}
Copy the code

Tool function

Before getting into the core functions, let’s look at the utility functions defined in the code. The most important are the three functions related to file operations.

copy

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

The copy function is used to copy files or folders SRC to the specified folder dest. It gets the SRC state stat, and if SRC is a folder (stat.isdirectory () is true), it calls the copyDir function described below to copy files from the SRC folder to dest. If SRC is a file, call fs.copyfilesync directly to copy the SRC file to the dest folder.

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)
  }
}
Copy the code

The copyDir function is used to copy files in a folder srcDir to the specified folder destDir. It first calls fs.mkdirSync to create the specified folder, then calls fs.readdirSync to get the files from the srcDir folder and iterate over them one by one. Finally, the copy function is called to make a copy, using recursion, because the files that may exist in folders are folders.

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) } } }Copy the code

The emptyDir function is used to empty the code in the dir folder. It will first determine whether the dir folder exists, then traverse the files under the folder, construct the path of the file ABS, when abs is a folder, will recursively call emptyDir function to delete the files under the folder, and then call fs.rmdirsync to delete the folder; When abs is a file, the fs.unlinksync function is called to delete the file.

The core function

Next comes the init function that implements the core function.

Command line interaction and folder creation

The first is to get command line arguments

let targetDir = argv._[0] let template = argv.template || argv.t const defaultProjectName = ! targetDir ? 'vite-project' : targetDirCopy the code

Argv. _[0] represents the first argument after @vitejs/app

Template is the name of the template to use

DefaultProjectName is the name of the project we created.

And the next step is to usepromptsPackage to print a query on the command line like this:

The specific code is as follows:

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 = the variant | | framework | | template / / output project folder path console.log(`\nScaffolding project in ${root}... `) const templateDir = path.join(__dirname, `template-${template}`)Copy the code

When the selection is complete, the result of our selection is returned

Root is the full file path built through the path.join function

Overwrite specifies whether to overwrite the existing file with the same name. If overwrite, call emptyDir to clear the folder. If no folder exists, call fs.mkdirsync to create the folder

TemplateDir Specifies the template folder name

Written to the file

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' // outputs some prompts telling you that the project has been created and the command 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()Copy the code

The write function takes two arguments, file and content, and does two things:

  • Call to write the specified content to the specified filefs.writeFileSyncFunction to write content to a file.
  • Copy the files in the template folder to the specified folder and call the one described earliercopyFunction to copy files.

Then call fs.readdirsync to read the files in the template folder and iterate over them one by one to the project folder (package.json file to filter because the name field in it needs to be changed); Finally, write the package.json file.

summary

The implementation of Vite’s Create-App package is only about 320 lines of code, but it allows for compatible handling of various scenarios; After learning, it is not difficult to implement such a CLI tool yourself.