[Advanced Front End Required] Hand-to-hand Teaching to Pull a Scaffold

scaffold

vue-cli,create-react-appreact-native-cliThese are excellent scaffolds. Through scaffolds, 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 actual application. We can customize a scaffold (or company-wide scaffold) to improve our development efficiency.

More quality articles to stamp on: https://github.com/YvetteLau/ …

Function of Scaffolding

  • To reduce repetitive work, there is no need to copy other projects and delete irrelevant codes, or to create a project and file from scratch.
  • Project structures and configuration files can be dynamically generated based on interactions.
  • It is more convenient for many people to cooperate, and there is no need to pass the files around.

Functions Implemented

Before we begin, we need to know what functions our scaffolding needs.vue init template-name project-namecreate-react-app project-name. The scaffold (eos-cli) we have written this time has the following capabilities (I chose Eos Dawn Goddess as the scaffold’s name is whatever I like to call it):

  • eos init template-name project-nameAccording to the remote template, initialize a project (the remote template can be configured)
  • eos config set <key> <value>Modify configuration information
  • eos config get [<key>]View configuration information
  • eos --versionView the current version number
  • eos -h

You can expand others on your owncommanderThis article aims to teach everyone how to realize a scaffold.

Effect display

Initialize an item

clipboard.png

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

clipboard.png

Third party libraries to be used

  • Babel-cli/babel-env: syntax conversion
  • Commander: command line tool
  • Download-git-repo: used to download remote templates
  • Ini: format conversion
  • Inquirer: Interactive Command Line Tool
  • Ora: Show loading Animation
  • Chalk: Modify console output content style
  • Log-symbols: icons showing √ or × etc.

The descriptions of these third-party libraries can be viewed directly on npm, and will not be expanded here.

Initialization item

Create an empty item (eos-cli) usingnpm initInitialize.

Installation dependency

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

directory structure

├── bin
│   └── www             //可执行文件
├── dist
    ├── ...             //生成文件
└── src
    ├── config.js       //管理eos配置文件
    ├── index.js        //主流程入口文件
    ├── init.js         //init command
    ├── main.js         //入口文件
    └── utils
        ├── constants.js //定义常量
        ├── get.js       //获取模板
        └── rc.js        //配置文件
├── .babelrc             //babel配置文件
├── package.json
├── README.md

Babel configuration

The development uses ES6 syntax, usingbabelEscape,

.bablerc

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

eosCommand

Node.js has built-in support for command line operations.package.jsonhit the targetbinThe field can define the command name and the associated execution file. Inpackage.jsonAdd tobinfield

package.json

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

Www file

Add a line at the beginning of the line#! /usr/bin/env nodeSpecifies 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 in the development process, in the currenteos-cliExecute under directorynpm link, willeosThe command links to the global environment.

Start project

npm run watch

Handle command line

utilizecommanderTo 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
}

// 添加 init / config 命令
Object.keys(actionMap).forEach((action) => {
    program.command(action)
    .description(actionMap[action].description)
    .alias(actionMap[action].alias) //别名
    .action(() => {
        switch (action) {
            case 'config': 
                //配置
                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 为 package.json 中的版本号
program.version(VERSION, '-V --version').parse(process.argv);

// eos 不带参数时
if (!process.argv.slice(2).length) {
    program.outputHelp(make_green);
}
function make_green(txt) {
    return chalk.green(txt); 
}

Download template

download-git-repoSupport to download remote warehouse from Github and Gitlab to local.

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 为下载到的本地目录
        downloadGit(api, projectName, (err) => {
            if (err) {
                reject(err);
            }
            resolve();
        });
    });
}

initCommand

Command line interaction

After the user executes the init command, ask the user questions, receive the user’s input and make corresponding processing. Command line interactive utilizationinquirerTo achieve:

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

Visual beautification

After the user inputs, download the template and use it at this time.oraTo prompt the user that the template is being downloaded, and to give a prompt after the download is finished.

import ora from 'ora';
let loading = ora('downloading template ...');
loading.start();
//download
loading.succeed(); //或 loading.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) => {
    //项目不存在
    if (!fs.existsSync(projectName)) {
        //命令行交互
        inquirer.prompt([
            {
                name: 'description',
                message: 'Please enter the project description: '
            },
            {
                name: 'author',
                message: 'Please enter the author name: '
            }
        ]).then(async (answer) => {
            //下载模板 选择模板
            //通过配置文件,获取模板信息
            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;
                    //修改项目文件夹中 package.json 文件
                    fs.writeFileSync(fileName, JSON.stringify(json, null, '\t'), 'utf-8');
                    console.log(symbol.success, chalk.green('Project initialization finished!'));
                }
            }, () => {
                loading.fail();
            });
        });
    }else {
        //项目已经存在
        console.log(symbol.error, chalk.red('The project already exists'));
    }
}
module.exports = init;

configConfiguration

eos config set registry vuejs-templates

Config configuration supports us to use templates from other warehouses. for example, we can use warehouses in vuejs-templates as templates. This has the advantage that updating the template does not require re-publishing the scaffold, users do not need to re-install it, and users are free to choose the download target.

config.js

// 管理 .eosrc 文件 (当前用户目录下)
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

The addition, deletion and modification of the. eosrc file.

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 是配置文件
//DEFAULTS 是默认的配置
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 publishPublish this scaffold to npm. Other users can usenpm install eos-cli -gGlobal installation.
Can be usedeosOrders.

Project address

Please stamp the complete code of this project:https://github.com/YvetteLau/ …

Although it took me some time to write this article, I also learned a lot in the process. Thank you for your friends’ willingness to spend precious time reading this article. If this article gives you some help or inspiration, please don’t be stingy with your praise and Star, your affirmation is the biggest motivation for me to move forward.
https://github.com/YvetteLau/ …

Reference articles:

[1] npm dependent documents (https://www.npmjs.com/package …

Thank you for pointing out:

Add a reference article: [simple front-end scaffold ICE] (https://link.juejin.im/? targe …

I recommend paying attention to my public number.

clipboard.png