We use a lot of scaffolding tools in our work, such as vue-CLI for Vue and create-react-app for React. Greatly improve our work efficiency, so today we will learn how to make a self – use front-end scaffolding.

The core depends on

  1. Commander Command line tool
  2. Download -git-repo Git repository code download
  3. The chalk command line output is styled
  4. Inquirer. Js command line interaction
  5. Ora command line loading effect
  6. The didyoumean script command matches
  7. Fs – Alternative to Extra FS.
  8. Log-symbols Log coloring
  9. Semver semantic log control
  10. Validate-npm-package-name Specifies the name of the verification package

The project structure

Project structures,

  1. Execute NPM init in an empty file
  2. To install all the above dependencies, run NPM install Commander… validate-npm-package-name -S

1. Initialization

Create a new bin/luchx.js file in the root directory and add the following code

#! /usr/bin/env node

// Check node version before requiring/doing anything else
// The user may be on a very old node version

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

program
  .version(require('.. /package').version)
  .usage('<command> [options]')

program.parse(process.argv)

if(! process.argv.slice(2).length) {
  program.outputHelp()
}

Copy the code

The first line of the file indicates that the file is running in the Node environment, followed by the introduction of Commander. The final program.parse method is used to parse arguments passed in from the command line.

2. Add the first instruction

Command There are two uses of the command. The following is an official example:

// Command implemented using action handler (description is supplied separately to `.command`)
// Returns new command for configuring.
program
  .command('clone <source> [destination]')
  .description('clone a repository into a newly created directory')
  .action((source, destination) = > {
    console.log('clone command called');
  });
 
// Command implemented using separate executable file (description is second parameter to `.command`)
// Returns top-level command for adding more commands.
program
  .command('start <service>'.'start named service')
  .command('stop [service]'.'stop named service, or all if no name supplied');

Copy the code

<> and [] are mandatory and optional respectively. Here we use the first method and add the following code:

program
  .command('create <app-name>')
  .description(' Create a project with template already created.')
  .action((name, cmd) = > {
    require('.. /lib/create')(name)
  })

Copy the code

3. Add the listener –help event

// add some useful info on help
program.on('--help', () = > {console.log()
  console.log(`  Run ${chalk.cyan('luchx <command> --help')} for detailed usage of given command.`)
  console.log()
})

Copy the code

The execution result

Interactive instructions

1. Create lib files in the root directory and add the create.js file.

module.exports = async function create (projectName) {}Copy the code

2. Name of the verification package

const path = require('path')
const fs = require('fs-extra')

const inquirer = require('inquirer')
const chalk = require('chalk')
const validateProjectName = require('validate-npm-package-name')

module.exports = async function create (projectName) {
  const cwd = process.cwd()
  const targetDir = path.resolve(cwd, projectName)
  const name = path.relative(cwd, projectName)

  const result = validateProjectName(name)
  if(! result.validForNewPackages) {console.error(chalk.red(`Invalid project name: "${name}"`))
    result.errors && result.errors.forEach(err= > {
      console.error(chalk.red.dim('Error: ' + err))
    })
    result.warnings && result.warnings.forEach(warn= > {
      console.error(chalk.red.dim('Warning: ' + warn))
    })
    process.exit(1)}if (fs.existsSync(targetDir)) {
    const { action } = await inquirer.prompt([
      {
        name: 'action'.type: 'list'.message: `Target directory ${chalk.cyan(targetDir)} already exists. Pick an action:`.choices: [{name: 'Overwrite'.value: 'overwrite' },
          { name: 'Cancel'.value: false}}]])if(! action) {return
    } else if (action === 'overwrite') {
      console.log(`\nRemoving ${chalk.cyan(targetDir)}. `)
      await fs.remove(targetDir)
    }
  }

  // ...
}

Copy the code

3. Inquirer. Js processes command interaction

const inquirer = require('inquirer')

module.exports = async function create (projectName) {
 	// ...

  const { bolierplateType, author, description, version } = await inquirer.prompt([
    {
      name: 'bolierplateType'.type: 'list'.default: 'vue'.choices: [{name: 'Vue'.value: 'vue'
        },
        {
          name: 'React'.value: 'react'}].message: 'Select the boilerplate type.'
    }, {
      type: 'input'.name: 'description'.message: 'Please input your project description.'.default: 'description',
      validate (val) {
        return true
      },
      transformer (val) {
        return val
      }
    }, {
      type: 'input'.name: 'author'.message: 'Please input your author name.'.default: 'author',
      validate (val) {
        return true
      },
      transformer (val) {
        return val
      }
    }, {
      type: 'input'.name: 'version'.message: 'Please input your version.'.default: '0.0.1',
      validate (val) {
        return true
      },
      transformer (val) {
        return val
      }
    }
  ])

  // ...
}

Copy the code

4. Package download file lib/downloadFromRemote. Js

const download = require('download-git-repo')

module.exports = function downloadFromRemote (url, name) {
  return new Promise((resolve, reject) = > {
    download(`direct:${url}`, name, { clone: true }, function (err) {
      if (err) {
        reject(err)
        return
      }
      resolve()
    })
  })
}

Copy the code

5. Add the download operation

const fs = require('fs-extra')
const chalk = require('chalk')
const logSymbols = require('log-symbols')
const downloadFromRemote = require('.. /lib/downloadFromRemote')

module.exports = async function create (projectName) {
  // ...
  
  downloadFromRemote(remoteUrl, projectName).then(res= > {
    fs.readFile(`. /${projectName}/package.json`.'utf8'.function (err, data) {
      if (err) {
        spinner.stop()
        console.error(err)
        return
      }
      const packageJson = JSON.parse(data)
      packageJson.name = projectName
      packageJson.description = description
      packageJson.author = author
      packageJson.version = version
      var updatePackageJson = JSON.stringify(packageJson, null.2)
      fs.writeFile(`. /${projectName}/package.json`, updatePackageJson, 'utf8'.function (err) {
        spinner.stop()
        if (err) {
          console.error(err)
        } else {
          console.log(logSymbols.success, chalk.green(`Successfully created project template of ${bolierplateType}\n`))
          console.log(`${chalk.grey(`cd ${projectName}`)}\n${chalk.grey('yarn install')}\n${chalk.grey('yarn serve')}\n`)
        }
        process.exit()
      })
    })
  }).catch((err) = > {
    console.log(logSymbols.error, err)
    spinner.fail(chalk.red('Sorry, it must be something error,please check it out. \n'))
    process.exit(- 1)})}Copy the code

run

This project is not published on NPM, just for study and research, you can pull the project and then execute NPM link, experience locally. To be used globally, we need to set it in package.json so that we can execute the luchx command at the beginning.

"bin": {
  "luchx": "bin/luchx.js"
},

Copy the code

The complete code above can be viewed at Github

reference

  • Vue – CLI vue. js standard tool for development
  • Commander Node.js complete solution for the command line interface
  • Inquirer command line interaction tool