Implement a general scaffold tool for Web Engineering from 0 to 1

Time:2021-11-25

preface

Front end engineering is often mentioned. Its purpose is basically to improve development efficiency, reduce cost and ensure quality. Scaffold tool is a very important link in front-end engineering. A useful general scaffold tool for Web engineering can do the above to a great extent.

We should not only be able to use many mature scaffolds in the market, but also realize some scaffolds suitable for our own projects according to the actual project situation. This paper will work with you to realize a basic general scaffold tool, which can be expanded at will in the future.

Project structure

The overall structure of the project is as follows. Later, we will write code step by step to finally realize the whole scaffold tool.

xman-cli
├─ bin
│  └─ xman.js
├─ command
│  ├─ add.js
│  ├─ delete.js
│  ├─ init.js
│  └─ list.js
├─ lib
│  ├─ remove.js
│  └─ update.js
├─ .gitignore
├─ LICENSE
├─ package.json
├─ README.md
└─ templates.json

Concrete implementation

Initialize project

Can usenpm initYou can also create it according to the list belowpackage.jsonMake changes.

{
  "name": "xman-cli",
  "version": "1.0.0",
  "Description": "web general scaffold tool",
  "bin": {
    "xman": "bin/xman.js"
  },
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/XmanLin/xman-cli.git"
  },
  "keywords": [
    "cli"
  ],
  "author": "xmanlin",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/XmanLin/xman-cli/issues"
  },
  "homepage": "https://github.com/XmanLin/xman-cli#readme",
  "dependencies": {
    "chalk": "^4.1.2",
    "clear": "^0.1.0",
    "clui": "^0.3.6",
    "commander": "^8.2.0",
    "figlet": "^1.5.2",
    "handlebars": "^4.7.7",
    "inquirer": "^8.1.5",
    "update-notifier": "^5.1.0"
  }
}

Here are two points:

  • binFields: you can customize the commands of scaffold tools, such as the abovexman, andxmanThe following is the command execution script.
  • Dependencies in the project will be used later and will be introduced when they are used.

Write bin / xman.js

To make the script executable, you need to add the following code at the top of xman.js:

#!/usr/bin/env node

Import after writingcommander(the complete solution of the node.js command line interface), you can click the link or go to the NPM official website to view the usage of the specific API. The related dependencies of the following columns are the same.

#!/usr/bin/env node

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

At this point, we can define the version of the current scaffold and the command to view the version.

#!/usr/bin/env node

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

program
    .version(require('../package').version, '-v, --version');
    
program.parse(process.argv); //  This is necessary

if (!program.args.length) {
    program.help();
}

In the current xman cli directory, executenpm linkAfter, the scaffold tools can be debugged locally.

Then execute in the current directory

xman -v

We can see the version number defined by us, which also proves that the scaffold tools have been successfully built.

Implement a general scaffold tool for Web Engineering from 0 to 1

Use scaffolding tools to initialize the construction project

This is the core function point of scaffold tools. Select and pull quickly through scaffold tool commands, and build the basic project template in Git warehouse in advance. We can customize the project template according to the actual needs, and formulate relevant development specifications and conventions in the project.

First of all, build your own basic project on GIT. Here’s what you need to pay attention to: when building the basic project template, the projectpackage.jsonMediumnameThe field should be written in the following form:

{
    "name": "{{name}}",
}

As for why to write like this, it will be reflected in the following code.

Then create it in the root directorytemplates.json:

{
    "templates": {
        "xman-manage": {
            "url": "https://github.com/XmanLin/xman-manage.git",
            "branch": "master"
        },
        "xman-web": {
            "url": "https://github.com/XmanLin/xman-web.git",
            "branch": "master"
        }
    }
}

abovexman-manageandxman-webThey represent different items, which can be customized according to the actual situation,urlIs the address of the base project,branchIt is the branch when pulling automatically.

Then create a command in the command folder (the implementation logic of some subsequent commands will be placed in this folder)init.js:

const fs = require('fs'); //  Node.js file system
const exec = require('child_process').exec; //  Start a new process to execute commands
const config = require('../templates'); //  Import a defined basic project list
const chalk = require('chalk'); //  Add color to the prompt
const clear = require('clear'); //  Clear command
const figlet = require('figlet'); //  Can be used to customize the CLI execution header
const inquirer = require('inquirer'); //  Provide interactive command line
const handlebars = require('handlebars'); //  A simple template language, you can baidu yourself
const clui = require('clui'); //  Provide waiting status
const Spinner = clui.Spinner;
Const status = new spinner ('downloading... ');
const removeDir = require('../lib/remove'); //  Used to delete files and folders

module.exports = () => {
    let gitUrl;
    let branch;
    clear();
    //Customize cool cli head
    console.log(chalk.yellow(figlet.textSync('XMAN-CLI', {
        horizontalLayout: 'full'
    })));
    inquirer.prompt([
        {
            name: 'templateName',
            type: 'list',
            Message: 'please select the project template you need:',
            choices: Object.keys(config.templates),
        },
        {
            name: 'projectName',
            type: 'input',
            Message: 'please enter your project name:',
            validate: function (value) {
                if (value.length) {
                    return true;
                } else {
                    Return 'please enter your project name';
                }
            },
        }
    ])
    .then(answers => {
        gitUrl = config.templates[answers.templateName].url;
        branch = config.templates[answers.templateName].branch;
        //Execute the command to clone the desired project template from GIT
        let cmdStr = `git clone ${gitUrl} ${answers.projectName} && cd ${answers.projectName} && git checkout ${branch}`;
        status.start();
        exec(cmdStr, (error, stdou, stderr) => {
            status.stop();
            if (error) {
                Console.log ('An error occurred: ', chalk.red (JSON. Stringify (error)));
                process.exit();
            }
            const meta = {
                name: answers.projectName
            };
            //Note here: the name in package.json of the project template should be written as "name": "{name}}"
            const content = fs.readFileSync(`${answers.projectName}/package.json`).toString();
            //Use handlebars.compile to fill in {{name}} 
            const result = handlebars.compile(content)(meta);
            fs.writeFileSync(`${answers.projectName}/package.json`, result);
            //Delete the. Git file that comes with the template
            removeDir(`${answers.projectName}/.git`);
            Console.log (chalk. Green ('\ n √ download completed!');
            console.log(chalk.cyan(`\n cd ${answers.projectName} && yarn \n`));
            process.exit();
        })
    })
    .catch(error => {
        console.log(error);
        Console.log ('An error occurred: ', chalk.red (JSON. Stringify (error)));
        process.exit();
    });
}

lib/remove.js

const fs = require('fs');
let path = require('path');

function removeDir(dir) {
    let files = fs.readdirSync(dir); // Returns an array object containing "all file names in the specified directory"
    for (var i = 0; i < files.length; i++) {
        let newPath = path.join(dir, files[i]);
        let stat = fs.statSync(newPath); //  Get fs.stats object
        if (stat.isDirectory()) {
            //Judge whether it is a folder. If it is a folder, it will recurse
            removeDir(newPath);
        } else {
            //Delete file
            fs.unlinkSync(newPath);
        }
    }
    fs.rmdirSync(dir); // If the folder is empty, delete yourself
};

module.exports = removeDir;

Finally, continue inxman.jsDefine command:

#!/usr/bin/env node

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

...
    
program
    .command('init')
    .description('Generate a new project')
    .alias('i')
    .action(() => {
        require('../command/init')()
    });
    

...

Find another folder and execute the defined command:

xman i

Implement a general scaffold tool for Web Engineering from 0 to 1

Open the downloaded template project to see:

Implement a general scaffold tool for Web Engineering from 0 to 1

Add project template configuration through command

Now we can pull the construction project through the command, but what if there is a new project template in the future? Do you modify it manually every timetemplates.jsonYes. Of course, this is unreasonable, so next we need to add the project template through the command.

First, create a new project template in the GIT warehouse. Whatever it is called, I call it herexman-mobileThen start to write the logic and commands added by the project template, and create a new command / add.js:

const config = require('../templates.json');
const chalk = require('chalk');
const fs = require('fs');
const inquirer = require('inquirer');
const clear = require('clear');

module.exports = () => {
    clear();
    inquirer.prompt([
        {
            name: 'templateName',
            type: 'input',
            Message: 'please enter the template name:',
            validate: function (value) {
                if (value.length) {
                    if (config.templates[value]) {
                        Return 'template already exists, please re-enter';
                    } else {
                        return true;
                    }
                } else {
                    Return 'please enter a template name';
                }
            },
        },
        {
            name: 'gitLink',
            type: 'input',
            Message: 'please enter git HTTPS link:',
            validate: function (value) {
                if (value.length) {
                    return true;
                } else {
                    Return 'please enter git HTTPS link';
                }
            },
        },
        {
            name: 'branch',
            type: 'input',
            Message: 'please enter Branch Name:',
            validate: function (value) {
                if (value.length) {
                    return true;
                } else {
                    Return 'please enter the branch name';
                }
            },
        }
    ])
    .then(res => {
        config.templates[res.templateName] = {};
        config.templates[res.templateName]['url'] = res.gitLink.replace(/[\u0000-\u0019]/g, ''); //  Filter Unicode characters
        config.templates[res.templateName]['branch'] = res.branch;
        fs.writeFile(__dirname + '/../templates.json', JSON.stringify(config), 'utf-8', (err) => {
            if (err) {
                console.log(err);
            } else {
                Console.log (chat. Green ('New template added successfully! \ n '));
            }
            process.exit();
        })
    })
    .catch(error => {
        console.log(error);
        Console.log ('An error occurred: ', chalk.red (JSON. Stringify (error)));
        process.exit();
    });
}

Continue adding commands in bin / xman.js

#!/usr/bin/env node

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

...

program
    .command('add')
    .description('Add a new template')
    .alias('a')
    .action(() => {
        require('../command/add')()
    });
    
...

implementnpm link --forceAnd then execute the configured commandxman a:

Implement a general scaffold tool for Web Engineering from 0 to 1

Can seetemplates.jsonIn, new template information has been added.

Delete project template configuration through command

Since there is an add command, there must be a delete command. Similarly, create a new command / delete.js:

const fs = require('fs');
const config = require('../templates');
const chalk = require('chalk');
const inquirer = require('inquirer');
const clear = require('clear');

module.exports = () => {
    clear();
    inquirer.prompt([
        {
            name: 'templateName',
            type: 'input',
            Message: 'please enter the name of the template to delete:',
            validate: function (value) {
                if (value.length) {
                    if (!config.templates[value]) {
                        Return 'template does not exist, please re-enter';
                    } else {
                        return true;
                    }
                } else {
                    Return 'please enter the name of the template to delete';
                }
            },
        }
    ])
    .then(res => {
        config.templates[res.templateName] = undefined;
        fs.writeFile(__dirname + '/../templates.json', JSON.stringify(config), 'utf-8', (err) => {
            if (err) {
                console.log(err);
            } else {
                Console.log (chalk. Green ('template deleted! ');
            }
            process.exit();
        });
    })
    .catch(error => {
        console.log(error);
        Console.log ('An error occurred: ', chalk.red (JSON. Stringify (error)));
        process.exit();
    });
}

Continue adding commands:

#!/usr/bin/env node

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

...

program
    .command('delete')
    .description('Delete a template')
    .alias('d')
    .action(() => {
        require('../command/delete')()
    });

...

implementnpm link --forceAnd then execute the configured commandxman d。 seetemplates.json, we have deleted the template information we want to delete.

Quick view of existing templates through commands

Generally speaking, it is impossible for us to remember all the added templates. Sometimes we need to check them quickly. So next, we will implement a simple command to quickly view the template list:

New command / list.js

const config = require('../templates');
const chalk = require('chalk');

module.exports = () => {
    let str = '';
    Object.keys(config.templates).forEach((item, index, array) => {
        if (index === array.length - 1) {
            str += item;
        } else {
            str += `${item} \n`;
        }
    });
    console.log(chalk.cyan(str));
    process.exit();

}

Add command:

#!/usr/bin/env node

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

...

program
    .command('list')
    .description('show temlpate list')
    .alias('l')
    .action(() => {
        require('../command/list')()
    });

...

implementnpm link --forceAnd then execute the configured commandxman l:

Implement a general scaffold tool for Web Engineering from 0 to 1

Check whether the CLI version is the latest version through the command

A general scaffold tool must not be used by one person. The user may need to know whether the CLI has the latest version, so it also needs to have the function of checking the CLI version.

Create a new bin / update.js:

const updateNotifier = require('update-notifier');  //  Notifications for updating cli applications
const chalk = require('chalk');
const pkg = require('../package.json');

const notifier = updateNotifier({
    pkg,
    Updatecheckinterval: 1000 * 60 * 60, // the default is 1000 * 60 * 60 * 24 (1 day)
})

function updateChk() {
    if (notifier.update) {
        Console.log ('a new version is available: ${chat. Cyan (notifier. Update. Latest)}. It is recommended that you update it before use ');
        notifier.notify();
    } else {
        Console.log (chalk.cyan ('the latest version ');
    }
};

module.exports = updateChk;

Add command:

#!/usr/bin/env node

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

...

program
    .command('upgrade')
    .description("Check the js-plugin-cli version.")
    .alias('u')
    .action(() => {
        updateChk();
    });

...

implementnpm link --forceAnd then execute the configured commandxman u:

Implement a general scaffold tool for Web Engineering from 0 to 1

So far, we have implemented a basic but complete general scaffolding tool for Web engineering. You can modify and expand according to your actual needs.

summary

The essential function of a web engineering general scaffold is actually the following:

  • Quickly create infrastructure project structure;
  • Provide specifications and agreements for project development;
  • Customize different functions according to the actual project requirements to improve our efficiency.