In front-end development, some CLI tools are often used, such as TS-Node to convert TS files, babel-cli to parse ES6+ syntax, and SPA project initialization scaffolding, create-React-app, VUe-cli, vite, etc. These CLI tools save developers time in configuring WebPack, tsconfig, and Babel by automating scripts. But do you really understand cli tools? Here’s how it works by creating a custom CLI tool.
CLI Usage tips
Will write the react friends definitely useful create – react – app, common use of scaffolding steps will be divided into two steps, the first step is the global installation, the second step the cli, such as the following order:
#NPM install:
npm install -g create-react-app
#Or YARN installation:
yarn global add create-react-app
#Perform the cli:
create-react-app my-project
Copy the code
If you want to avoid the global installation of modules, you can try NPX. NPX will first check the local dependencies for executable files, and if not, it will download a temporary file from a remote repository, and then delete it after using it.
#NPX installation:
npx create-react-app my-project
Copy the code
Starting from VERSION 6.1 of NPM, you can use the NPM init or yarn create command to omit the create-prefix. For example, create-react-app becomes the following:
#NPM install:
npm init react-app my-project
#Yarn installation:
yarn create react-app my-project
Copy the code
New CLI project
CLI projects don’t have to be named with a prefix of create-*, such as vue-cli, but create-xxx is better understood. Scaffolding projects usually use create, so the demo used here is called create-tst.
mkdir create-tst && cd create-tst
npm init --yes
Copy the code
–yes Skip the prompts and create the default configuration package.json
Next, create a js file that receives the command and executes the script. The general practice is to create a bin directory for the corresponding JS script file and configure the “bin” field information in package.json.
mkdir bin && cd bin
touch create-tst.js
Copy the code
Create-tst.js contains the following contents:
#! /usr/bin/env noderequire = require('esm')(module /*, options*/); require('.. /src/cli').cli(process.argv);Copy the code
The local Node command is used first, and the ESM library is used to support the ES6+ syntax for the following execution code. The specific CLI logic is usually executed in SRC /cli.js, and process.argv is all arguments for subsequent command line execution.
Before getting into the cli.js logic, here’s the overall directory structure:
| ____bin | | ____create - app. | js command line entrance ____package. Json | ____templates | | ____TypeScript js template | | ____JavaScript ts template | ____src | ____main. Js create mission | ____cli. Js handle user inputCopy the code
There are also related dependencies (the use of each dependency is described below) :
Dependent libraries | role | NPM address |
---|---|---|
arg | Parses the raw command line argument and returns the object | portal |
chalk | Let the command line output support bold highlighting | portal |
esm | Have Node6 + support ES6 syntax | portal |
execa | Execute other command line statements | portal |
inquirer | Support complex interactive prompts, such as select, confirm | portal |
listr | Manage and execute tasks in serial and parallel mode | portal |
ncp | Perform file replication asynchronously | portal |
pkg-install | Install NPM dependencies in JS | portal |
Interactive UI
Before executing the script, you need to run the NPM link command in the current directory. This command generates a symbolic link file in the global environment. The file name is the module name specified in package.json
Parsing the participation
Create a cli.js file in the SRC directory to print out the parameters entered on the command line:
export async function cli(args) {
console.log(args)
}
Copy the code
Cli.js outputs an array of five elements, the first and second are fixed, the last three are user-defined input parameters, so we only need to parse all the parameters except the first two, then we can use arG to parse.
// Parse the input parameters
const parseArgsIntoOptions = (rawArgs) = > {
const args = arg({
'--git': Boolean.// Parse to a Boolean value
'--yes': Boolean.'--install': Boolean.'-g': '--git'.-g is the same as --git
'-y': '--yes'.'-i': '--install'}, {argv: rawArgs.slice(2)})return {
skipPrompts: args['--yes'] | |false.initGit: args['--git'] | |false.template: args._[0].runInstall: args['--install'] | |false}}Copy the code
Of course, using the Commander tool for parsing is also a good option.
If the arG is not specified in the first object argument, it will go into the args._ array, as in javascript
GUI selection configuration
Those of you who have used vue-CLI know that when you create a project, the interface provides different versions of vUE, or custom configurations. This complex interaction requires the inquirer mentioned above.
Here we write the logic for getting the relevant configuration by prompting as a general function, which will prompt if the user does not use the default configuration or does not enter the relevant parameters:
- Select the project template
- Select whether to initialize Git
- Select whether to install NPM dependencies
const promptForOptions = async (options) => {
const defaultTemplate = 'JavaScript';
if (options.skipPrompts) {
return {
...options,
template: options.template || defaultTemplate
}
}
const questions = [];
if(! options.template) {// 1. Select a project template
questions.push({
type: 'list'.name: 'template'.message: 'Please select the template for the current new project'.choices: ['JavaScript'.'TypeScript'].default: defaultTemplate
})
}
if(! options.initGit) {// 2. Determine whether to initialize git
questions.push({
type: 'confirm'.name: 'git'.message: 'Initialize git repository'.default: false})}if(! options.runInstall) {// 3. Determine whether to install the NPM dependency
questions.push({
type: 'confirm'.name: 'install'.message: 'Whether to install dependencies'.default: false})}const answers = await inquirer.prompt(questions)
return {
...options,
template: options.template || answers.template,
git: options.initGit || answers.git,
install: options.runInstall || answers.install
}
}
Copy the code
Good results, andvue-cli
almost
Finally, change the CLI function to create a createSpaApp, the logic of which is described below.
import createSpaApp from './main'
function cli(args) {
let options = parseArgsIntoOptions(args)
options = await promptForOptions(options)
createSpaApp(options)
}
Copy the code
The core function
Once you have the configuration in place, you can consider how to make the configuration parameters work. Assuming the user has all the configuration turned on, you need to do three things:
- Creating a Template File
- Initialize the Git repository
- Install NPM dependencies
Creating a Template File
To create a template file, you simply copy the preset template file to the target directory. The target directory is the directory where the user currently runs the command line. Therefore, write a copy function here.
import ncp from 'ncp'
import { promisify } from 'util'
const copy = promisify(ncp)
// Copy the file
const copyTemplateToTarget = async (options) => {
return copy(options.templateDir, options.targetDir, {
clobber: false // Overwrite existing files directly})}...try {
// Check whether the file exists in the current directory
await access(templateDir, fs.constants.F_OK);
} catch (e) {
console.error('%s Invalid template name', chalk.red.bold('ERROR'));
process.exit(1);
}
Copy the code
Before actually executing copy, consider whether the template file exists, and if none exists, exit.
The default template root directory should correspond to the above options, the template content is not provided here, you can customize.
Initialize the Git repository
Git repository initialization is usually done by running git init directly, so you can use execa to run the git command line directly.
import execa from 'execa'
const initGit = async (options) => {
const result = await execa('git'['init'] and {cwd: options.targetDir
})
if (result.failed) {
return Promise.reject(new Error('Failed to initialize git'))}return
}
Copy the code
Install NPM dependencies
After the front-end project initialization, you can do more automation, directly help users to install the NPM package, here also has a ready-made tool called PKG-install, support YARN installation, promise usage.
import { projectInstall } from 'pkg-install';
await projectInstall({
prefer: 'yarn'.cwd: options.targetDir
})
Copy the code
Serial task
After defining the functions corresponding to each option, it is necessary to configure the corresponding functions, here uses listR for task management, the final execution function is as follows:
export default async function createSpaApp(options) { options = { ... options,targetDir: options.targetDir || process.cwd()
}
// Default template directory
const templateDir = path.resolve(
new URL(import.meta.url).pathname,
'.. /.. /templates',
options.template
)
options.templateDir = templateDir;
try {
// Check whether the file exists in the current directory
await access(templateDir, fs.constants.F_OK);
} catch (e) {
console.error('%s Invalid template name', chalk.red.bold('ERROR'));
process.exit(1);
}
const tasks = new Listr([
{
title: 'Copy project files'.task: () = > copyTemplateToTarget(options)
},
{
title: 'Initialize git'.task: () = > initGit(options),
enabled: () = > options.git
},
{
title: 'Install dependencies'.task: () = >
projectInstall({
prefer: 'yarn'.cwd: options.targetDir
})
,
skip: () = > {
!options.runInstall
? 'Pass --install to automatically install dependencies'
: undefined}}])await tasks.run()
console.log('%s Project ready', chalk.green.bold('DONE'));
return true
}
Copy the code
Listr provides a display of page progress, each step will have a loading effect
The final result is something like this, a bit of create-React-app lite
Git repository: github.com/Tinsson/cre…
The end of the
CLI tools are not only used to do scaffolding, but also capable of a lot of automated operation and maintenance, syntax parsing, file monitoring and other things for development. Here is just a simple demo to demonstrate the principle.
Creation is not easy, hope to dig a lot of praise + pay attention to erlian, continue to update!!
PS: If there are any mistakes in this article, please kindly correct them
Past wonderful 📌
- Make a vscode plugin to stop the work from 996
- Vue3 check this out ✨ before building the “hooks” wheel
- React Hooks✨
- React DevTools ✨
- Vue3 hugs TypeScript properly ✨
- Vue3-one piece尝鲜:React Hooks VS Composition API ✨