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.