paint-brush
How to Create a Customer Frontend Scaffoldby@montmorency
314 reads
314 reads

How to Create a Customer Frontend Scaffold

by ClarenceAugust 9th, 2023
Read on Terminal Reader
Read this story w/o Javascript
tldt arrow

Too Long; Didn't Read

Frontend scaffolding expedites front-end project development by providing predefined structures and features. Building a custom scaffold involves using toolkits like "commander" for CLI personalization, "inquirer" for interactive user sessions, "fs-extra" for enhanced file operations, and others. The process includes creating directories, selecting templates, and configuring projects. Libraries like "chalk" and "figlet" enhance visual aesthetics. The scaffolding tool is tested for directory creation, template selection, and JSON file generation.

People Mentioned

Mention Thumbnail
featured image - How to Create a Customer Frontend Scaffold
Clarence HackerNoon profile picture


In the realm of practical development, we oft employ scaffolding to erect the edifice of front-end engineering projects. Esteemed frameworks such as vue-cli, create-react-appeach boasts its own scaffoldings.


Within grand corporations, bespoke scaffoldings are fashioned to suit the internal milieu, bestowing celerity upon the construction of projects. Through the medium of command-line interactivity, one may swiftly fashion an inaugural project by selecting the desired configurations and integrations.


Considering the manifold employments of these scaffoldings, why not fashion one tailored to thy own proclivities? This blog shall embark upon the construction of a custom-made scaffolding tool, commencing from naught.


Content Overview

  • What is Frontend Scaffolding?
  • Creating the Scaffold
  • Enhancing the Scaffold
  • Perfecting the Scaffold


What is Frontend Scaffolding?

Frontend scaffolding refers to a tool or framework that aids in the rapid development and organization of front-end projects.


It provides a predefined structure, configuration, and set of functionalities, allowing developers to bootstrap their projects quickly and efficiently.


These scaffolding tools often come with built-in features such as project initialization, dependency management, code generation, and build automation.


By using front-end scaffolding, developers can save time and effort in setting up a project from scratch and focus more on actual development tasks.


Before embarking upon the construction of scaffolding, it is imperative to familiarize oneself with a repertoire of indispensable toolkits.


  • commander: It signifies the ability to personalize command-line instructions, whereby upon entering a bespoke command, the system shall duly execute the corresponding operation.


  • inquirer: It bestows upon thee the power to engage in interactive question-and-answer sessions within the terminal, thus facilitating seamless choice-based interactions.


  • fs-extra: It is an extension of the “fs” module in Node.js. It offers a plethora of convenient APIs and inherits all the methods of “fs” while adding support for promises. This allows for more streamlined and asynchronous file system operations in your code.


  • download-git-repo: It can download remote repositories.


  • chalk: Chalk is a very popular Node.js module that is used to add styles and colors to the terminal, thus beautifying the output. It provides a simple and easy-to-use API that allows you to customize the appearance of the output by setting different styles and colors.


  • figlet: Figlet is a tool used to output ASCII art font logos in the terminal. It can convert ordinary text into beautiful ASCII art fonts and display them in the terminal.


  • ora: Ora is a Node.js module used to create console-loading animations. It provides a set of simple and easy-to-use APIs that help you display various types of loading animations in the terminal.



Creating the Scaffold

Initiating the project by creating a new directory.


$ mkdir foo
$ cd foo
$ npm init -y

Wrote to /Users/stt/foo/package.json:

{
  "name": "foo",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC"
}


Create the bin directory, primarily designated for housing command program entry files. Subsequently, fashioning a index.js file within the said directory. The directory structure shall be as follows:


foo   
├─ bin
│  ├─ index.js          
└─ package.json  


Modify the package.json file, wherein the command’s entry file shall be revised to index.js.

Moreover, configuring the foo command within the bin directory as a swift launching directive for the program.


{
    "name": "fo",
    "version": "1.0.0",
    "main": "index.js",
    "bin": {
        "foo": "./bin/index.js"
    },
  ...
}


Modifying the entry file bin/index.js to the following content.


The file commences with #! to indicate that it is treated as an executable file and can be executed as a script.


The subsequent /usr/bin/env node signifies that this file is executed using Node.js, which is located based on the environment variables in the user’s installation root directory:


#! /usr/bin/env node

console.log('hello world')


To link the current command globally and test its execution, you can use the following command in your terminal:


$ npm link


This will create a symbolic link in the global node_modules directory, allowing you to execute the command from anywhere on your system. Once the linking process is completed, you can test the command by running:


$ foo


If everything is set up correctly, the command should execute successfully.


$ foo
hello world


Indeed, when using third-party toolkits, it is common to encounter outputs with different colors and logos.


One way to achieve this is by utilizing the chalk and figlet libraries.


However, it is important to note that such operations are not mandatory.


These libraries are used to enhance the visual appeal and aesthetics of the output, but they are not essential for the functionality of the program.


Below is a simple example of how to use chalk in Terminal to beautify the output: chalk.js .


$ npm install chalk -S

$ vi chalk.js
import chalk from 'chalk';
console.log(chalk.blue('hello world'));
console.log(chalk.red.bold('hello world'));
console.log(chalk.bgYellow.black('hello world'));


Then we run:


$ node chalk.js


Image by author


If your NodeJS version is too low, there may be an Error [ERR_REQUIRE_ESM]: Must use import to load ES Module, please refer to this link https://bobbyhadz.com/blog/error-err-require-esm-must-use-import-to-load-es-module for the solution.


Below is a simple example of how to use figlet in Terminal to beautify the output: figlet.js .


$ npm install figlet -S

$ vi figlet.js

import figlet from 'figlet';
console.log(figlet.text('Hello World', {
  font: 'Shadow',
  horizontalLayout: 'default',
  verticalLayout: 'default'
}, function (err, data) {
  if (err) {
    console.log(err);
  } else {
    console.log(data);
  }
}));


Then we run:


$ node figlet.js

  |   |        |  |            \ \        /             |      |
  |   |   _ \  |  |   _ \       \ \  \   /  _ \    __|  |   _` |
  ___ |   __/  |  |  (   |       \ \  \ /  (   |  |     |  (   |
 _|  _| \___| _| _| \___/         \_/\_/  \___/  _|    _| \__,_|


More can be found at the address https://github.com/patorjk/figlet.js



Enhancing the Scaffold

Above we implemented the foo command its function is very simple, now we use the commander package to extend the function, the specific use can be viewed in the official documentation, below we implement an example.


$ npm install commander -S

$ vi bin/index.js

#! /usr/bin/env node

import { Command } from 'commander';
const program = new Command();
program.version('1.0.0');

program
  .command('hello [name]')
  .description('Greet the user')
  .action((name) => {
    console.log(`Hello ${name || 'world'}!`);
  });

program.parse(process.argv);


In the above example, we extended a hello command to the scaffolding foo, which requires passing the parameter name, the default value of the parameter is world, and finally, the input parameter is obtained through the action callback, and then the project can be created with the name entered by the user.


$ foo

Usage: foo [options] [command]

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

Commands:
  hello [name]    Greet the user
  help [command]  display help for command

$ foo hello
Hello world!

$ foo hello Alice
Hello Alice!

Perfecting the Scaffold

Once we have acquired the foundational knowledge, let us embark on constructing a practical scaffolding.


To begin with, we need to determine the objectives and functionalities that our scaffolding should achieve.


We need to implement a scaffolding tool for creating projects. It should have the following functionalities:


  • Create directory.

  • Select template.

  • Set the project’s information.

  • Create code.


npm install path --save
npm install fs-extra --save

const pwd = process.cwd();
const fpath = path.join(pwd, project_name)

if (fs.existsSync(fpath)) {
  // Inquire whether the user wishes to enforce directory creation?
} else {
  // In the event that the directory does not exist, it shall be created in a conventional manner.
}


Due to the intermediary questioning process, it is necessary to interact with the user through the command line in order to obtain the selected information.


The inquirer package can be utilized for posing questions to the user in the command line and obtaining the subsequent results for further logical interaction.


npm install inquirer --save

async function create (projectName) {
  const pwd = process.cwd();
  const targetDir = path.join(pwd, projectName)

  if (fs.existsSync(targetDir)) {
    // Inquire whether the user wishes to enforce directory creation?
    let { action } = await inquirer.prompt(
      [
        {
          name: 'action',
          type: 'list',
          message: 'The directory already exists. Please make a selection:',
          choices: [
            {
              name: 'Overwrite.',
              value: true
            },
            {
              name: 'Cancel',
              value: false
            }
          ]
        }
      ]
    )

    if (!action) {
      return;
    } else if (action) {
      console.log(`\nRemoving ${chalk.cyan(targetDir)}...`)
      await fs.remove(targetDir)
    }
  }

  // In the event that the directory does not exist, it shall be created in a conventional manner.
  console.log(chalk.blue('[ Creating dir ] > '), targetDir);
  fs.mkdirSync(targetDir)
}



Next, let us proceed to acquire the fundamental project information that the user wishes to configure.


async function template () {
  const answers = await inquirer.prompt(
    [
      {
        name: 'name',
        type: 'string',
        required: true,
        message: 'The project name',
      },
      {
        name: 'description',
        type: 'string',
        required: true,
        message: 'The project description',
      },
      {
        name: 'template',
        type: 'list',
        message: 'Please select a template:',
        choices: [
          {
            name: 'Vue for web',
            value: "vue-web"
          },
          {
            name: 'Vue for mobile',
            value: "vue-mobile"
          }
        ]
      }
    ]
  )
  
  return answers
}



Moving forward, we shall commence the code generation process, wherein we primarily capture the user’s input and write it into a JSON file.


async function code (answers) {
  console.log(chalk.blue('[ Creating json file ] '));
  const jsonData = JSON.stringify(answers);
  fs.writeFile('result.json', jsonData, 'utf8', (err) => {
    if (err) {
      console.error(err);
      return;
    }
    console.log('JSON file has been created!');
  });
}


With that, our scaffolding is complete. Of course, in the final step, you may consider downloading various repository codes based on the user’s template selection to finalize the project initialization. This aspect is left for your contemplation.


Let us proceed to test the functionality of the scaffold.


➜ foo create test
[ Creating project ] >  test
? The directory already exists. Please make a selection: Overwrite.

Removing /xxx/foo/test...
[ Creating dir ] >  /Users/shutiao/code/foo/test
? The project name p_test
? The project description this is a description
? Please select a template: Vue for web
[ Creating json file ] 
JSON file has been created!


The complete code is as follows:


#! /usr/bin/env node
import { Command } from 'commander';
import chalk from 'chalk';
import path from 'path';
import fs from 'fs-extra';
import inquirer from 'inquirer';

const program = new Command();
program.version('1.0.0');

program
  .command('create [project_name]')
  .description('The name of project')
  .action((project_name) => {
    console.log(chalk.blue('[ Creating project ] > '), project_name);
    create(project_name)
  });
  program.parse(process.argv);


async function create (projectName) {
  const pwd = process.cwd();
  const targetDir = path.join(pwd, projectName)

  if (fs.existsSync(targetDir)) {
    // Inquire whether the user wishes to enforce directory creation?
    let { action } = await inquirer.prompt(
      [
        {
          name: 'action',
          type: 'list',
          message: 'The directory already exists. Please make a selection:',
          choices: [
            {
              name: 'Overwrite.',
              value: true
            },
            {
              name: 'Cancel',
              value: false
            }
          ]
        }
      ]
    )

    if (!action) {
      return;
    } else if (action) {
      console.log(`\nRemoving ${chalk.cyan(targetDir)}...`)
      await fs.remove(targetDir)
    }
  }

  // In the event that the directory does not exist, it shall be created in a conventional manner.
  console.log(chalk.blue('[ Creating dir ] > '), targetDir);
  fs.mkdirSync(targetDir)

  let answers = await template()
  await code(answers)
}

async function template () {
  const answers = await inquirer.prompt(
    [
      {
        name: 'name',
        type: 'string',
        required: true,
        message: 'The project name',
      },
      {
        name: 'description',
        type: 'string',
        required: true,
        message: 'The project description',
      },
      {
        name: 'template',
        type: 'list',
        message: 'Please select a template:',
        choices: [
          {
            name: 'Vue for web',
            value: "vue-web"
          },
          {
            name: 'Vue for mobile',
            value: "vue-mobile"
          }
        ]
      }
    ]
  )
  
  return answers
}

async function code (answers) {
  console.log(chalk.blue('[ Creating json file ] '));
  const jsonData = JSON.stringify(answers);
  fs.writeFile('result.json', jsonData, 'utf8', (err) => {
    if (err) {
      console.error(err);
      return;
    }
    console.log('JSON file has been created!');
  });
}