preface
Some time ago, Yuxi answered a question that many netizens are curious about: Will Vite replace Vue CLI?
The answer is: yes!
So, have you started Vite yet? For those of you who have used Vite, creating a project template for Vite is done via NPM init. vitejs/app. The NPM init command is available at [email protected], which actually installs Vite’s @vitejs/create-app package before executing create-app.
@vitejs/create-app is in the Vite project packages/create-app folder. Its overall directory structure:
// packages/create-app| -- - the template element - lit - | -- - the template - lit - element - ts | -- - the template - preact | -- - the template - preact - ts | -- - The template - the react | | -- -- -- -- the template - the react - ts template - vanilla | -- - the template - vue | -- - the template - vue - ts index. Js package.jsonCopy the code
Vite create-app CLI (collectively referred to as create-app CLI) has few capabilities, currently only supports the creation of the basic template, so the total code is only 160 lines, the overall architecture diagram:
As you can see, the CREate-app CLI is a good example to get started on how to implement a simplified VERSION of the CLI.
This article will take you through the following two sections to learn how to implement a simple version of the CREate-app CLI:
-
Libraries used in create-app (Minimist, Kolorist)
-
Step by step disassemble and analyze create-app CLI source code
Create-app Indicates the library used in the CLI
The library (NPM) used by the CREate-app CLI implementation is really interesting, ranging from the familiar Enquirer (for command line prompts) to the unfamiliar minimist and Kolorist. So, what are the latter two for? Below, we come to understand some ~
minimist
Minimist is a lightweight tool for parsing command-line arguments. When it comes to tools that parse the command line, I think you can easily think of Commander. Compared to Commander, Minimist wins with light! Because it is only 32.4 kB, commander is 142 kB, which is about 1/5 of the latter.
So, let’s look at the basics of minimist.
For example, at this point on the command line we type:
node index.js my-project
Copy the code
So, in the index.js file, you can use minimist to get the myProject parameter as input:
var argv = require('minimist')(process.argv.slice(2));
console.log(argv._[0]);
/ / output my - project
Copy the code
In this case, argv is an object, and the value of the _ attribute in the object is an array of parameters parsed from Node index.js.
kolorist
Kolorist is a lightweight tool to color command line output. And I think chalk is the one that comes to mind. However, compared with chalk, the size difference between the two packets is not obvious, with the former being 49.9 kB and the latter 33.6 kB. Kolorist may be relatively niche, however, with NPM having far fewer downloads than Chalk, which has a more detailed API.
Similarly, let’s look at the basic uses of Kolorist.
For example, when an exception occurs in the application, we need to print a red exception message to inform the user of the exception. We can use the red function provided by Kolorist:
import { red } from 'kolorist'
console.log(red("Something is wrong"))
Copy the code
Alternatively, kolorist provides stripColors to print colored strings directly:
import { red, stripColors } from 'kolorist'
console.log(stripColors(red("Something is wrong"))
Copy the code
Step by step disassemble and analyze create-app CLI source code
For those of you who are familiar with CLI, the commands we usually use are configured in the package.json file bin. The bin configuration of the create-app CLI file in the root directory is as follows:
// pacakges/create-app/package.json
"bin": {
"create-app": "index.js"."cva": "index.js"
}
Copy the code
As you can see, the create-app command is registered here and points to the index.js file in the current directory. Also, it’s worth noting that two commands are registered here, which means we can also use the CVA command to create a Vite based project template (surprise 😲).
The core of create-app CLI implementation is in the index.js file. So, let’s look at the implementation of the code in index.js
Base dependency introduction
As mentioned above, create-app CLI introduced minimist, enquire, Kolorist, and other dependencies, so we introduced them first:
const fs = require('fs')
const path = require('path')
const argv = require('minimist')(process.argv.slice(2))
const { prompt } = require('enquirer')
const {
yellow,
green,
cyan,
magenta,
lightRed,
stripColors
} = require('kolorist')
Copy the code
Fs and PATH are built-in modules of Node. The former is used for file-related operations and the latter for file-path-related operations. The introduction of minimist, Enquirer, and Kolorist, which have been mentioned above, will not be repeated here
Define project base templates (colors) and files
From the/Packages /create-app directory, we can see that the CREate-app CLI provides us with nine project base templates. In addition, during command line interaction, each template has a different color, that is, the CLI uses the color function provided by Kolorist to define the corresponding color for the template:
const TEMPLATES = [
yellow('vanilla'),
green('vue'),
green('vue-ts'),
cyan('react'),
cyan('react-ts'),
magenta('preact'),
magenta('preact-ts'),
lightRed('lit-element'),
lightRed('lit-element-ts')]Copy the code
Second, due to the special nature of the.gitignore file, under each project template the _gitignore file is created first, and the name of the file is replaced with.gitignore when the project is created later. Therefore, the CLI pre-defines an object to hold the file to be renamed:
const renameFiles = {
_gitignore: '.gitignore'
}
Copy the code
Define utility functions related to file operations
To create a project, you need to perform operations related to files, so the CLI defines three tool functions:
CopyDir function
The copyDir function is used to copy files in a folder srcDir to the specified folder destDir. It creates the specified folder by calling the fs.mkdirsync function, and then enumerates an array of filenames retrieved from the srcDir folder, fs.readdirsync (srcDir).
The corresponding code is as follows:
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 copy function
The copy function is used to copy files or folders SRC to the specified folder dest. It gets the SRC status stat, and if SRC is a folder (stat.isdirectory () is true), it calls the copyDir function described above 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.
The corresponding code is as follows:
function copy(src, dest) {
const stat = fs.statSync(src)
if (stat.isDirectory()) {
copyDir(src, dest)
} else {
fs.copyFileSync(src, dest)
}
}
Copy the code
EmptyDir function
The emptyDir function is used to empty the code in the dir folder. It will first determine whether the dir folder exists, then enumerate the files under this folder, construct the path abs of this file, call fs.unlinkSync function to delete this file, and when abs is a folder, recursively call emptyDir function to delete the files under this folder. Then call fs.rmdirsync to delete the folder.
The corresponding code is as follows:
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 CLI implements core functions
The core function of the CLI implementation is init, which is responsible for implementing the corresponding function using the functions and toolkits described above. Here’s a point-by-point analysis of the init function implementation:
1. Create a project folder
In general, we can use create-app my-project to specify the project folder to be created, i.e., under which folder:
let targetDir = argv._[0]
cwd = process.cwd()
const root = path.join(cwd, targetDir)
console.log(`Scaffolding project in ${root}. `)
Copy the code
Where argv._[0] represents the first argument after create-app, and root is the full file path built through the path.join function. Then, on the command line, you will be prompted to tell you what path to your Scaffolding project will create:
Scaffolding project in /Users/wjc/Documents/project/vite-project...
Copy the code
Of course, sometimes we don’t want to type the project folder after create-app, but just the create-app command. So, tagertDir doesn’t exist at this point. The CLI uses the Enquirer package prompt to print a query on the command line:
? project name: > vite-project
Copy the code
You can enter the project folder name here, or you can enter the default project folder name given by the CLI. The code for this procedure is:
if(! targetDir) {const { name } = await prompt({
type: "input".name: "name".message: "Project name:".initial: "vite-project"
})
targetDir = name
}
Copy the code
The CLI then determines if the folder exists under the current working directory (CWD), and if it does not, creates a folder using fs.mkdirsync:
if(! fs.existsSync(root)) { fs.mkdirSync(root, {recursive: true})}Copy the code
Otherwise, if the folder exists, the system will determine whether there are files in the folder, that is, use fs.readdirsync (root) to obtain the files in the folder:
const existing = fs.readdirSync(root)
Copy the code
“Existing” is going to be an array, and if that array is not zero, then there are files in that folder. The CLI will ask you whether to delete the files in this folder:
Target directory vite-project is not empty.
Remove existing files and continue? (y/n) : yCopy the code
You can optionally tell the CLI whether to clear the directory by typing y or n. Also, if you type n at this point, that is, do not clear the folder, then the entire CLI execution will exit. The code for this procedure is:
if (existing.length) {
const { yes } = await prompt({
type: 'confirm'.name: 'yes'.initial: 'Y'.message:
`Target directory ${targetDir} is not empty.\n` +
`Remove existing files and continue? `
})
if (yes) {
emptyDir(root)
} else {
return}}Copy the code
2. Determine the project template
After the project folder is created, the CLI gets the –template option when we type a command like this:
NPM init. vitejs/app --template folder nameCopy the code
If the –template option does not exist (i.e., undefined), the project template to select will be asked:
let template = argv.t || argv.template
if(! template) {const { t } = await prompt({
type: "select".name: "t".message: "Select a template:".choices: TEMPLATES
})
template = stripColors(t)
}
Copy the code
Because TEMPLATES only defines the type of TEMPLATES, the project TEMPLATES folder is named differently (missing the template prefix) from the Packages /create-app directory. For example, if template is equal to vue-ts, then we need to concatenate the prefix to template and build the full directory:
const templateDir = path.join(__dirname, `template-${template}`)
Copy the code
So now templateDir will be equal to the current working directory + template-vue-ts.
3. Write the project template file
After determining the template of the project to be created, the CLI reads the files in the project template folder selected by the user and writes them one by one to the created project folder:
For example, if you want to create a project folder named viet-project, you will write the files in the create-app/template-vue-ts folder to the viet-project folder.
const files = fs.readdirSync(templateDir)
for (const file of files.filter((f) = >f ! = ='package.json')) {
write(file)
}
Copy the code
Since the fs.readdirSync function returns an array of file names under that folder, the array is enumerated by for of, and each enumeration calls the write function to write the file.
Note that the package.json file is skipped at this point, and I’ll explain why you need to skip the package.json file later.
The write function, which takes two parameters file and content, has two capabilities:
-
Write the specified content to the specified file, call fs.writeFileSync function to write the content to the file
-
Copy the files in the template folder to the specified folder, and call the copy function described earlier to copy the files
Write function definition:
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)
}
}
Copy the code
Also, it’s worth noting that the targetPath acquisition process builds the full file path for file and is compatible with handling _gitignore files.
After writing these files within the template, the CLI processes the package.json file. The reason for treating package.json files separately is that the package.json name in each project template is written dead, and when the user creates the project, the name should be named for the project’s folder. The code for this process would look like this:
const pkg = require(path.join(templateDir, `package.json`))
pkg.name = path.basename(root)
write('package.json'.JSON.stringify(pkg, null.2))
Copy the code
The path.basename function is used to get the last folder name of a full path
Finally, the CLI will output some prompts telling you that the project has been created and the commands you need to run to start the project:
console.log(`\nDone. Now run:\n`)
if(root ! == cwd) {console.log(` cd ${path.relative(cwd, root)}`)}console.log(` npm install (or \`yarn\`)`)
console.log(` npm run dev (or \`yarn dev\`)`)
console.log()
Copy the code
conclusion
Vite’s create-App CLI implementation is only 160 lines of code, but it takes into account the various scenarios for creating a project and makes it compatible. In short, very small and beautiful. So, I believe that after learning Vite create-app CLI implementation, should be able to throw out (implementation) a CLI code 😎 ~
Thumb up 👍
If you get something from this post, please give me a “like”. This will be my motivation to keep sharing. Thank you
My name is Wu Liu. I like innovation and source Code manipulation, and I focus on source Code (Vue 3, Vite), front-end engineering, cross-end and other technology learning and sharing. Welcome to follow my wechat official account: Code Center.