The scaffold

Vue-cli, CREate-React-app, And React-native CLI are all excellent scaffolding, through which we can quickly initialize a project without having to configure it step by step from scratch, effectively improving the development experience. Although these scaffolds are excellent, they are not necessarily suitable for our practical application. We can customize our own scaffold (or corporate scaffold) to improve our development efficiency.

Role of scaffolding

  • Reduce repetitive work, no need to copy other projects and delete irrelevant code, or create a project and files from zero.
  • The project structure and configuration file can be dynamically generated based on the interaction.
  • It’s easier to work with multiple people, and you don’t need to pass files around.

The complete code of this project please stamp: github.com/YvetteLau/B…

Implemented functions

Before we start, we need to figure out what functionality our scaffolding needs. Vue init template-name project-name, create-react-app project-name. The scaffolding we wrote this time (EOS-CLI) has the following capabilities (the scaffolding name is whatever you like, I chose EOS Goddess of Dawn):

  • eos init template-name project-nameInitializing a project based on a remote template (remote templates are configurable)
  • eos config set <key> <value>Modifying configuration Information
  • eos config get [<key>]Viewing configuration Information
  • eos --versionView the current version number
  • eos -h

You can expand on the other commanders, but this article will show you how to implement a scaffold.

Please stamp the complete code of this project (Clone code is recommended first) : github.com/YvetteLau/B…

Results show

Initializes a project

Modify the. Eosrc file and download the template from vuejs-template

Third-party libraries that need to be used

  • @babel/cli @babel/core @babel/preset-env: preset syntax conversion
  • Commander: command line tool
  • Download-git-repo: Used to download remote templates
  • Ini: format conversion
  • Inquirer: An interactive command-line tool
  • Ora: Displays loading animation
  • Chalk: Changes the console output content style
  • Log-symbols: Displays symbols such as √ or ×

For the description of these third-party libraries, you can directly view the corresponding description on NPM, which will not be expanded here.

Initialize the project

Create an empty project (Eos-CLI) and initialize it with NPM init.

Install dependencies

npm install @babel/cli @babel/core @babel/preset-env chalk commander download-git-repo ini inquirer log-symbols ora -D
Copy the code

The directory structure

├── bin │ ├─ WWW// Executable file├ ─ ─ dist ├ ─ ─...// Generate the file└ ─ ─ the SRC ├ ─ ─ config. Js// Manage the EOS profile├ ─ ─ index. Js// Main flow entry file├ ─ ─ init. Js//init command├ ─ ─ the main js// Import file└ ─ ─ utils ├ ─ ─ the js// Define constants├ ─ ─ the get. Js// Get the template└ ─ ─ rc. Js// Configuration file├ ─ ─ babelrc// Babel configuration file├ ─ ─ package. Json ├ ─ ─ the README, mdCopy the code

Babel configuration

Developed using ES6 syntax, using Babel to escape,

.bablerc

{
    "presets": [["@babel/env",
            {
                "targets": {
                    "node": "current"}}]]}Copy the code

eosThe command

Node.js has built-in support for command-line operations, and the bin field in package.json defines the command name and associated execution file. 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"}}Copy the code

WWW file

Add a line #! /usr/bin/env node specifies that the current script is parsed by Node.js

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

Link to the global environment

To facilitate debugging during development, NPM link was executed in the current EOS-CLI directory to link eos commands to the global environment.

Start the project

npm run watch
Copy the code

Processing command line

Use Commander to handle 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
Object.keys(actionMap).forEach((action) = > {
    program.command(action)
    .description(actionMap[action].description)
    .alias(actionMap[action].alias) / / alias
    .action(() = > {
        switch (action) {
            case 'config': 
                / / configurationapply(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(The '-' + 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 with no parameters
if(! process.argv.slice(2).length) {
    program.outputHelp(make_green);
}
function make_green(txt) {
    return chalk.green(txt); 
}
Copy the code

Download the template

Download-git-repo supports downloading remote repositories from Github and Gitlab.

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) = > {
        //projectName is the local directory to download to
        downloadGit(api, projectName, (err) = > {
            if (err) {
                reject(err);
            }
            resolve();
        });
    });
}
Copy the code

initThe command

Command line interaction

After the user executes the init command, it asks the user a question, receives the user’s input, and processes accordingly. Command line interaction is achieved using the Inquirer:

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

Visual beautification

After user input, the template is downloaded, using ORA to prompt the user that the template is being downloaded, and to prompt when the download is complete.

import ora from 'ora';
let loading = ora('downloading template ... ');
loading.start();
//download
loading.succeed(); / / or loading fail ();
Copy the code

init.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) => {
    // The 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 the template
            // Obtain template information from the configuration file
            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 the package.json file in the 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;
Copy the code

configconfiguration

eos config set registry vuejs-templates
Copy the code

Config allows us to use templates from other repositories. For example, we can use the repository in vuejs-templates as a template. This has the advantage that updating the template does not require republishing the scaffolding, users do not need to reinstall it, and users are free to choose where to download.

config.js

// Manage the.eosrc file (in the 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;
Copy the code

rc.js

. Eosrc file additions and deletions

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 the configuration file
//DEFAULTS are the default Settings
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'); }}Copy the code

release

NPM Publish Publishes the scaffold to NPM. Other users can use NPM install eos-cli -g for global installation. The EOS command can be used.

The project address

The complete code of this project please stamp: github.com/YvetteLau/B…

Although it took me some time to write this article, I also learned a lot in the process. Thank you for spending your precious time to read this article. If this article gives you some help or inspiration, please don’t hesitate to give me your likes and stars. Github.com/YvetteLau/B…

Reference article:

[1] NPM on documentation (www.npmjs.com/package/dow.)

Thanks for pointing out:

[Link. Juejin. Im /? Target = HTTP…).

Follow the public account and join the technical exchange group