It’s a long article, divided into four parts, each of which can be eaten on its own. I hope you can have a little effect, thank you!

  1. Publish plug-in packages using NPM
  2. Learn how to use the commander. Js plug-in
  3. Learn to use the inquirer. Js plugin
  4. Encapsulate a CLI based on vue-CLI

1. Use NPM to publish plug-in packages

NPM opens the door to the whole world of JavaScript geniuses. It is the largest software registry in the world, with approximately 3 billion downloads per week and contains more than 600,000 packages (i.e., code modules). Open-source software developers from all continents use NPM to share and learn from each other. The package structure makes it easy to track dependencies and versions. NPM consists of three separate parts:

  1. Website: the main way for developers to find packages, set parameters, and manage the NPM experience.
  2. Registry: Is a large database that holds information about each package.
  3. Command line tool (CLI) : Run on the CLI or terminal. Developers interact with NPM through the CLI.

We can get a lot of good third-party packages from NPM, and of course we can upload our own. Here’s a quick guide to creating and publishing your own packages using NPM.

Create an NPM account

To upload our own package, you must have an NPM account first. If you do not have an NPM account, you can go to the registration page to register and log in your account. Enter the user name, email address, and password to create one. Enter NPM login on the command line and enter your account and password as prompted to login to our account on the command line. You can use this account to upload the NPM package we developed later. To test your login success, type NPM whoami on the command line, and your user name will be printed.

The classification of the package

There are two types of NPM packages: public and private.

scopes

Before we do that, let’s look at the concept of scopes (scopes in Chinese). When we register an NPM account and create an organization, you will be granted a scope that matches your user name or organization name. This scope is your user name or the name of the organization that you created. You can use this scope as a namespace for related packages. If you have a package named mypackage and your username is myusername, then you can put this package in your domain @myusername/mypackage. Restrict access to this package.

pubilc package

As an NPM user or member of an organization, you can create and publish public packages that anyone can download and use in their own projects. Unscoped: the public package exists in the namespace of the global public registry (registry.npmjs.org) and can be referenced in package.json files only by the package name: package-name. Scoped: the public package for scoped belongs to a user or organization and must be preceded by the user or organization name when included as a dependency in package.json. @username/package-name @org-name/package-name

private package

To use a proprietary package, you must be using NPM 2.7.0 or higher and have a paying user or organization account. With the NPM Private package, you can use the NPM registry to host code visible only to you and selected partners, allowing you to manage and use private and public code in your projects

Private packages always have a scope, and packages within a scope default to private.

User-scoped private packages can only be accessed by you and collaborators you have granted read or read/write permission to. Organization-wide private packages can only be accessed by teams that have been granted read or read/write permission.

Create a package.json file

Packages published to the registry must contain a package.json file. The package.json file indicates your package name, version, what it does, lists the packages your project depends on, keywords, and so on. This file is all you need to know about the package.json file. It must be actual JSON, not just a JavaScript object literal. So package.json is crucial for NPM packages. We’ll use a hands-on demo to learn. Open the command line and create a demo project in the specified directory mkdir zzmath CD zzmath create package.json file NPM init and enter your package information as prompted. Use the editor to open the package.json file.

{
  "name": "zzmath"."version": "1.0.0"."description": "Some simple data calculation method encapsulation."."main": "index.js"."scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [
    "math"."js"]."author": ""."license": "ISC"
}
Copy the code

This is some of the information that comes with creating the package.json file. Name: package name version: version number description: package description scripts: script main: entry file of the specified package keywords: keywords of the package author: author license: ISC for more fields, see official introduction

README.md

To help others find your packages on NPM and have a good experience using your code in their projects, it is recommended to include a README file in the root directory of your packages. Your README file can include instructions for installing, configuring, and using the code in your package, as well as any other information that other users may find useful. The README file will be displayed on the package page.

Semantic versioning

To keep the JavaScript ecosystem healthy, reliable, and safe, whenever you make a major update to your own NPM package, it is recommended that you publish the new version of the package in a package.json file and label it with the updated version number to comply with semantic versioning specifications. Following the semantic versioning specification helps other developers who rely on your code to know how much a particular version has changed and adjust their code as necessary. The package version starts at 1.0.0 and follows these rules. The concept of semantic versioning is simple: all versions have three numbers: X.Y.Z.

1. The first number is the major version. 1. The second number is the second version. 1. The third number is the patch version.Copy the code

When a new version is released: Upgrade the major version when incompatible API changes are made. When functionality is added in a backward compatible manner, the next version is upgraded. When backward compatible defect fixes are made, the patch version is upgraded. NPM sets rules that can be used in package.json files to select the version to update the package to (when running NPM Update). The rules use these symbols:

^: Only updates that do not change the leftmost non-zero digit are performed. If you write ^0.13.0, then when running NPM update, you can update to 0.13.1, 0.13.2, etc., but not to 0.14.0 or higher. If you write ^1.13.0, when running NPM update, you can update to 1.13.1, 1.14.0, etc., but not to 2.0.0 or higher. ~: If ~ 0.13.0 is written, the patch version will be updated when NPM update is run: 0.13.1 is ok, but 0.14.0 is not. >: Accepts any version higher than the specified version. >=: Accepts any version equal to or higher than the specified version. <=: Accepts any version equal to or lower than the specified version. <: Accepts any version below the specified version. =: Accept the exact version. -: Accepts a certain range of versions. For example, 2.1.0-2.6.2. | | : set combination. For example, < 2.1 | | > 2.6. Can merge some of the symbols, such as 1.0.0 | | > = 1.1.0 < 1.2.0, namely using 1.0.0 or starting 1.1.0 but below 1.2.0 version. There are other rules: unsigned: only the specific version specified is accepted (for example, 1.2.1). Latest: Use the latest version available.

Release and update packages

Continuing with the previous demo, let’s develop a simple toolkit and publish it to NPM. Create an index.js file and encapsulate two simple mathematical calculations.

//index.js
/ * * *@description: returns twice the largest number in the array *@param {Array} arr
 * @return {*}* /
function maxDouble(arr){
  return Math.max(... arr) *2;
}

/ * * *@description: Returns twice the smallest number in the array *@param {Array} arr
 * @return {*}* /
function minDouble(arr){
 return Math.min(... arr) *2;
}

module.exports = {
  maxDouble,
  minDouble,
};
Copy the code

Publish a package

Execute at the command linenpm publishTo release our first NPM package





Search the NPM website to find our newly released NPM package



Use published packages



Create a new test-demo small project and execute the install command to install our package







Create test.js and import our package

//test.js
const zzmath = require('zzmath')

console.log(zzmath.maxDouble([2.10.11]));
console.log(zzmath.minDouble([2.10.11]));
Copy the code

Run the node test.js command on the console to view the output.

zhuxiaodong@zhuxiaodongdeMacBook-Pro test-demo % node test.js
22
4
Copy the code

Update package

As mentioned earlier, our packages need to be strictly language versioning, and we need to update the package version number when we update the package. We can change the package version by changing version in the package.json file. Alternatively, run the NPM version patch command to update the patch version. NPM version Major: updates the major version. NPM version minor: updates the minor version

Let’s modify our package, then change the version number, and executenpm publishThe package can be updated.

Add dist-tags to the package

Dist-tags are human-readable tags that you can use to organize and tag different versions of your distributed software packages. Dist – Tags are a complement to the semantic version. In addition to being easier to read than semantic version numbers, labels allow publishers to distribute their packages more efficiently. For example, we can label the package as beta, telling people that the package is still in beta and should be used with caution. ::: Warning: Because dist-tags and semantic versions share a namespace, avoid conflicts between dist-tags and existing version numbers. It is recommended to avoid using dist- tags that begin with a number or letter “V”. : : :

Use the command NPM publish –tag

to tag the package.

You can also use the following command to tag the specified version: NPM dist-tag add @

[

]

We put a beta tag on our package, and we need to update the version number before we can tag it.





That’s it for distributing plug-in packages using NPM, check out moreThe official documentation.

Simple use of commander.js

Commander. Js is the complete Node.js command-line solution. You can define executing commands, options, handling incoming parameters, and automatically generating help messages for your scaffolding tool. Basically a plug-in that scaffolding tools now have to install.

The installation

npm install commander



Declare program variables

#! /usr/bin/env node
const { program } = require('commander');

//or

const { Command } = require('commander');
const program = new Command();
Copy the code

Let’s start with a small example:

  1. Create a new project foldereasy-cliGo to the folder and executenpm initKeep pressing Enter as prompted to create a package.json file.
  2. performnpm install commanderInstall the commander.



It looks something like this after the first two operations.

  1. Create a new directory in the root directorybinFolder. Create a foldereasy.jsFile.
  2. inpackage.jsonFile to set the mapping between command names and local file names.
  "bin": {
    "easy": "bin/easy.js"
  },
Copy the code
  1. ineasyStart of file input#! /usr/bin/env node, specifyeasy.jsThe interpreter is Node.
  2. Declare program variables
#! /usr/bin/env node
//easy.js
const { Command } = require("commander");
const program = new Command();
Copy the code
  1. Define an option
//easy.js
/ /.. other code

program.option('-c, --cons'.'console hello world! ')

/ / parsing
program.parse()
const options = program.opts();

if(options.cons) console.log("hello world!");
Copy the code

Test our command:

Enter NPM i-g at the console.. /easy-cli To install our CLI, MAC users may need to use the sudo permission sudo NPM I -g.. / easy – cli installation. This establishes a connection globally to our CLI project file. Use NPM list -g to view all the packages we have installed globally.

ZXD @ zxddeMBP easy - cli % NPM list - g/usr/local/lib ├ ─ ─ @ vue/[email protected] ├ ─ ─ @ vue/[email protected] ├ ─ ─ [email protected] - >. /.. /.. /.. / Users/ZXD/learnSpace/easy - cli ├ ─ ─ [email protected] ├ ─ ─ [email protected] ├ ─ ─ [email protected] ├ ─ ─ [email protected] └ ─ ─ [email protected]Copy the code

You can see that our easy-CLI has been installed globally. You only need to establish a connection once. You do not need to perform this operation again after modifying the CLI.

You can use NPM unlink -g easy- CLI to delete connections when debugging is not required.

Execute our command from the command line with the option easy-c and output: Hello world!

  1. Define a command
//easy.js
/ /.. other code
program.option('-c, --cons'.'console hello world! ')

program
  .command("double")
  .arguments("<number>")
  .description("Take the input parameter x2")
  .action((number) = > {
    console.log(number*2);
  });

program.parse()
const options = program.opts();

if(options.cons) console.log("hello world!");
Copy the code

On the command line, type easy double 2 to execute the command output: 4

On the command line type easy double 2 -c output: 4 Hello world! Try executing the following command to see what is printed

  1. easy
  2. easy double
  3. easy -c double 2
  4. Execute undefined commands and defined commands respectivelyeasy -c

Start learning commander. Js with curiosity and questions

Options

Options are options that we can execute by command. For example, when installing the NPM package, install the package in the dev dependency. NPM I -save-dev,–save-dev is the defined option.

Commander uses the.option() method to define options, and can append a brief description of the options. Each option can define a short name of the option (- followed by a single character) and a long option name (-) followed by one or more words, use commas, Spaces, or | off.

program.option('-s, --small'.'small pizza size')
// Short name short name options
Copy the code

Option parameters

The two most common options are Boolean options that require no parameters (the kind experienced above), and options that allow parameters to be set (declared after the option with Angle brackets, as in –expect). If no specific option or parameter is specified on the command line, it is defined as undefined. Parameters are divided into mandatory parameters and optional parameters

Options can be obtained by calling the.opts() method on the Command object and processing arguments through the program.parse(arguments) method. Options that are not used are stored in the program.args array. The argument to this method is optional and defaults to process.argv.

Required parameters

Parameters defined with Angle brackets are mandatory.

#! /usr/bin/env node
const { Command } = require("commander");
const program = new Command();

program.option("-c, --cons <input>"."console input"); / / parameters

program.parse()

const options = program.opts();

if(options.cons) console.log(options.cons);
Copy the code

Easy -c “Hey,how are you?” Output: Hey,how are you?

Error: option ‘-c, –cons’ argument missing

Optional parameters

Parameters defined using square brackets are optional. Option can be used as a Boolean option with no arguments, or get a value from the argument with arguments.

#! /usr/bin/env node
const { Command } = require("commander");
const program = new Command();

program.option("-c, --cons [input]"."console input or console hello world!"); / / parameters

program.parse();

const options = program.opts();

if (options.cons===true) {console.log("hello world!");
}else{
  console.log(options.cons);
}

Copy the code

Easy -c “Hey,how are you?” Output: Hey,how are you?

Run the easy-c command. The output is hello world!

Option Default value

You can add a default value to an option by adding a fourth value after the option explanation. ::: WARNING The default value of a mandatory option is used only when a command is entered without a mandatory option. If a mandatory option is entered but no mandatory parameter is entered, an error is reported. : : :

#! /usr/bin/env node
const { Command } = require("commander");
const program = new Command();

program.option("-c, --cons [input]"."console input or console hello world!"."Hola"); // Optional Default value
program.option(
  "-ch, --cheese <type>"."add the specified type of cheese"."blue"
);// Mandatory option default value

program.parse();
const options = program.opts();

console.log(options.cons);
console.log(`cheese: ${program.opts().cheese}`);
Copy the code

Easy -c “Hey,how are you?” Output: Hey,how are you? cheese: blue

Run the easy-c command. The output is Hola cheese: blue

Run the easy-c -ch command. Error: option ‘-ch, –cheese ‘argument missing

Variable length parameter option

You can define options by using… To set the parameter to variable length. On the command line, you can enter multiple parameters, which are parsed and stored in an array in the corresponding property fields. Instructions entered by the user are treated as variable-length arguments until the next option is entered (starting with – or –). As with normal arguments, you can mark the end of the current command with –.

#! /usr/bin/env node
program
  .option('-n, --number 
      
        '
      .'specify numbers')
  .option('-l, --letter [letters...] '.'specify letters');

program.parse();

console.log('Options: ', program.opts());
console.log('Remaining arguments: ', program.args);
Copy the code

Easy -n 1 2 3 –letter a b c

Options:  { number: [ '1', '2', '3' ], letter: [ 'a', 'b', 'c' ] }
Remaining arguments:  []
Copy the code

Easy -n 1 2 3 –letter a b c — ext

Options:  { number: [ '1', '2', '3' ], letter: [ 'a', 'b', 'c' ] }
Remaining arguments:  [ 'ext' ]
Copy the code

Version options

The version method allows you to set the version. The default options are -v and –version. After setting the version, the command line displays the current version number.

#! /usr/bin/env node
const { Command } = require("commander");
const program = new Command();

program.version("0.0.1");

// Usually use version in package.json file
//program.version(require(".. /package.json").version)

// You can pass more parameters (long option name, description) in the version method, similar to the use of the option method.
//program.version('0.0.1', '-v, --vers', 'output the current version');

program.parse()
Copy the code

Run the easy-v command. Output: 0.0.1

The help option

Commander. Js automatically generates help information. The default help option is -h,–help. Command line to execute easy-h output:

Usage: easy [options]

Options:
  -V, --version  output the version number
  -h, --help     display help for command
Copy the code

We can also customize the help information

#! /usr/bin/env node
const { Command } = require("commander");
const program = new Command();

program
  .option('-f, --foo'.'enable some foo');

program.addHelpText('after'.` Example call: $ custom-help --help`);

program.parse(process.argv);
Copy the code

Command line to execute easy-h output:

Usage: easy [options]

Options:
  -f, --foo   enable some foo
  -h, --help  display help for command

Example call:
  $ custom-help --help
Copy the code

Please refer to the official documentation for more help on setting up.

Command

For example, NPM I -s or NPM install -s command is used to execute the installation method when using the NPM installation package. I and install are method names. I is short for the method, install is the full name of the method, and -s is the command option. Install the package in Dependencies.

You can configure commands using.command() or.addCommand() in two ways:

  1. Bind handler functions for commands
  2. Write commands as a separate executable (when too much logic applies to commands)

The first parameter of.command() can be used to set the command name and command parameters. Parameters can be mandatory (Angle brackets), optional (square brackets), or variable length (periods). If used, only the last parameter can be used. It’s similar to the choices. Angle brackets (for example) mean mandatory, while square brackets (for example [optional]) mean optional. You can pass a second argument to the.description() method to display information about command parameters in the help. This parameter is an object containing the command Parameter Name: Command Parameter Description key-value pair.

#! /usr/bin/env node
const { Command } = require("commander");
const program = new Command();


program
  .command("clone <source> [destination]") 
	Name clone Mandatory parameter source Optional parameter destination
  .description("clone a repository into a newly created directory") // Command description
  .action((source, destination) = > {// Command handlers
    console.log(`repository has been cloned from ${source} to ${destination}`);
  });

program.parse(process.argv);
Copy the code

Easy clone a b

repository has been cloned from a to b
Copy the code

You can also write commands as separate executable files

When.command() takes a description argument, it means that a separate executable file is used as a subcommand. Commander will try to search the directory of entry scripts (e.g./examples/ PM) for executables in the form of program-command, such as easy-start, pm-install. You can customize the name with the configuration option executableFile.

#! /usr/bin/env node
//easy.js
const { Command } = require("commander");
const program = new Command();


program
.command("start"."Output the current time in seconds every 1 second")// The easy-start.js executable file is required
.command("start2"."Output the current time in seconds every 2 seconds", { executableFile: 'milliseconds' })// Requires the milliseconds. Js executable

program.parse(process.argv);
Copy the code

Create the easy-start.js file

#! /usr/bin/env node

setInterval(() = > {
  console.log(new Date().getSeconds());
}, 1000);

Copy the code

Create a new milliseconds. Js file

#! /usr/bin/env node

setInterval(() = > {
  console.log(new Date().getSeconds());
}, 2000);
Copy the code

Run the easy start and easy start2 commands respectively to view the output.

Variable-length argument

Add… to the parameter name. To declare mutable arguments, and only the last argument supports this usage.

program
  .command('rmdir 
      
        '
      )
  .action(function (dirs) {
    dirs.forEach((dir) = > {
      console.log('rmdir %s', dir);
    });
  });
Copy the code

Easy rmdir 1 2 34 45 The command output is rmdir 1 rmdir 2 rmdir 34 rmdir 45

A separate executable receives parameters

#! /usr/bin/env node
//easy.js
const { Command } = require("commander");
const program = new Command();


program
.command("start 
       
       
        "
       
      ."Traverse number")// The easy-start.js executable file is required

program.parse(process.argv);
Copy the code
#! /usr/bin/env node
//easy-start.js
const { Command } = require("commander");
const program = new Command();

program.parse();

console.log(program.args)
Copy the code

Easy start 5 12345 4 23 [‘5’, ‘12345’, ‘4’, ’23’]

Parameters need to be fetched by specifying their positions. “5” is length, and everything else is number.

Listening to the

Listen commands and options to perform custom functions.

#! /usr/bin/env node
const { Command } = require("commander");
const program = new Command();

program.option("-c, --cons"."console hello world!");

program
  .command("clone <source> [destination]")
  Name clone Mandatory parameter source Optional parameter destination
  .description("clone a repository into a newly created directory") // Command description
  .action((source, destination) = > {
    // Command handlers
    console.log(`repository has been cloned from ${source} to ${destination}`);
  });

program.on("option:cons".function () {
  console.log("Input options --cons")}); program.on("command:*".function (operands) {
  console.error(`error: unknown command '${operands[0]}'`);
  const availableCommands = program.commands.map((cmd) = > cmd.name());
  console.log("availableCommands:", availableCommands);
});

program.parse();
const options = program.opts();

if (options.cons) console.log("hello world!");
Copy the code

Easy clone a b -c

Input option -cons repository has been empirical from a to b hello world!Copy the code

Easy pull a b -c

--cons Error: unknown command 'pull' availableCommands: ['clone'] Hello world!Copy the code

The sample

const { Command } = require('commander');
const program = new Command();

program
  .version('0.0.1')
  .option('-c, --config <path>'.'set config path'.'./deploy.conf');

program
  .command('setup [env]')
  .description('run setup commands for all envs')
  .option('-s, --setup_mode <mode>'.'Which setup mode to use'.'normal')// Command options
  .action((env, options) = > {
    env = env || 'all';
    console.log('read config from %s', program.opts().config);
    console.log('setup for %s env(s) with %s mode', env, options.setup_mode);
  });

program
  .command('exec <script>')
  .alias('ex')// Define an alias
  .description('execute the given remote cmd')
  .option('-e, --exec_mode <mode>'.'Which exec mode to use'.'fast')
  .action((script, options) = > {
    console.log('read config from %s', program.opts().config);
    console.log('exec "%s" using %s mode and config %s', script, options.exec_mode, program.opts().config);
  }).addHelpText('after'.` Examples: $ deploy exec sequential $ deploy exec async`
  );
  
program.parse(process.argv);

Copy the code

For more information please visit the official documentation.

3. Inquirer. The use of js

Inquirer. Js is a command-line interactive tool that sets questions, options, displays these questions on the console when executing commands, and receives the answers after the user has answered them.

The installation

npm install inquirer

Small Demo experience

#! /usr/bin/env node
const inquirer = require("inquirer");

const requireLetterAndNumber = (value) = > {
  if (/\w/.test(value) && /\d/.test(value)) {
    return true;
  }

  return "Password need to have at least a letter and a number";
};

const questions = [
  {
    type: "expand".message: "Conflict on `file.js`".name: "q_1".choices: [{key: "y".name: "Overwrite".value: "overwrite"}, {key: "a".name: "Overwrite this one and all next".value: "overwrite_all"}, {key: "d".name: "Show diff".value: "diff",},new inquirer.Separator("."),
      {
        key: "x".name: "Abort".value: "abort",},],}, {type: "input".name: "q_2".message: "Question with filtering and validating Text".validate: async() = > {await new Promise((r) = > setTimeout(r, 3000));
      return true;
    },
    filter: async (answer) => {
      await new Promise((r) = > setTimeout(r, 3000));
      return `filtered${answer}`;
    },
    filteringText: "Filtering your answer...".validatingText: "validating what you wrote"}, {type: "input".name: "q_3".message: "Question without filtering and validating Text".validate: async() = > {await new Promise((r) = > setTimeout(r, 3000));
      return true;
    },
    filter: async (answer) => {
      await new Promise((r) = > setTimeout(r, 3000));
      return `filtered${answer}`; }, {},type: "input".name: "q_4".message: "What 's your last name".default() {
      return "Doe"; }, {},type: "list".name: "q_5".message: "What do you want to do?".choices: [
      "Order a pizza"."Make a reservation".new inquirer.Separator(),
      "Ask for opening hours",
      {
        name: "Contact support".disabled: "Unavailable at this time",},"Talk to the receptionist",]}, {type: "list".name: "q_6".message: "What size do you need?".choices: ["Jumbo"."Large"."Standard"."Medium"].filter(val) {
      returnval.toLowerCase(); }, {},type: "password".name: "q_9".message: "Enter a password".validate: requireLetterAndNumber,
  },
  {
    type: "password".name: "q_10".message: "Enter a masked password".mask: "*".validate: requireLetterAndNumber,
  },
  {
    type: "confirm".name: "q_11".message: "password is ok?".default: true}, {type: "input".name: "q_12".message: "How many do you need?".validate(value) {
      const valid = !isNaN(parseFloat(value));
      return valid || "Please enter a number";
    },
    filter: Number}, {type: "rawlist".name: "q_13".message: "What do you want to do?".choices: [
      "Order a pizza"."Make a reservation".new inquirer.Separator(),
      "Ask opening hours"."Talk to the receptionist",]}]; inquirer.prompt(questions).then((answers) = > {
  console.log(JSON.stringify(answers, null.""));
});

Copy the code

Run Node demo.js on the console to see the effect.

By analyzing the interaction between the above code and the console, we can see that we define an array of “questions” objects. Each object has type ‘ ‘name’ ‘message, some have validate’ ‘dafault’ ‘filter’ ‘choices, etc. Pass the Questions array as an argument to the inquirer.prompt() method, which returns a promise for the answer the user enters on the console.

Methods

inquirer.prompt(questions, answers) -> promise

Launch our prompt with the inquirer.prompt() method. Questions (array) contains question objects (Using the reactive interface, you can also pass an Rx.Observable instance Answers (object) that contains the value of the answered question. The default is {}. Return a Promise

The problem (question)

A question has the following properties:

  1. type: (String)The type of the prompt. Default value:input– Possible values:input.number.confirm.list.rawlist.expand.checkbox.password.editor.
  2. name: (String) The name used to store the answer in the answer hash. If the name contains a period, it defines a path in the answer hash.
  3. message: (String|Function)Questions to print. If defined as a function, the first argument will be the answer to the current query session. The default isname“(followed by a colon).
  4. default: (String|Number|Boolean|Array|Function)If no information is entered, the default value is used, or the function that returns the default value. If defined as a function, the first argument will be the answer to the current query session.
  5. choices: (Array|Function)Selects an array or a function that returns a selection array. If defined as a function, the first argument will be the answer to the current questioner meeting. Array values can be simplenumbers.strings.objects(Contains onename(shown in the list), onevalue(saved in the answer hash) and oneshort(Displayed after selection) property. The selection array can also contain a delimiter.
  6. validate: (Function)Receive user input and answer hashing. If the value is valid, it should return true, otherwise an error message (String) is returned. If false is returned, a default error message is provided.
  7. filter: (Function)Accepts hashes of user input and answers. Returns the filtered value for use within the program. The returned value will be added to the Answers hash.
  8. transformer: (Function)Takes user input, an answer hash, and an option flag, and returns a converted value to display to the user. The transformation only affects what is displayed at edit time. It doesn’t change the answer hash.
  9. when: (Function, Boolean)Receives the current user’s answer hash and should return based on whether the question should be askedtrueorfalse. The value can also be a simple Boolean value.
  10. pageSize: (Number)Change to uselist,rawList,expandorcheckboxThe number of lines in.
  11. prefix: (String)Change the default prefix information.
  12. suffix: (String)Change the default suffix information.
  13. askAnswered: (Boolean)Force the question if the answer already exists.
  14. loop: (Boolean)Enable the list loop. Default value: true

Answer (answers)

Answers: The key/value that contains the user’s answer in each prompt.

Key: The name property of the question object

Value Possible values: confirm: Boolean INPUT: String Input by the user (a filter is configured) Number: number Input by the user (a filter is configured) RawList, list: Number Selected selection value (name if no value is specified)

Separators (Separator)

Delimiters can be added to any Choices array. Eg: Q_5 in demo, the default is “-“, you can pass in the specified character as the delimiter.

Complete demo

#! /usr/bin/env node
const inquirer = require("inquirer");

const choices = Array.apply(0.new Array(26)).map((x, y) = >
  String.fromCharCode(y + 65)); choices.push("Multiline option 1\n super cool feature \n more lines");
choices.push("Multiline option 2\n super cool feature \n more lines");
choices.push("Multiline option 3\n super cool feature \n more lines");
choices.push("Multiline option 4\n super cool feature \n more lines");
choices.push("Multiline option 5\n super cool feature \n more lines");
choices.push(new inquirer.Separator());
choices.push("Multiline option \n super cool feature");
choices.push({
  name: "Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. Cum sociis natoque penatibus et magnis dis  parturient montes, nascetur ridiculus mus. Donec quam felis, ultricies nec, pellentesque eu, pretium quis, sem. Nulla consequat massa quis enim. Donec pede justo, fringilla vel, aliquet nec, vulputate eget, arcu. In enim justo, rhoncus ut, imperdiet a, venenatis vitae, justo. Nullam dictum felis eu pede mollis pretium.".value: "foo".short: "The long option"});const requireLetterAndNumber = (value) = > {
  if (/\w/.test(value) && /\d/.test(value)) {
    return true;
  }

  return "Password need to have at least a letter and a number";
};

const questions = [
  {
    type: "expand".message: "Conflict on `file.js`".name: "q_1".choices: [{key: "y".name: "Overwrite".value: "overwrite"}, {key: "a".name: "Overwrite this one and all next".value: "overwrite_all"}, {key: "d".name: "Show diff".value: "diff",},new inquirer.Separator("."),
      {
        key: "x".name: "Abort".value: "abort",},],}, {type: "input".name: "q_2".message: "Question with filtering and validating Text".validate: async() = > {await new Promise((r) = > setTimeout(r, 3000));
      return true;
    },
    filter: async (answer) => {
      await new Promise((r) = > setTimeout(r, 3000));
      return `filtered${answer}`;
    },
    filteringText: "Filtering your answer...".validatingText: "validating what you wrote"}, {type: "input".name: "q_3".message: "Question without filtering and validating Text".validate: async() = > {await new Promise((r) = > setTimeout(r, 3000));
      return true;
    },
    filter: async (answer) => {
      await new Promise((r) = > setTimeout(r, 3000));
      return `filtered${answer}`; }, {},type: "input".name: "q_4".message: "What 's your last name".default() {
      return "Doe"; }, {},type: "list".name: "q_5".message: "What do you want to do?".choices: [
      "Order a pizza"."Make a reservation".new inquirer.Separator(),
      "Ask for opening hours",
      {
        name: "Contact support".disabled: "Unavailable at this time",},"Talk to the receptionist",]}, {type: "list".name: "q_6".message: "What size do you need?".choices: ["Jumbo"."Large"."Standard"."Medium"].filter(val) {
      returnval.toLowerCase(); }, {},type: "list".name: "q_7".message: "What's your favorite letter?".loop: false,
    choices,
  },
  {
    type: "checkbox".name: "q_8".message: "Select the letter contained in your name:",
    choices
  },
  {
    type: "password".name: "q_9".message: "Enter a password".validate: requireLetterAndNumber,
  },
  {
    type: "password".name: "q_10".message: "Enter a masked password".mask: "*".validate: requireLetterAndNumber,
  },
  {
    type: "confirm".name: "q_11".message: "password is ok?".default: true}, {type: "input".name: "q_12".message: "How many do you need?".validate(value) {
      const valid = !isNaN(parseFloat(value));
      return valid || "Please enter a number";
    },
    filter: Number}, {type: "rawlist".name: "q_13".message: "What do you want to do?".choices: [
      "Order a pizza"."Make a reservation".new inquirer.Separator(),
      "Ask opening hours"."Talk to the receptionist",]}, {type: "rawlist".name: "q_14".message: "What size do you need".choices: ["Jumbo"."Large"."Standard"."Medium"."Small"."Micro"].filter(val) {
      returnval.toLowerCase(); }, {},type: "confirm".name: "q_15".message: "Do you like bacon?"}, {type: "input".name: "favorite".message: "Bacon lover, what is your favorite type of bacon?".when(answers) {
      returnanswers.q_15; }},]; inquirer.prompt(questions).then((answers) = > {
  console.log(JSON.stringify(answers, null.""));
      inquirer.prompt({  / / nested
        type: "list".name: "beverage".message: "And your favorite beverage?".choices: ["Pepsi"."Coke"."7up"."Mountain Dew"."Red Bull"]}); });Copy the code

You can annotate the problem object one by one to execute the corresponding problem, analysis, it is not difficult to learn the use of the corresponding type. Please consult the official documentation for more information. For most scenarios, the above demo will suffice.

4. Develop CLI scaffolding

When we create a vUE project, we usually use the official scaffolding tool VUe-CLI or use our own template project, which encapsulates various configurations and creates the project by cloning the template project. First, for vue-CLI created projects, we need to reconfigure various dependencies each time. Js, Axios, vuex, VUE-Router, UI component library, etc. There is a lot of duplication. For projects created using the previous template, it is not flexible enough. Simple projects do not need vuex or VUE-Router, and do not need UI component library. For mobile projects, vconsole plug-in and PostCSS-Pxtorem plug-in are required. Different projects require different plug-ins, so template projects are not flexible enough to be configured separately or divided into multiple template projects. So let’s build a flexible scaffolding easy-vue-CLI. On the basis of VUe-CLI, another layer is encapsulated to install plug-ins and configure projects through command + query. Easy-vue-cli options include whether to create vue.config.js file, whether to use Gzip compression, whether to install AXIos, whether to install Vconsole, and whether to use the UI component library. There are many Node.js apis used in the project, which are not detailed here. Please check the documentation for details. In fact, after learning the use of the above plug-in, mainly requires our Node.js skills, play their own imagination, to achieve some powerful functions. The main idea is to use the vue create command to create an initialization project. After the initialization project is created, the user is asked whether to install the plug-in and generate the configuration.

1. Initialize the project

Create the easy-vue-CLI project folder. Use the command NPM init to create the package.json file as prompted. Create bin and lib folders to store our command files and main files, respectively. The project also uses the Oraloading plug-in. You can install it first.

2. Create the main project file

Create a new easy.js file in the bin folder. The content of the document is as follows:

#! /usr/bin/env node
const { Command } = require("commander");
const program = new Command();

program.version(require(".. /package.json").version);


program
  .command("create <app-name>")   // The scaffolding command is create
  .description("create a vue app and config some dependencies")
  .action((name) = > { // Get the project name and call the function that creates the project
   const options = program.opts();  
    require(".. /lib/create")(name, options); 
  });

program.parse(process.argv);
Copy the code
Create a new 'create.js' file in the' lib 'folder. The content of the document is as follows:Copy the code
#! /usr/bin/env node

const spawn = require("child_process").spawn Spawn spawns a new process with the given command and command-line arguments in args
const fs = require("fs")
const inquirer = require("inquirer");
const generate = require("./generate")

/ * * *@description: delete project
 */
function removeDir(filePath) {
 const stat = fs.statSync(filePath);
 if(stat.isFile()){
   fs.unlinkSync(filePath);
 }else{
  const files = fs.readdirSync(filePath);
  console.log(files)
  if (files.length === 0) {
    fs.rmdirSync(filePath);
  } else {
    let tempFiles = 0;
    files.forEach((file) = > {
      tempFiles++;
      const nextFilePath = `${filePath}/${file}`;
      removeDir(nextFilePath);
    });
    // Delete all child empty folders under the parent folder and delete the parent folder
    if(tempFiles === files.length) { fs.rmdirSync(filePath); }}}}const choices = [
  {
    name: "vue.config.js".checked:true
  },
  {
    name: "Axios"}, {name: "Gzip"}, {name: "Vconsole",}];const questions = [
  {
    type: "checkbox".name: "configs".message: "Select the config in your app",
    choices,
  },
  {
    type: "confirm".name: "UI_Components".message: "Do you want to install the ui component library?"}, {type: "list".name: "ui".message: "Select the config in your app".choices: ["Element UI"."Ant Design"."Vant"].when(answers) {
      returnanswers.UI_Components; }},];async function create(projectName,options) {
 const cmd = spawn("vue"["create", projectName],{ stdio: ["inherit"."inherit"."pipe"]}) // Use the vue create command to create the initialization project

 cmd.on("close".function(code, signal){
   if(code===0) {// Call the inquirer method to ask the user if the vue initialization project is successfully created
    console.log("Vue initialization project created successfully");
    inquirer.prompt(questions).then(async (answers) => {
      if(answers.configs.includes("vue.config.js")) {await generate.generateVueConfigJS(projectName);// Call the method that generates vue.config.js
      }
      if (answers.configs.includes("Gzip")) {
        // Call the method to install and configure the compression-webpack-plugin
        awaitgenerate.installCompressionWebpackPlugin(projectName); }}); } }) process.on("SIGINT".function () {// The listener automatically closes and deletes the unfinished project
   console.log("Got SIGINT. Press Control-D/Control-C to exit.");
   removeDir(projectName);
 });

}

module.exports = (. args) = > {
  returncreate(... args).catch((err) = > {
    error(err);
    process.exit(1);
  });
};



Copy the code

Create a new generate.js file in the lib folder. The content of the document is as follows:

const fs = require("fs");
const ora = require("ora");
const spawn = require("child_process").spawn;

/ * * *@description: Generates the vue.config.js file */
function generateVueConfigJS(projectName) {
  return new Promise((resolve, reject) = > {
    const spinner = ora({
      text: "Generating vue.config.js".color: "yellow"});let file = fs.createReadStream(`${__dirname}/vueConfig.js`, {
      encoding: "utf8"});let out = fs.createWriteStream(
      `${process.cwd()}/${projectName}/vue.config.js`,
      {
        encoding: "utf8"}); file.on("data".function (dataChunk) {
      out.write(dataChunk, function () {
        spinner.start();
      });
    });

    out.on("open".function (fd) {});

    file.on("end".function () {
      out.end("".function () {
        setTimeout(() = > {
          spinner.succeed("Successfully generated vue.config.js");
        }, 500);
        resolve(true);
      });
    });
  });
}

// Install and configure compression-webpack-plugin
function installCompressionWebpackPlugin(projectName) {
 return new Promise((resolve, reject) = > {
      const spinner = ora({
        text: "install compression-webpack-plugin".color: "yellow",
      }).start();
      const cmd = spawn(
        "npm"["install"."[email protected]"] and {stdio: "pipe".cwd: `${process.cwd()}/${projectName}`}); cmd.on("close".function (code, signal) {
        if (code === 0) {
          const content = `const path = require("path") const CompressionWebpackPlugin = require("compression-webpack-plugin") const isProd = process.env.NODE_ENV === "production" function resolve(dir) { return path.join(__dirname, dir) } `;
          const content2 = ` configureWebpack: (config) => {if (isProd) {// Production environment config.plugins.push(new CompressionWebpackPlugin({// is matching the file suffix test: / \. (js | | CSS SVG | woff | the vera.ttf | json | HTML) $/, / / greater than 10 KB compression threshold: 10240, deleteOriginalAssets: False // For the rest of the configuration see compression-webpack-plugin}))}}, ';
          // Write data to a fixed row
          const data = fs
            .readFileSync(`${process.cwd()}/${projectName}/vue.config.js`."utf8")
            .split("\n");
          data.splice(0.0, content);
          fs.writeFileSync(
            `${process.cwd()}/${projectName}/vue.config.js`,
            data.join("\n"),
            "utf8"
          );
          const data2 = fs
            .readFileSync(`${process.cwd()}/${projectName}/vue.config.js`."utf8")
            .split("\n");
          data2.splice(data2.length - 45.0, content2);
          fs.writeFileSync(
            `${process.cwd()}/${projectName}/vue.config.js`,
            data2.join("\n"),
            "utf8"
          );
          spinner.succeed("install compression-webpack-plugin success");
          resolve();
        } else {
          spinner.warn("install compression-webpack-plugin error");
          reject(`install compression-webpack-plugin error`); }}); }); }module.exports = {
  generateVueConfigJS,
  installCompressionWebpackPlugin,
};

Copy the code
This is how to generate the 'vue.config.js' file and how to install and configure the' compression-webpack-plugin 'plug-in. <br />Copy the code

Create a new vueConfig. Js file and store the template file of our vue.config.js file. You can adjust it flexibly.

module.exports = {
  publicPath: ". /".// Configure the root path
  outputDir: "dist".// Build the output directory
  assetsDir: "assets".// Static resource directory (js\ CSS \img)
  lintOnSave: true.// Whether to enable eslint
  productionSourceMap: false.// Whether the production environment generates the sourceMap file
  devServer: {},
  css: {
    // Whether to use CSS ExtractTextPlugin
    // Set to false if CSS hot update is required, and to true for packaging
    extract: false.// Enable CSS source maps?
    sourceMap: process.env.NODE_ENV ! = ="production".// CSS default configuration item
    // loaderOptions: {
    // sass: {
    // prependData: `@import "@/styles/variables.scss"; `,
    / /},
    // },
  },

  chainWebpack: (config) = > {
    config.resolve.symlinks(true);
    (config.entry.app = ["babel-polyfill"."./src/main.js"]),
      // Alias configuration
      config.resolve.alias
        .set("@", resolve("src"))
        .set("@utils", resolve("src/utils"))
        .set("@api", resolve("src/api"))
        .set("@components", resolve("src/components"))
        .set("@pic", resolve("src/assets/imgs"));
    config.resolve.extensions.clear().merge([".js".".vue".".json"]);


    config.optimization.splitChunks({
      chunks: "all".// Controls which code blocks WebPack chooses to split (other types of code blocks are packed the default way). There are three optional values: Initial, async, and all.
      minSize: 30000.// Form the smallest size of a new code block
      maxSize: 0.minChunks: 2.// The minimum number of times the code block should be referenced before splitting (the default policy is to split without multiple references)
      maxAsyncRequests: 5.// The maximum number of code blocks loaded on demand should be less than or equal to 5
      maxInitialRequests: 3.// The maximum number of code blocks initially loaded should be less than or equal to 3
      automaticNameDelimiter: "~".name: true.cacheGroups: {
        libs: {
          name: "chunk-libs".test: /[\\/]node_modules[\\/]/,
          priority: 10.chunks: "initial".// only package third parties that are initially dependent
        },
        commons: {
          name: "chunk-commons".test: resolve("src/components"), // can customize your rules
          minChunks: 3.// minimum common number
          priority: 5.reuseExistingChunk: true,}}}); config.plugins.delete("prefetch-index");
    config.plugins.delete("preload-index"); }};Copy the code

3. Configure commands

Add the bin attribute to the package.json file

  "bin": {
    "easy": "bin/easy.js"
  },
Copy the code

4. Test

Using NPM to install our scaffolding.npm i .. / Scaffolding path /

useeasy create testCommand to create a project, will first call vue-cli create initialization project



The project has been created successfully. If you look at the Test project, you will see that the project has been createdvue.config.jsFile, installed and configuredcompression-webpack-plugin



End of the 5.

Here, easy-vue-CLI only realized the generation of vue.config.js file and installation and configuration of compression-webpack-plugin, but not the installation of AXIOS and installation of UI components, which can be realized by referring to the above method. It’s too long. You can optimize your code, use remote template files, and so on. This is just to give you an idea. Welcome to discuss and revise if there is any improvement or idea. thank you

The last

The length is too long, thank you to be able to read patiently, I am still a small dish chicken, welcome to put forward improvement, wrong place, if the article content has a little effect on you hope you can give a 👍, rush!! Rush!!!!!! .