Hand touching teaches you to build a scaffold

Keywords: node.js JSON npm git React

Scaffolding

vue-cli, create-react-app, react-native-cli and so on are excellent scaffolding. Through scaffolding, we can quickly initialize a project without having to configure it step by step from scratch to effectively enhance the development experience. Although these scaffolds are very excellent, but not necessarily in line with our practical application, we can customize a scaffold of our own (or the company's general scaffolding) to improve their development efficiency.

The role of scaffolding

  • Reduce repetitive work, do not need to copy other projects and delete irrelevant code, or create a project and file from scratch.
  • The project structure and configuration files can be dynamically generated based on interaction.
  • Multi-person collaboration is more convenient, and there is no need to transfer files.

Functions realized

Before we start, we need to be clear about what functions our scaffolding needs. vue init template-name project-name, create-react-app project-name. Our scaffolding (eos-cli) has the following capabilities (what is the name of the scaffolding, I chose Eos Dawn Goddess):

  • eos init template-name project-name initializes a project based on the remote template (remote template configurable)
  • EOS config set < key > < value > modify configuration information
  • EOS config get [< key >] view configuration information
  • eos --version to view the current version number
  • eos -h

You can expand other commander s on your own. This article aims to teach you how to implement a scaffold.

Effect Show

Initialize a project

Modify the. eosrc file to download the template from vuejs-template

Third-party libraries to be used

  • babel-cli/babel-env: Grammar Conversion
  • commander: Command line tool
  • download-git-repo: Used to download remote templates
  • ini: Format Conversion
  • inquirer: An interactive command-line tool
  • ora: Display loading animation
  • chalk: Modify the console output content style
  • log-symbols: icons showing or * etc.

The instructions for these third-party libraries can be viewed directly on npm, but they are not expanded here.

Initialization project

Create an empty project (eos-cli) and initialize it with npm init.

Installation dependency

npm install babel-cli babel-env chalk commander download-git-repo ini inquirer log-symbols ora

directory structure

├── bin
│   └── www             //Executable file
├── dist
    ├── ...             //Generate files
└── src
    ├── config.js       //Manage eos configuration files
    ├── index.js        //Mainstream process entry file
    ├── init.js         //init command
    ├── main.js         //Entry file
    └── utils
        ├── constants.js //Const
        ├── get.js       //Getting Templates
        └── rc.js        //configuration file
├── .babelrc             //babel configuration file
├── package.json
├── README.md

babel configuration

The development uses ES6 grammar and babel for escaping.

.bablerc

{
    "presets": [
        [
            "env",
            {
                "targets": {
                    "node": "current"
                }
            }
        ]
    ]
}

eos command

node.js has built-in support for command line operations, and the bin field in package.json can define command names and associated execution files. Add the bin field to package. JSON

package.json

{
    "name": "eos-cli",
    "version": "1.0.0",
    "description": "Scaffolding",
    "main": "index.js",
    "bin": {
        "eos": "./bin/www"
    },
    "scripts": {
        "compile": "babel src -d dist",
        "watch": "npm run compile -- --watch"
    }
}

www file

Add a line #!/ usr/bin/env node at the beginning of the line to specify that the current script is parsed by node.js

#! /usr/bin/env node
require('../dist/main.js');

Link to global environment

In order to facilitate debugging, npm link is executed in the current eos-cli directory and EOS commands are linked to the global environment.

Start the project

npm run watch

Processing command line

Use commander to process the command line.

main

import program from 'commander';
import { VERSION } from './utils/constants';
import apply from './index';
import chalk from 'chalk';

/**
 * eos commands
 *    - config
 *    - init 
 */

let actionMap = {
    init: {
        description: 'generate a new project from a template',
        usages: [
            'eos init templateName projectName'
        ]
    },
    config: {
        alias: 'cfg',
        description: 'config .eosrc',
        usages: [
            'eos config set <k> <v>',
            'eos config get <k>',
            'eos config remove <k>'
        ]
        
    },
    //other commands
}

// Add init / config command
Object.keys(actionMap).forEach((action) => {
    program.command(action)
    .description(actionMap[action].description)
    .alias(actionMap[action].alias) //alias
    .action(() => {
        switch (action) {
            case 'config': 
                //To configure
                apply(action, ...process.argv.slice(3));
                break;
            case 'init':
                apply(action, ...process.argv.slice(3));
                break;
            default:
                break;
        }
    });
});

function help() {
    console.log('\r\nUsage:');
    Object.keys(actionMap).forEach((action) => {
        actionMap[action].usages.forEach(usage => {
            console.log('  - ' + usage);
        });
    });
    console.log('\r');
}
program.usage('<command> [options]');
// eos -h 
program.on('-h', help);
program.on('--help', help);
// Eos-V VERSION is the version number in package.json
program.version(VERSION, '-V --version').parse(process.argv);

// eos without parameters
if (!process.argv.slice(2).length) {
    program.outputHelp(make_green);
}
function make_green(txt) {
    return chalk.green(txt); 
}

Download template

download-git-repo supports downloading remote warehouses from Github and Gitlab to local locations.

get.js

import { getAll } from './rc';
import downloadGit from 'download-git-repo';

export const downloadLocal = async (templateName, projectName) => {
    let config = await getAll();
    let api = `${config.registry}/${templateName}`;
    return new Promise((resolve, reject) => {
        //Project Name is the local directory to download
        downloadGit(api, projectName, (err) => {
            if (err) {
                reject(err);
            }
            resolve();
        });
    });
}

init command

Command-line interaction

After the user executes the init command, he asks the user questions, receives the user's input and makes corresponding processing. Command-line interaction is implemented using inquirer:

inquirer.prompt([
    {
        name: 'description',
        message: 'Please enter the project description: '
    },
    {
        name: 'author',
        message: 'Please enter the author name: '
    }
]).then((answer) => {
    //...
});

Visual Beautification

After user input, start downloading the template, then use ora to remind the user that the template is being downloaded, and give prompts after the download is completed.

import ora from 'ora';
let loading = ora('downloading template ...');
loading.start();
//download
loading.succeed(); //Or load. fail ();

index.js

import { downloadLocal } from './utils/get';
import ora from 'ora';
import inquirer from 'inquirer';
import fs from 'fs';
import chalk from 'chalk';
import symbol from 'log-symbols';

let init = async (templateName, projectName) => {
    //Project does not exist
    if (!fs.existsSync(projectName)) {
        //Command-line interaction
        inquirer.prompt([
            {
                name: 'description',
                message: 'Please enter the project description: '
            },
            {
                name: 'author',
                message: 'Please enter the author name: '
            }
        ]).then(async (answer) => {
            //Download Template Selection Template
            //Get template information through configuration files
            let loading = ora('downloading template ...');
            loading.start();
            downloadLocal(templateName, projectName).then(() => {
                loading.succeed();
                const fileName = `${projectName}/package.json`;
                if(fs.existsSync(fileName)){
                    const data = fs.readFileSync(fileName).toString();
                    let json = JSON.parse(data);
                    json.name = projectName;
                    json.author = answer.author;
                    json.description = answer.description;
                    //Modify package.json file in project folder
                    fs.writeFileSync(fileName, JSON.stringify(json, null, '\t'), 'utf-8');
                    console.log(symbol.success, chalk.green('Project initialization finished!'));
                }
            }, () => {
                loading.fail();
            });
        });
    }else {
        //The project already exists
        console.log(symbol.error, chalk.red('The project already exists'));
    }
}
module.exports = init;

config configuration

eos config set registry vuejs-templates

config configuration allows us to use other warehouse templates, for example, we can use warehouses in vuejs-templates as templates. One advantage is that updating the template does not require re-publishing the scaffolding, users do not need to re-install, and they are free to choose the download target.

config.js

// Manage. eosrc files (under current user directory)
import { get, set, getAll, remove } from './utils/rc';

let config = async (action, key, value) => {
    switch (action) {
        case 'get':
            if (key) {
                let result = await get(key);
                console.log(result);
            } else {
                let obj = await getAll();
                Object.keys(obj).forEach(key => {
                    console.log(`${key}=${obj[key]}`);
                })
            }
            break;
        case 'set':
            set(key, value);
            break;
        case 'remove':
            remove(key);
            break;
        default:
            break;
    }
}

module.exports = config;

rc.js

Addition, deletion and alteration of. eosrc files

import { RC, DEFAULTS } from './constants';
import { decode, encode } from 'ini';
import { promisify } from 'util';
import chalk from 'chalk';
import fs from 'fs';

const exits = promisify(fs.exists);
const readFile = promisify(fs.readFile);
const writeFile = promisify(fs.writeFile);

//RC is a configuration file
//DEFAULTS is the default configuration
export const get = async (key) => {
    const exit = await exits(RC);
    let opts;
    if (exit) {
        opts = await readFile(RC, 'utf8');
        opts = decode(opts);
        return opts[key];
    }
    return '';
}

export const getAll = async () => {
    const exit = await exits(RC);
    let opts;
    if (exit) {
        opts = await readFile(RC, 'utf8');
        opts = decode(opts);
        return opts;
    }
    return {};
}

export const set = async (key, value) => {
    const exit = await exits(RC);
    let opts;
    if (exit) {
        opts = await readFile(RC, 'utf8');
        opts = decode(opts);
        if(!key) {
            console.log(chalk.red(chalk.bold('Error:')), chalk.red('key is required'));
            return;
        }
        if(!value) {
            console.log(chalk.red(chalk.bold('Error:')), chalk.red('value is required'));
            return;
        }
        Object.assign(opts, { [key]: value });
    } else {
        opts = Object.assign(DEFAULTS, { [key]: value });
    }
    await writeFile(RC, encode(opts), 'utf8');
}

export const remove = async (key) => {
    const exit = await exits(RC);
    let opts;
    if (exit) {
        opts = await readFile(RC, 'utf8');
        opts = decode(opts);
        delete opts[key];
        await writeFile(RC, encode(opts), 'utf8');
    }
}

Release

npm publish publishes the scaffold to npm. Other users can install globally through NPM install eos-cli-g.
You can use the eos command.

Project address

Complete code stamp for this project: https://github.com/YvetteLau/...

Writing this article, although it took some time, but in the process, I also learned a lot of knowledge. Thank you for your valuable time to read this article. If this article gives you some help or inspiration, please don't stint your praise and Star, you must be the greatest driving force for my progress.
https://github.com/YvetteLau/...

Reference article:

[1] Everest Architecture Course (Wall Crack Recommendation)

[2] npm depends on documents( https://www.npmjs.com/package...

Pay attention to the public number and join the technology exchange group.

Posted by alemapo on Wed, 24 Jul 2019 19:02:51 -0700