Recently, the leader of the company required to build wheels to improve the technical precipitation of the team (0_0), so the team members were required to carry out the corresponding technical infrastructure construction. Since I used to make simple scaffolding tools, I was entrusted to develop the CLI part. Personally, I am also interested in this part of the content (I can go out for an interview). This article will set out along my personal ideas and build a front-end CLI tool from scratch.

Cli Functions

Received the task, after investigating multiple internal and external CLI tools (borrow… Jian…). Absorbing the strengths of hundred schools and combining with the company’s current development process, the following optimization points are sorted out:

  • Version checking
    • Before running all commands, check the tool version, prompting you to upgrade the tool
  • Initialization-init
    • Unified project template, multiple project templates to choose from
    • Integration of VUE/CLI (vUE is the main technology stack in the department)
    • Unified ESLint, unified department code style
    • Unified development and configuration, personalized configuration according to the characteristics of the department
  • Start – dev
    • Checking Item Configuration
    • Providing data mocks
  • Packaging – build
    • Comply with company packaging specification
    • Checking the Configuration File
    • Static resource path replacement
  • Publish – public (why don’t companies automate builds?)
    • Check the configuration files required for publication
    • Review the packaging files required for publication
    • Publish to the specified CDN

The above is my arrangement of CLI tool functions, more in line with our current development process. Function finishing, open dry!

preparation

Before we start to write the code, it is necessary to find some useful plug-ins to improve our development efficiency. Check the corresponding development library of CLI for you to know. I have listed the libraries used in my project below.

The node tools

  • Path: Native path tool
  • Fs: a native file read and write tool
  • Child_process: node child process package
  • Process: node process information
// path
// Generate a normalized absolute path
path.resolve(__dirname, b) 
// /User/Desktop/code/b

// - Path connection
path.join(a,b,c)
// a/b/c

// fs
// Read the folder
fs.readdirSync
// Read the file
fs.readFileSync
// Whether the file path exists
fs.existsSync
// Returns file/folder information
const f = fs.statSync
f.isDirectory() // Whether it is a folder
f.isFile() // Whether it is a file
// Write data
fs.writeFileSync

/ / the child process
import {execSync, spawnSync} from 'child_process';
// Different forms of parameter transfer
execSync('npm run dev') // The maximum execution result is 200K
spawnSync('npm'['run'.'dev']) // Return an execution result of unlimited size

import { cwd } from 'process';
// Returns the absolute path of the current command directory
cwd()
Copy the code

Third-party plug-ins:

  • Commander: Command line tool
  • Ora: Console progress display
  • Chalk: Console print content optimization
  • Inquirer: Console interaction tool
  • Fontmin: font compression

Start – Create the project

The project structure

Scaffolding is developed using ts syntax. Eventually translated into JS syntax.

npm install -g typescript
npm init
tsc --init
npm i path fs ..... fontmin
Copy the code

Directory structure:

  • SRC: the source code
    • Index. ts entry file
    • Utils utility functions
    • The actions command
  • Bin: export directory
  • package.json
  • tsconfig.json

Package. The json configuration

// package.json
{
    // Scaffolding name
    name: "xxx-cli".// Scaffolding version, must be three digits, otherwise publish error
    version: "1.0.0".// Scaffold execution name and corresponding entry
    bin: {
        xxx: './bin/index.js'
    },
    ......
    // File required for NPM publishing
    files: [
        "bin"]}Copy the code

Configuration tsconfig. Json

{
  "compilerOptions": {
    "types": [
      "node"]."typeRoots": [
      "node_modules/@types"]."outDir": "bin"."strict": true."allowSyntheticDefaultImports": true."module": "commonjs"."paths": {
      "src/*": [
        "src/*"]}},"include": [
    "src/**/*"]."exclude": [
    "node_modules"]}Copy the code

Version checking

Check the version of the local tool and the latest VERSION of the NPM. If the local version is earlier than the NPM version, the system prompts you to upgrade the tool. Otherwise, no action is performed

// src/actions/checkVersion
/ / pseudo code
const checkVersion = () = > {
    // Get the local version number
    const local = new Promise((resolve) = > {
        let localStr = ' ';
        const childProcesslocal = exec('xxx -V');
        if (childProcesslocal.stdout) {
            childProcesslocal.stdout.on('data'.chunk= > {
                localStr += chunk;
            });
            childProcesslocal.stdout.on('end'.() = >{ childProcesslocal.kill(); resolve(localStr); }); }});constlatest = ..... Ditto the exec ('npm views xxx versions');
    
    if (latest > local) {
        / / upgrade
        npm i -g xxx@latest
        return true; }};Copy the code

Entrance to the file

The entry file identifies the commands entered by the user and performs corresponding operations. We can optimize commands, add optional parameters, and modify actions. Here we mainly use the Commender command line tool, the following is the pseudo code of init command, other commands are basically the same way to add, use as needed.

src/index.js

// Run XXX init demo
// The init command of the XXX init tool does not take effect
// 
      
        corresponds to the third parameter entered by the user
      
program.command('init <project-name>')
  .description(chalk.yellow('Initialize the project'))
  .action((projectName: string) = > {
     init(projectName);
  });
  
// Another way to write it
// After we add option, execute XXX init -t
// Template = true in the options argument
// the -t parameter is optional, so the init function of the XXX init tool takes effect.
program.command('init')
  .options('-t, --template'.'Initialize a template')
  .description(chalk.yellow('Initialize the project'))
  .action((options) = > {
  // options = {template: }
     if (checkVersion()) return;
     init(options);
  });


program.parse(process.argv);

Copy the code

Init command parsing

To execute the XXX init command, we need to provide developers with multiple template options. My company currently has three major categories of projects

  • Little game
  • activity
  • propaganda

Here’s my idea:

  • xxx init
  • Download the template code
  • Prompts the user to select a template
  • Other Configuration options
  • Initialize the project
  • Copy the template into the project
  • Perform NPM install
  • done

Template code base to configure a JSON file, each template to make, in the JSON file configuration, after pulling the template, read the JSON file, and then show the user, so that we later new template, do not need to upgrade the tool operation.

Project unified configuration can also be removed, maintain a file, put in the code base, after the project is generated, directly copy into the project.

Vue project preset

As for the initialization project, since the technical stack of our department is VUE, we actually used the vue create command when we initialized the project. One problem here is that if we directly vue create the project, the configuration option of VUE/CLI will appear. If the configuration is wrong, it may affect the unified config configuration of our project. So we need to skip the vue/ CLI configuration items directly. Here vue/ CLI provides a command

vue create app -p path
Copy the code

We can specify a default template (PATH) so that the VUE will initialize the project according to the default template, and the default template will also allow us to directly inject the NPM packages required by the project.

// preset/preset.js
// vue/ CLI configuration item
{
  "useConfigFiles": true."cssPreprocessor": "sass"."plugins": {
    "@vue/cli-plugin-babel": {},
    "@vue/cli-plugin-eslint": {
      "config": "airbnb"."lintOn": ["save"."commit"]},"@vue/cli-plugin-router": {},
    "@vue/cli-plugin-vuex": {}}}// preset/generator.js
// Add the NPM package to package.json when initializing the project
module.exports = (api) = > {
 api.extendPackage({
    dependencies: {
        xxxxxx,
        xxxxx,
    },
    devDependencies: {
        "webpack": "^ 4.3.2." ",}}); }Copy the code

Init module

Init module implementation is as follows:

// src/actions/init.js
    export default function Init() {
        // Download the template
        spawn('git'['clone'.'git.xxx'] and {cwd: cwd() });
        // Read the template
        const config = fs.readFileSync('template.json');
        
        // The user console selects templates
        inquirer.prompt([
            {
                type: 'list'.choices: config,
                name: 'templateName'}])// vue/cli initializes a project and uses the default configuration,
        // If your company has its own NPM image, you can also set this up
        spawn('vue'['create', projectName, '-p', presetPath, '-r'.'xxx.npm']);// Copy the template
        spawn('cp'['-r', templatePath, projectPath]);
        
        console.log('done');
    }
Copy the code

Init part of the code is the most important template download and template injection into the initialization project, which involves git, shell, vue commands, and the use of various paths is also more complex, as well as the judgment of various situations, improve fault tolerance, basically after init, other commands are very simple, practice makes perfect belongs to.

End

Although it was my boss’s task, I gained a lot during the development process. I always wanted to write something interesting before, but I was always affected by other things and gave up halfway. This time, under the influence of external forces, I forced myself to do something meaningful to me (something to blow). The above code and development ideas are just a summary of the CLI development, not perfect, but for those who have not developed before, it is an unforgettable experience. There will be a summary of other interesting features later.

Off duty!!