Write a standard process scaffolding

For some process-oriented, without any technical content of work, can be handed over to the computer to complete it? Can we just enter a few variables and get the job done? Usually, the first thing that comes to mind is using a script like a shell, but you can do a lot more with Node and do it better. This section provides an example of how node can be used to process work by implementing a front-end scaffolding.

preface

In front end engineering, we want to improve development efficiency by using the latest syntax (JS and CSS) and various code checking tools. We also want to inherit some of the best practices, common practices, deployment configurations, performance optimizations, and so on that we’ve accumulated. We would love to be able to apply these things to new projects.

The first thing that came to mind was to create a best practices sample project on GitLab, with regular maintenance and updates of the latest plug-ins. When we start a new project, developers only need to download the project and make a few configuration changes to apply it. However, this small amount of configuration can waste endless time for a variety of reasons, such as poorly written configuration documentation, documentation that is not updated in a timely manner, and developers who have no experience with configuration.

Therefore, we expect a sample project and a scaffolding that automatically generates the project so that we can create the project correctly through multiple steps.

Below we will explain how to implement a front-end scaffolding from several aspects.

  • Train of thought
    • General idea:
    • Concrete implementation idea
  • Step by step to achieve
    • 1. Register commands
      • 1.1: Initialization project
      • 1.2: to createsrc/bin.jsfile
      • 1.3: to adjustpackage.json
      • 1.4: runnpm link
      • 1.5 test
      • The sample project
    • 2. Parse commands
      • 2.1 adjustsrc/bin.js
      • 2.2 test
      • The sample project
    • 3 Obtain user information
      • 3.1 createprompts.jsfile
      • 3.2 test
    • 4. Download the template project
      • 4.1 to prepare
      • 4.2 createsrc/download-template.jsfile
      • test
    • 5. Modify the file
      • 5.1 createupdate-files.jsfile
      • 5.2 test
    • 6. Dependencies in installation engineering
      • 6.1 createsrc/install-dependencies.js
      • 6.2 test
    • 7. To adjustsrc/bin.jsfile
    • 8. Full presentation
    • 9. Publish to NPM
    • 10. Sample project
  • Recommend a document

Train of thought

General idea:

  • The user interminalInput necessary information;
  • Automatic script slavegitlabdownloadTemplate engineering;
  • The script to useUser input informationModify theTemplate engineeringFile in;

Concrete implementation idea

  1. Registers the command and can receive arguments from the command line
  2. Get user input by asking
  3. fromgitlabTo gainTemplate engineering
  4. Rewrite files in the template project
  5. Automatically download dependencies in the project

We will implement this step by step

Step by step to achieve

1. Register commands

We need to use the NPM link command to link our ** executable (bin)** to {prefix}/bin/{name}. In other words, the NPM link command lets us run our scripts directly from the command line anywhere.

Here are our concrete implementation steps:

1.1: Initialization project

Initialize a project and install plugins such as.EditorConfig, eslint.js, prettier.js in the project. See an easy-to-maintain WebPack project for details.

Example project: examples/01

1.2: to createsrc/bin.jsfile

In the first line, #! Is a convention tag that tells the system what interpreter the script needs to execute, that is, which Shell to use. #! /usr/bin/env node tells the system to dynamically find node in the PATH directory to execute your script file.

src/bin.js

#! /usr/bin/env node

// Get parameters
console.info(process.argv)
Copy the code

1.3: to adjustpackage.json

In package.json, add the bin property

{
  "bin": {
    "lcli": "src/bin.js"}}Copy the code

1.4: runnpm link

Run a command in the project root directory to create a soft connection

npm link
Copy the code

After executing the script, the link information is displayed in Terminal:

. /usr/local/bin/scli -> /usr/local/lib/node_modules/00/src/bin.js /usr/local/lib/node_modules/00 -> /Users/CodingNutsZac/Documents/git/write-a-cli/examples/01Copy the code

1.5 test

Run the terminal command and enter lcli to obtain the following results:


2. Parse commands

From the console, we know that the input parameters start with the third element of argv array. We can parse the parameters with switch or if else, or we can use the better commander. Js library

Commander. Js is a lightweight NodeJS library that provides powerful user command line input and parameter parsing. Commander.js is a port from the Ruby project of the same name. You can refer to the commander. Js documentation for details

2.1 adjustsrc/bin.js

src/bin.js

#! /usr/bin/env node

const { program } = require('commander')
const version = require('.. /package.json').version

program.version(version, '-v, --version')

program
  .usage('<command> [options]') // User prompts
  .command('create') // If there is no action, the x-init file will be executed in the same directory
  .description('Create a project')
  .action(async() = > {console.info('----done----')
  })

program.parseAsync(process.argv)
Copy the code

2.2 test

Enter in terminal

fcli

fcli create
Copy the code

View the results:

The sample project

Example project: examples/02

3 Obtain user information

3.1 createprompts.jsfile

We use inquirer to help us query user input.

We set the project name, version number, description, template, package management tool, and so on to get a JSON result.

src/prompts.js

const fse = require('fs-extra')
const inquirer = require('inquirer')
const cwd = process.cwd()

const templates = require('.. /config/default.json').templates
const choices = Object.keys(templates).map((type) = > ({
  name: type,
  value: type
}))

// Set the interaction problem
const projectNameQuestion = {
  type: 'input'.message: 'Please enter project name (English) :'.name: 'name'.validate: function (name) {
    if (!/^[a-zA-Z_][\w_\-]+$/g.test(name)) {
      return 'Please enter the correct project name! '
    }
    if (fse.existsSync(`${cwd}/${name}`)) {
      return 'This folder already exists! '
    }
    return true}}const versionQuestion = {
  type: 'input'.message: `Version: `.name: 'version'.default: '0.1.0 from'.validate: function (val) {
    if () / ^ ((\ d {1, 3} \.) (\ d {1, 3} \. (\ d {1, 3}) $/ g.test(val)) {
      // Check digit
      return true
    }
    return 'Please enter the correct format [number.number.number], for example: 11.22.33'}}const descriptionQuestion = {
  type: 'input'.message: 'Please enter project description:'.name: 'description'
}

const templateQuestion = {
  type: 'list'.message: Please select the template to download:.name: 'template'.choices: [...choices]
}

const cliQuestion = {
  type: 'list'.message: Please select package management tool (if it is a company project, yarn must be used!) : `.name: 'cli'.choices: [{name: 'yarn'.value: 'yarn'
    },
    {
      name: 'npm'.value: 'npm'
    },
    {
      name: 'cnpm'.value: 'cnpm'}}]async function getUserInputs() {
  const answers = await inquirer.prompt([
    projectNameQuestion,
    versionQuestion,
    descriptionQuestion,
    templateQuestion,
    cliQuestion
  ])
  return {
    ...answers,
    projectPath: `${cwd}/${answers.name}`}}module.exports = getUserInputs
Copy the code

3.2 test

For convenience, we call the getUserInputs() method directly on the last line of SRC /prompts.

// ...

const promise = getUserInputs()
promise.then((answer) = > {
  console.info(answer)
})
Copy the code

Run SRC /prompts. Js with Node to see the result

4. Download the template project

4.1 to prepare

We use the following plug-ins to improve the user experience during the download process:

Download-git-repo: Downloads a Git template

Chalk: Adds color to text output by the client

Ora: Displays the progress bar

Symbols: Adds ICONS to the log

4.2 createsrc/download-template.jsfile

In SRC /download-template.js, the main implementation is to use the download-git-repo library to download the template project according to the git address of the template.

Callback is the only method available in download-Git-repo. To avoid callback hell, we return a Promise object.

src/download-template.js

const fse = require('fs-extra')
const downloadTemplate = require('download-git-repo')
const chalk = require('chalk')
const ora = require('ora')
const symbols = require('log-symbols')

// Where to download the template
const templateGitUrls = require('.. /config/default.json').templates

async function downloadTemplates(options) {
  return new Promise((resolve, reject) = > {
    // 1. Delete the folder with the same name as the project
    if (fse.existsSync(options.projectPath)) {
      const fse = require('fs-extra')
      fse.removeSync(options.projectPath)
    }
    // 2. Download the template
    const spinner = ora(Download `${options.template}In the template... `).start()
    downloadTemplate(
      `direct:${templateGitUrls[options.template].url}`,
      options.name,
      { clone: true },
      (err) = > {
        // If it fails, end the process
        if (err) {
          spinner.fail()
          console.error(
            symbols.error,
            chalk.red(`${err}\n Failed to download template, network problems may be caused... `)
          )
          reject(err)
          process.exit(1)}// If successful, return options
        spinner.succeed('Download successful! ')
        resolve(options)
      }
    )
  })
}

module.exports = downloadTemplates

// const name = 'hello-world'
// downloadTemplates({
// projectPath: process.cwd() + '/' + name,
// name,
// template: 'web'
// })
Copy the code

test

We add it on the bottom line

// ...

const name = 'hello-world'
downloadTemplates({
  projectPath: process.cwd() + '/' + name,
  name,
  template: 'web'
})
Copy the code

Run SRC /download-template.js with Node to check the result

5. Modify the file

5.1 createupdate-files.jsfile

We read package. JSON in JSON format with the help of FS-extra, and save it in JSON form after modification.

src/update-files.js

const fse = require('fs-extra')

function updateFiles(options) {
  return new Promise((resolve, reject) = > {
    / / update package. Json
    updatePackageJson(options)
    resolve()
  })
}

function updatePackageJson(options) {
  const packageJson = `${options.projectPath}/package.json`
    // 1. Read the JSON file synchronously
    const json = fse.readJsonSync(packageJson)
    // 2. Modify the file
    json.name = options.name
    json.description = options.description
    json.version = options.version
    // 3. Generate json files
    fse.outputJsonSync(packageJson, json, { spaces: 2})}module.exports = updateFiles
Copy the code

5.2 test

We add it on the bottom line

// ...

updateFiles({
  projectPath: process.cwd() + '/' + 'hello-world'.name: 'hello-world'.description: 'description'.version: '1.1.1'
})
Copy the code

Run SRC /update-files.js with Node to see the result

6. Dependencies in installation engineering

6.1 createsrc/install-dependencies.js

This step is relatively simple, we use cross – spawn allows us to perform cross-platform yarn | | NPM script installation engineering of dependency.

src/install-dependencies.js

const spawn = require('cross-spawn')
const chalk = require('chalk')
const symbols = require('log-symbols')

function install(options) {
  const cwd = options.projectPath || process.cwd()
  console.info(symbols.info, 'Project Catalogue:' + cwd)
  return new Promise((resolve, reject) = > {
    const command = options.cli || 'npm'
    const args = ['install'.'--save'.'--save-exact'.'--loglevel'.'error']
    / / in the download directory to execute script yarn | | NPM script
    const child = spawn(command, args, {
      cwd,
      stdio: ['pipe', process.stdout, process.stderr]
    })

    // The script is called back after execution, and returns a promise
    child.once('close'.(code) = > {
      if(code ! = =0) {
        console.error(symbols.error, chalk.red('Failed to install dependencies... Rattail juice, please... `))
        reject({
          command: `${command} ${args.join(' ')}`
        })
        return
      }
      resolve()
    })
    child.once('error', reject)
  })
}

module.exports = install

Copy the code

6.2 test

We add it on the bottom line

// ...

install({
  projectPath: process.cwd() + '/' + 'hello-world'.cli: 'yarn'
})
Copy the code

Run SRC /update-files.js with Node to see the result

7. To adjustsrc/bin.jsfile

Finally, we complete the process by referring to the files in steps 2, 3, 4, 5, and 6 in SRC /bin.js.

#! /usr/bin/env node

const { program } = require('commander')
const version = require('.. /package.json').version
const symbols = require('log-symbols')

// Get user input
const getUserInputs = require('./prompts.js')
const downloadTemplate = require('./download-template')
const updateFilesWithUserInputs = require('./update-files')
const installDependencies = require('./install-dependencies')

program.version(version, '-v, --version')

program
  .usage('<command> [options]') // User prompts
  .command('create') // If there is no action, the x-init file will be executed in the same directory
  .description('Create a project')
  .action(async() = > {try {
      // 1. Get the data entered by the user in terminal. }
      const options = await getUserInputs()
      // 2. Download the code from gitlab and save it as a folder
      await downloadTemplate(options)
      // 3. Copy the contents of options into the downloaded file
      await updateFilesWithUserInputs(options)
      / / 4. Execution (yarn | | NPM) install command to download
      await installDependencies(options)
    } catch (e) {
      console.error(symbols.error, e)
    }
  })

program.parseAsync(process.argv)

Copy the code

8. Full presentation

Run the fcli command in terminal

The final generated project file

9. Publish to NPM

For easy use, we can publish the script to NPM. We recommend several articles for your reference:

  • Fzyun. IO /projects/rd…
  • First release NPM package: www.jianshu.com/p/ea64fd016…
  • Release your NPM package: www.cnblogs.com/cangqinglan…

Recommend a document

  • The role of NPM link command analyses: blog.csdn.net/juhaotian/a…