What happens when you run a terminal command to create a scaffold?

Scaffolding, of course, uses a lot of command line knowledge. This article will introduce you to a simple VUE scaffolding from scratch to the whole process, I hope you have a harvest.

Project directory

| ─ ─ bin | └ ─ ─ the try. Js | ─ ─ the config | └ ─ ─ config. Js | ─ ─ lib | ├ ─ ─ actions. Js | ├ ─ ─ the create. Js | └ ─ ─ otherCommand. Js | ─ ─ utils | └ ─ ─ terminal. Js | ─ ─ templates | └ ─ ─ component. The ejsCopy the code

Use the library

Command line control tool -- commander Download and clone tool -- download-git-repo command line interaction tool -- Inquirer file operation -- fs-extra address operation -- pathCopy the code

Directly to fit

1. Create a project

npm init -y  // Initialize package.json
Copy the code

2. Create the execution file and configure the directory

2.1 Create the /bin folder and configure the entry file of the command: /bin/try.js

The first line of the file must read:

#! /usr/bin/env node
Copy the code

#! Called shebang, this line is added to specify that the script file is executed as node.

2.2 Configure the bin field in package.json

We named our first scaffold tool: just-cli, and the value corresponding to the key name refers to the address of the command line execution entry file.

{
	"bin": {
        "just-cli": "./bin/try.js"}},Copy the code

3 Link commands globally

Run ** NPM link ** on the terminal to register the configured commands globally.

npm link  // Unlink: NPM unlink
Copy the code

When Windows users go to the NPM installation directory, they will find that we have these files:

  • just-cli
  • just-cli.cmd
  • just-cli.ps1

PS: MAC users can check /usr/local/lib/node_modules.

4 Configure the version number

4.1 First install the commander package
npm install commander --save
Copy the code
4.2 Configuring the version: just-cli -v & just-cli -v
#! /usr/bin/env node

/* * file: /bin/try.js */

/ / into the commander
const program = require('commander')

// Set the version number
program.version(`Version is The ${require('.. /package.json').version}`.'-v, --version')// Support lowercase: '-v'
    .description('Write a scaffold by hand')
    .usage('<command> [options]')

// Parses arguments, usually after the definition command
program.parse(process.argv);
Copy the code

At this point, you have configured the version information for just-cli, which is dynamically obtained from the scaffold project package.json and supports the following two case-insensitive ways to view the version information.

just-cli -v
just-cli -V
Copy the code

Try printing your own version of the scaffolding!

5 First command

Define a command of the form: create name [options] that is used to initialize the scaffold.

5.1 Defining Commands

First we define the command, configure the command to take an argument: name, add a description of the command, give it the ability of -f, and specify the execute function: createAction

/* * File: /lib/create.js * Command: scaffolding * create 
      
        */
      

const program = require('commander')
const {createAction} = require('./actions')
const create = function(){
    program.command('create <name>')
    .description('create a new project')
    .option('-f, --force'.'title to use before name')
    .action(createAction)
}
module.exports =create
Copy the code

Then import the above configuration into the command entry file.

/* * file: /bin/try.js */

/ /...

// Import the split configuration file
const createConfig = require('.. /lib/create')

// Add a split configuration: create 
      
createConfig()

/ /...
Copy the code
5.2 Defining the Execution content of the command
/* * File: /lib/actions.js * Purpose: Define the execution of the command */
const open = require('open')
const { promisify } = require('util')

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

const { labAdress } = require('.. /config/lab-config')
const { spawnCommand } = require('.. /utils/terminal')



const createAction = async (project,options) => {
    console.log(`creating a project:${project},\noptions:\nThe ${JSON.stringify(options)}`)
	
    // Clone the template
    console.log(`downloading the templates documents`)
    await download(labAdress, project, { clone: true })

    // Run the NPM install command
    // Check the type of the command in the operating system, and Windows will return 'win32' regardless of its own operating system 32-bit / 64-bit
    const npm = process.platform === 'win32' ? 'npm.cmd' : 'npm'
    console.log(`spawn npm install:`)
    await spawnCommand(npm, ['install'] and {cwd: `. /${project}` })

    // Run the NPM run dev command synchronously
    // The reason for synchronous calls: the thread blocks subsequent code execution after the project runs
    console.log(`spawn npm run dev`)
    spawnCommand(npm, ['run'.'dev'] and {cwd: `. /${project}` })

    // // Open the browser synchronously
    open('http://localhost:8080');

}
module.exports = {
    createAction
}
Copy the code

See what needs to be understood in particular:

  1. The /config/config.js file stores the download address of the template. Global variables are stored in this file.
  2. The promisify method provides a way to turn synchronous methods into asynchronous methods, avoiding callback hell.
  3. Opening a browser is not usually configured in a scaffold, but is used here to visualize the entire scaffold creation process. It is usually specified in the Webpack of the template to open the page for the corresponding port after the packaging is complete.
  4. SpawnCommand is the encapsulated child_process. Spwan method. The encapsulated content is as follows: In fact, both the exec and spawn methods of child_process can execute terminal commands. Child_process. spawn is suitable for scenarios where there is no limit to the amount of data returned.
/* Run the terminal command */
const { spawn } = require('child_process')

// Args parameters include command, args, and options
const spawnCommand = (. args) = > {
    return new Promise((resolve, reject) = > {
        constspawnProcess = spawn(... args)// The copy console prints to the main process
        spawnProcess.stdout.pipe(process.stdout)
        spawnProcess.stderr.pipe(process.stderr)
        // The shutdown of the listener process is triggered upon completion or error
        spawnProcess.on('close'.() = > {
            resolve()
        })
    })
}
module.exports = {
    spawnCommand
}
Copy the code

6 Design interactive logic

In the previous section, we implemented the use of the command line to create the scaffold template. Next, we need to optimize the process of creating the scaffold by adding the necessary interactive actions and logical judgments to the execution method of the command. The inquirer tool is introduced to solve the problem of command line interaction, interaction and logical judgment code is as follows.

/* * File: /lib/actions.js * Purpose: Define the execution of the command */
const open = require('open')
const path = require('path')
const fsextra = require('fs-extra')
const Inquirer = require('inquirer')
const { promisify } = require('util')
const download = promisify(require('download-git-repo'))

const { labAdress } = require('.. /config/lab-config')
const { spawnCommand } = require('.. /utils/terminal')
const { delDir } = require('.. /utils/common')

async function createAction(projectName, options) {
    console.log(`${projectName},\noptions:\nThe ${JSON.stringify(options)}`)

    const cwd = process.cwd();// Obtain the working directory where the current command is executed
    const targetDir = path.join(cwd, projectName);// The target directory
    console.log(targetDir)

    if (fsextra.existsSync(targetDir)) {
        if (options.force) {
            delDir(targetDir);
            console.log('Deletion of original directory succeeded')
            create(projectName)
        } else {
            let { action } = await Inquirer.prompt([
                {
                    name: 'action'.type: 'list'.message: 'Destination folder already exists, please choose whether to overwrite :'.choices: [{name: 'cover'.value: true },
                        { name: 'cancel'.value: false}}]])if(! action) {console.log('Cancel operation')
                return
            } else {
                console.log('\r\n is deleting the original directory.... `);
                delDir(targetDir)
                console.log('Deletion succeeded')
                create(projectName)
            }
        }
    } else {
        create(projectName)
    }
}

const create = async (projectName) => {
    // See section 5.2
}
module.exports = {
    createAction
}
Copy the code

DelDir is a recursive method that iterates through files and deletes folders, defined as follows:

// Introduce the fs module
const fs = require('fs-extra');

function delDir(path) {
  // Read all files and folders in the folder
  const list = fs.readdirSync(path)
  list.forEach((item) = > {
    // Splice paths
    const url = path + '/' + item
    // Read the file information
    const stats = fs.statSync(url)
    // Determine whether it is a file or a folder
    if (stats.isFile()) {
      // If it is a file, delete the file
      fs.unlinkSync(url)
    } else {
      // If the current folder is a folder, then call itself recursively
      arguments.callee(url)
    }
  })
  // Delete the empty folder
  fs.rmdirSync(path)
}

module.exports={
    delDir
}
Copy the code

7 Other functions of scaffolding

In addition to creating templates, scaffolding should have other instructions that make it easier to work. Here’s how to use the scaffolding command to add a new component. We define a form like:

just-cli component <componentName>  -d <destination>
Copy the code

-d and destination are optional. We need the following four steps to execute this command.

  1. Template file
  2. Interactive judgment: whether to exist/whether to override/address parameters, etc
  3. Pass in the parameters and compile the template
  4. Generates a.vue file for the specified location/default location

Do it now! First we define the command, configure the -f and -d capabilities, and take the dest argument to specify the location of the file

/* * file: /lib/newComponent.js * Define command: add page * page 
      
        */
      

const program = require('commander')
const { addPageAction } = require('./actions')
const page = function () {
    program.command('page <name>')
        .description('add a new page')
        .option('-f, --force'.'spwan with force')
        .option('-d, --dest <dest>'.'Specify a storage address, for example :just-cli component 
      
        -d /studio/task'
      )
        .action(addPageAction)
}
module.exports = page
Copy the code
7.1 Template Files

Use the EJS template to implement the template parameter placeholder, a default. Vue file template as follows:

<template>
    <div class="<%= data.lowerName %>">{{msg}}
        <h1>{{message}}</h1>
    </div>
</template>
<script>
    export default {
        name: '<%= data.name %>'.props: {
            msg: String
        },
        components: {},
        mixins: [].data() {
            return {
                message: "<%= data.name %>"}},create(){},mounted(){},computed: {}}</script>
<style>
    .<%=data.lowerName %> {}
</style>
Copy the code

Note: Template strings can also do this.

7.2 Interactive Judgment

Omitted, refer to section 6

7.3 Compiling templates

The template compiler functions are encapsulated as follows:

const ejs = require('ejs')
const path = require('path');

const compileEjs = (templateName, data) = > {
    const templatePosition = `.. /templates/${templateName}`
    const templatePath = path.join(__dirname, templatePosition)
    return new Promise((resolve, reject) = > {
        ejs.renderFile(templatePath, { data }, {}, (err, res) = > {
            if (err) {
                console.log(err);
                reject(err)
                return
            }
            resolve(res)
        })
    })
}
Copy the code

The data parameter contains the parameter to be passed to the template, such as name.

7.4 Generating a. Vue file in the specified location/default location

The file write function is encapsulated as follows:

const fs = require('fs-extra');
const writeToFile = (path, templateContent) = > {
    return fs.promises.writeFile(path, templateContent)
}
Copy the code

After completing these four steps in sequence, a command to add a new component is complete!

————————————————————

Learn here, friends have been able to use the common project configuration by uploading the code warehouse, running scaffolding to generate the project scaffolding template, but this is just a little scaffolding, scaffolding customization is far more than these content, please open your imagination, begin to customize your own scaffolding!