This article source has been included in github, welcome to the little brothers miss sisters star, continue to update.

preface

In the first two articles, 10 minutes will take you to upgrade Webpack 5. In the first two articles, from 0 to 1, we will take you to draw a set of Webpack + Vue project template. We will teach you how to manually build a front-end project template from 0 to 1. It is obviously inefficient to generate a new project simply by copying the built project template. How do you get around inefficient copying? The answer is scaffolding /CLI. Then, based on the project template we built manually in the previous article, we will quickly generate the project we want by one key in the form of scaffolding /CLI.

Why scaffolding /CLI

The CLI is a command line interface.

What are the benefits of scaffolding?

  • Avoid inefficient manual copying of project templates
  • Standardize the project development directory structure
  • Unified team unified development style, facilitate cross-team cooperation, and later maintenance, reduce the cost of new recruits
  • Provide one-click front-end project creation, configuration, local development, plug-in extension and other functions, so that developers can focus more time on business

Scaffolding /CLI workflow

  • A complete scaffold usually contains these:Project creation,Add and delete project modules,Project package,Unified project testing,Project releasedAnd so on;
  • Below is a simple illustration of the scaffolding workflow:Different ends -> Execute various scaffolding commands -> Pull different project templates from the remote end -> Develop projects according to different templates drawn -> After developing the self-test, submit the code to the remote repository;

Scaffolding /CLI dependency analysis needed

The package name Functional description
commander Processing console command
chalk Beautify the style of our output on the command line
fs-extra File operations
inquirer Console query
handlebars Template engine rendering
log-symbols Print Log Reminder
ora Command line loading active effect
download-git-repo Remotely Downloading templates

How to write a minimalist scaffolding /CLI

Let’s give the scaffolding a name first, caoyp-cli

Complete step disassembly:

  1. Create a project (create)
  2. Custom scaffold start command (commander)
  3. Ask the user for information about the creation (inquirer)
  4. Download the template you need from Github (download-git-repo)
  5. Release project (npm)

1. Create projects

Before creating the project, let’s briefly define our project structure

├─ ├─ class.txt # ├─ class.txt # class.txt # class.txt # class.txt # class.txt # class.txt # class.txt # class.txt # class.txt # class.txt # class.txt # class.txt └ ─ package. JsonCopy the code

Create a project on Github

git clone  https://github.com/Paulinho89/caoyp-cli
cd caoyp-cli
Copy the code

Execute in project directory:

npm init -y
Copy the code

This will automatically help us generate a package.json file in the project root directory

{
  "name": "caoyp-cli"."version": "1.0.0"."description": "One-page front end scaffold"."main": "index.js"."scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [
    "caoyp-cli"."caoyp".scaffolding]."author": "caoyp"."license": "ISC"
}
Copy the code

Knocking on the blackboard, here is the key!!

Next we’ll add a bin field to package.json, and at installation time NPM will symlink the file to prefix/bin for global installation or./node_modules/.bin/ local installation. This way, it can be used globally. For example, the following uses caoyp-cli as the command name, and the execution file is index.js in the root directory;

{
  "name": "caoyp-cli"."version": "1.0.0"."description": "One-page front end scaffold"."main": "index.js"."bin": {
    "caoyp": "./bin/cli.js"
  },
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [
    "caoyp-cli"."caoyp".scaffolding]."author": "caoyp"."license": "ISC"
}
Copy the code

/bin/cli.js

#! /usr/bin/env node

console.log('caoyp-cli working ~~')
Copy the code

#! /usr/bin/env node — this is a code that tells your scripting tool (bash/ ZSH) that you want to run it under node environment.

To facilitate debugging, use the NPM link to link globally

➜  caoyp-cli git:(main) npm link
npm WARN [email protected] No description
npm WARN [email protected] No repository field.

audited 51 packages in1.7s found 0 December /usr/for1local/bin/caoyp-cli -> /usr/local/lib/node_modules/caoyp-cli/cli.js
/usr/local/lib/node_modules/caoyp-cli -> /Users/caoyp/study/caoyp-cli
Copy the code

Test it by typing caoyp on the command line

➜  caoyp-cli git:(main) caoyp

caoyp-cli working ~~
Copy the code

Finish the test and print out the results we want.

2. Customize the scaffolding start command

So first of all, what do we have to do?

  1. We’re going to usecommanderTo implement the
  2. referencevue-cliTo initialize some common commandscreate,help, etc.
  3. If the project already exists, you need to prompt the user whether it is neededForced to cover

2.1 Installation Dependencies

npm i commander --save
Copy the code

2.2 Creating Commands

In./bin/cli.js

#! /usr/bin/env node

const program = require('commander')

program
  // Define commands and parameters
  .command('create <app-name>')
  .description('create a new project')
  -for --force Forcibly creates a directory. If the created directory exists, the directory is overwritten directly
  .option('-f, --force'.'overwrite target directory if it exist')
  .action((name, options) = > {
    // Prints the execution result
    console.log('name:', name, 'options:', options)
  })
  
program
   // Set the version number
  .version(`vThe ${require('.. /package.json').version}`)
  .usage('<command> [option]')
  
// Parses the parameters passed by the user to execute the command
program.parse(process.argv);
Copy the code

Test it by typing caoyp on the command line

➜  caoyp-cli git:(main) caoyp
Usage: caoyp <command> [option]

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

Commands:
  create [options] <app-name>  create a new project
  help [command]               display help for command
Copy the code

Create [options]

= caoyp create = caoyp create = caoyp create = caoyp create = caoyp create = caoyp create = caoyp create = caoyp create = caoyp create = caoyp

➜  caoyp-cli git:(main) caoyp create
error: missing required argument 'app-name'➜ caoyp-cli git:(main) caoyp create my-project >>> name: my-project options: {} ➜ caoyp-cli git:(main) caoyp create my-project -f >>> name: my-project options: {force:true} ➜ caoyp-cli git:(main) caoyp create my-project --force >>> name: my-project options: {force:true }
Copy the code

At this point, we can get our input information at the command line.

2.3 Running Commands

Create a lib folder and create a create.js

module.exports = async function(name, options) {
    console.log('-----create.js', name, options);
}
Copy the code

/bin/cli.js introduces create.js

const program = require('commander');

program.command('create <app-name>')
    .description('create a new project')
    -for --force Forcibly creates a directory. If the created directory exists, the directory is overwritten directly
    .option('-f, --force'.'overwrite target directory if it exist')
    .action((name, options) = > {
        // Perform the creation task in create.js
        require('.. /lib/create.js')(name, options)
    })
Copy the code

Execute caoyp create my-project again and print the result

➜  caoyp-cli git:(main) caoyp create my-project
-----create.js
my-project {}
Copy the code

As described above, we need to consider whether the project directory exists at all when we execute the create;

  • If yes, a message is displayed indicating that the project already exists. If the value of -f is added to the existing project directory, the command is executed forcibly. In this case, the old directory is deleted and a new directory is created
  • If no, create a new directory

We need to use another plug-in fS-Extra to help us judge whether the file exists or not. Meanwhile, we need to install log-Symbols so that we can give users a more friendly reminder when printing logs

Install fs-extra and log-Symbols

npm i fs-extra log-symbols --save
Copy the code

We will continue to improve create.js along these lines

const path = require('path');
const fs = require('fs-extra');
const symbols = require('log-symbols');

module.exports = async function(name, options) {
    const cwd = process.cwd();
    // Directory address to be created
    const targetAir = path.join(cwd, name);
    // Check whether the directory already exists
    if(! fs.existsSync(targetAir)) {// TODO: create a new directory
    } else {
        console.log(symbols.error, chalk.red('Project already exists'));
        // Whether to force creation
        if (options.force) {
            await fs.remove(targetAir);
            // TODO: asks the user if they are sure to overwrite}}}Copy the code

The logic for labeling TODO is explained in more detail below.

2.4 Improve help commands

For those of you who are careful to use vue-CLI, –help will find a number of help commands that can guide you to use scaffolding ✅.

➜ ~ vue -help
Usage: vue <command> [options]

Options:
  -V, --version  output the version number
  -h, --help     output usage information

Commands:
  init           generate a new project from a template
  list           list available official templates
  build          prototype a new project
  create         (for v3 warning only)
  help [cmd]     display help for [cmd]
Copy the code

So, here we also imitate vue-cli to improve our help command, and first install Chalk to beautify the command line. Figlet helps us print our own logo.

const program = require('commander');
const figlet = require('figlet');
const chalk = require('chalk');

program
  .on('--help', () => {
    console.log('\r\n' + figlet.textSync('caoyp', {
      font: 'Ghost',
      horizontalLayout: 'default',
      verticalLayout: 'default',
      width: 80,
      whitespaceBreak: true
    }));
    console.log(`\r\nRun ${chalk.cyan(`caoyp <command> --help`)} for detailed usage of given command\r\n`)
  })
Copy the code

Run caoyp –help to see what is printed

➜  caoyp-cli git:(main) caoyp --help
Usage: caoyp <command> [option]

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

Commands:
  create [options] <app-name>  create a new project
  help [command]               display help for command

             ('-. _ (`-. ( OO ).-. ( (OO ) .-----. / . --. / .-'), -- -- -- -- -- -., -. _. ` \' .--./ | \-. \ ( OO'.....'\ `.'/ (__... --' '| | -'-.. - '-. / '| | | | | | -')     /  |  /  | | 
 /_) |OO  )\| |_.' |\_) | |\| |(OO \ / | |_.'| | | | ` -. "| | - | \ | | | | | / / \ _ | ___. ' 
(_' '--'\ | | | | `'  The '-'  '` -. / /. __) | | ` -- -- -- -- --'  `--'` -'` -- -- -- -- --'` -'      `--' Run caoyp  --help for detailed usage of given commandCopy the code

3. Ask the user to obtain the creation information

So again, before we do this, let’s analyze how to ask for user information, okay?

  1. Perfecting the previous TODO step: Ask the user if they want to overwrite the existing directory to download the latest one
const path = require('path');
const fs = require('fs-extra');
const symbols = require('log-symbols');

module.exports = async function(name, options) {
    const cwd = process.cwd();
    // Directory address to be created
    const targetAir = path.join(cwd, name);
    // Check whether the directory already exists
    if(! fs.existsSync(targetAir)) {// Create a new directory
        fn(name, targetAir);
    } else {
        console.log(symbols.error, chalk.red('Project already exists'));
        // Whether to force creation
        if (options.force) {
            await fs.remove(targetAir);
            // Create a new directoryfn(name, targetAir); }}}Copy the code
  1. Customize template information
const fn = (name, targetAir) = > {
    // Customize template project information
    inquirer.prompt([
        {
            name: 'version'.message: 'Please enter the project version'.default: '1.0.0'
        },
        {
            name: 'description'.message: 'Please enter project description information'.default: 'This is a custom scaffold generation project'
        },
        {
            name: 'author'.message: 'Please enter author name'.default: ' '
        }
    ]).then(res= > {
        // TODO downloads the Github template})}Copy the code

Caoyp create my-project: caoyp create my-project: caoyp create my-project: If you enter caoyp create my-project -f, you will find that the my-project directory created last time has been removed. Let’s enter the corresponding template information again and create a new template project.

4. Download the required template from Github

As usual, let’s first analyze how to download a user’s template from Github.

  1. githubWe have uploaded the default download in advanceThe template

  1. With the help ofdownload-git-repoAuxiliary Download template

Install a dependency on Download-git-repo to download templates from Github. Handlebars user dynamic rendering template prompts; Ora gives a loading effect when downloading.

npm i download-git-repo handlebars ora --save
Copy the code

Create a generator.js file in./lib

const ora = require('ora');
const symbols = require('log-symbols');
const inquirer = require('inquirer');
const fs = require('fs-extra');
const downloadGitRepo = require('download-git-repo');
const chalk = require('chalk');
const handlebar = require('handlebars');

class Generator {
    constructor(name, targetDir, res) {
        this.name = name;
        this.targetDir = targetDir;
        this.res = res;
    }

    async download() {
        // 1) Add the download address
        const requestUrl = `github.com:Paulinho89/webpack5-single-template#master`;
        const spinner = ora('Downloading template, source address:${requestUrl}`);
        spinner.start();
        // 2) Call the download method
        await downloadGitRepo(
            // Direct download, default download master
            requestUrl,
            this.targetDir,
            (error) = > {
                if (error) {
                    spinner.fail();
                   Console. log(symbol. error, chalk. Red (error));
                } else {
                    spinner.succeed();
                    const fileName =  `The ${this.name}/package.json`;
                    // The command line prompts are displayed after the download is successful
                    const meta = {
                        name: this.name,
                        version: this.res.version,
                        description: this.res.description,
                        author: this.res.author
                    }
                    // {{name}}/package.json if the path exists
                    if (fs.existsSync(fileName)) {
                    // Read the prompt message
                    const content = fs.readFileSync(fileName).toString();
                    const resultContent = handlebar.compile(content)(meta); // Write a prompt to the package.json text of the template project
                    fs.writeFileSync(fileName, resultContent);
                    }
                    console.log(symbols.success, chalk.green('Project initialization successful'))
                    console.log(symbols.info, `\r\n  cd ${chalk.cyan(this.name)}`)
                    console.log(symbols.info, `\r\n npm install`)
                    console.log(symbols.info, 'npm run start\r\n')}})}}module.exports = Generator;
Copy the code

Git clone status 128 git clone status 128 git clone status 128

Solution:

Wrong way to write:

  • Go directly to the Github template link as the first parameter passed directly
  • Use {clone: true}
downloadGitRepo('https://github.com/Paulinho89/webpack5-single-template', name, {clone: true}, (err) = > {
    console.log(err ? 'Fail' : 'Success')})Copy the code

Correct way to write it:

  • Use github.com: username/template name # branch name
  • Remove the {clone: true}
downloadGitRepo('github.com:Paulinho89/webpack5-single-template#master', name, (err) = > {
    console.log(err ? 'Fail' : 'Success')})Copy the code
  1. Complete the previous TODO step: After the download is successful, fill in the customized template information entered each time into the template projectpackage.jsonin

The version, Description, and Author fields in the package.json of the template project can be written as dynamic rendering modes.

{
  "name": "webpack-single-template"."version": "{{version}}"."description": "{{description}}"."main": "index.js"."scripts": {
    "start": "webpack serve --config build/webpack.config.dev.js"."build:test": "rimraf dist && webpack --config build/webpack.config.test.js"."build:prod": "rimraf dist && webpack --config build/webpack.config.prod.js"."lint": "eslint --fix --ext .vue,.js src build"."lint-staged": "lint-staged"."build:stats": "webpack --config build/webpack.config.prod.js --json > stats.json"."analyz": "cross-env NODE_ENV=production ANALYZE=true npm_config_report=true npm run prod"."dll": "webpack --config build/webpack.config.dll"
  },
  "pre-commit": [
    "lint-staged"]."lint-staged": {
    "*.{js, vue}": [
      "eslint --fix"."git add"]},"author": "{{author}}"."license": "ISC"
}
Copy the code
const Generator = require('./generator');

const fn = (name, targetAir) = > {
    // Customize template project information
    inquirer.prompt([
        {
            name: 'version'.message: 'Please enter the project version'.default: '1.0.0'
        },
        {
            name: 'description'.message: 'Please enter project description information'.default: 'This is a custom scaffold generation project'
        },
        {
            name: 'author'.message: 'Please enter author name'.default: ' '
        }
    ]).then(res= > {
       // Create the project
        const generator = newGenerator(name, targetAir, res); generator.download(); })}Copy the code

When the download template is complete, the user’s input is rendered to package.json.

5. Publish projects

After the above logic sorting, the simple scaffolding we built has been completed, and we need to release it to the NPM warehouse when we actually use it.

Release process:

  1. Sign up for aNPM account

If the account is newly registered, you must log in to the email address for verification. Otherwise, the account fails to be advertised.

  1. perfectpackage.jsonTo determine the release number
{
  "name": "caoyp-cli"."version": "1.0.0"."description": "One-page front end scaffold"."main": "index.js"."bin": {
    "caoyp": "./bin/cli.js"
  },
  "directories": {
    "lib": "lib"
  },
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "repository": {
    "type": "git"."url": "git+https://github.com/Paulinho89/caoyp-cli.git"
  },
  "author": "caoyp"."keywords": [
    "caoyp-cli"."caoyp".scaffolding]."license": "ISC"."bugs": {
    "url": "https://github.com/Paulinho89/caoyp-cli/issues"
  },
  "homepage": "https://github.com/Paulinho89/caoyp-cli#readme"."dependencies": {
    "axios": "^ 0.21.1"."chalk": "^ 4.4.1"."commander": "^ 7.2.0"."download-git-repo": "^ 3.0.2." "."figlet": "^ 1.5.0." "."fs-extra": "^ 10.0.0"."handlebars": "^ 4.7.7"."inquirer": "^ 8.1.1"."log-symbols": "^ 3.0.0"."ora": "^ 5.4.1." "}}Copy the code
  1. usenpm publishrelease
npm publish
Copy the code

After successful publishing, you can go to the NPM repository and see the packages you have published.

Follow our readme.md setup command to install the scaffold to our local, then create, restart, and everything is fine; OK, now our scaffolding from project construction to release the whole process is over.

Wrote last

  • At the moment, the scaffolding is a bit more simple, and some steps need to be refined, such as when we pull templates from the remote warehouse, which we usually do during actual developmentMultiple versions of templates, and every template will have itDifferent tag; Each pull can be done by selecting different version templates, different tags, adding more templates, deleting templates, and so on. Next, I will continue to improve these functions into scaffolding.

If you feel that this article is a little help to you, you are welcome to give me a thumbs up, comments, attention and support after reading, your support is my writing motivation, thank you ~~😁