Key features include basic components, generic business components, network request library, Proxy cross-domain Proxy, Auth permissions, Router Routing Unit, E2E testing, ESLint code specification, Mock Data native Mock backend data, chart library, WebPack packaging tool

Many partners have been entangled in what is scaffolding? In fact, the core function is to create the initial project file, so the problem is again, there is not enough scaffolding on the market? Why write it yourself? Vue-cli, create-react-app, DVA-CLI… Their characteristic needless to say is single-mindedness! But in the company you will find the following series of problems!

  • Multiple business types
  • Repeated wheel building, project upgrading and other problems
  • Company code specification, can not be unified

Most of the time we need to create a new project when developing, copy the existing project code again, retain the basic capabilities. (But the process is trivial and time-consuming). Then we can customize the template and build our own scaffold. To solve these problems.


1. Necessary modules

Let’s start with vue-CLI, as it’s known, and see which NPM packages he uses to implement it

  • Commander: Parameter parsing –help is used to help
  • Inquirer: Interactive command line tool that enables command line selection
  • Download-git-repo: Download the template in Git
  • 【 典 型 范 例 2 】 The chalk helped us draw various colors on the console
  • Metalsmith: Read all files, implement template rendering
  • Consolidate: indicates a unified template engine

2. Installation depends on NPM

NPM link // Zhu -cli command on the command line, And execute the main.js file NPM I commander // install the Commander package to automatically generate the help parsing option parameter NPM I axios // Ajax data NPM I ORA Inquirer //loading style selection template NPM I download-git-repo // Select the project template name and the corresponding version, NPM I NCP download NPM I NCP // Copy files NPM I metalsmith EJS consolidate // go through folders Borrow EJS templates use multiple template engines or yarn Add commander YARN add axios yarn add ora inquirer yarn add download-git-repo yarn add ncp yarn metalsmith ejs consolidate yarn add commander axios ora inquirer ncp metalsmith ejs consolidateCopy the code
  • Create the bin folder and WWW file bin/ WWW
  • Create the SRC folder and the main.js file SRC /main.js
  • Initialize NPM init-y
  • npm install babel-cli babel-env -D
{/ /. Babelrc file "presets" : [[" env "{" targets" : {" node ":" current "}}]]}Copy the code
  • SRC/main. Js file
console.log('hello! ');Copy the code
  • Modify the package.json file

Call WWW file in bin directory when zhu-cli is executed under command

"Scripts ": {"compile":" Babel src-d dist",// can only generate "watch" once: "NPM run the compile -- - watch", / / continues to generate}, "bin" : {/ / bin fields, set up the scaffolding entry "zf - cli" : ". / bin/WWW "},Copy the code
  • NPM run watch NPM run watch NPM run watch
  • Bin/WWW file
#! /usr/bin/env node // uses main as the entry file, and executes the file require('.. /dist/main.js')Copy the code
  • NPM link links packages to global use and can be used from the command linezf-cliCommand, and execute the main.js file.

3. Create a folder

├ ─ ─ bin │ └ ─ ─ zf # global command execution root file ├ ─ ─ the SRC # source │ ├ ─ ─ heiper │ ├ ─ ─ utils # store tools method │ ├ ─ ─ config. Js │ ├ ─ ─ init. Js │ ├ ─ ─ └.js │ ├─ list.js │ ├─ index.js # import file │ ├─ ├.js │ ├─.├.js # import file │ ├─.├.js # import file │ ├─.├.js # import file │ ├─.├.js # import file │ ├─.├.js # import file │ ├─.├.js # import file │.. ├─ ├─ ├─ package.json # package.jsonCopy the code

Parse command line arguments

Commander :The complete solution for node.js command-line interfaces Vue-cli –help! Vue-cli create

4.1. Use Commander

npm install commander
Copy the code

1 Main.js is our entry file

const program = require('commander'); The program version (' 0.0.1). The parse (process. Argv); // process.argv is the argument passed by the user on the command lineCopy the code

Zf -cli –help This version number should be the version number of the current CLI project, which we need to retrieve dynamically and put all constants in the Constants folder under util for convenience

const { name, version } = require('.. /.. /package.json'); module.exports = { name, version, };Copy the code

So we can get the version number dynamically

const program = require('commander');
const { version } = require('./utils/constants');
program.version(version)
  .parse(process.argv);
Copy the code

4.2 Configuration Commands

Enter zf-cli –help in the command line to print the help information to download the package as shown in the code

npm install commander
Copy the code

Create a new SRC /utils/constants.js file

import { version } from ".. /.. /package.json"; // The current package.json VERSION number export const VERSION = VERSION;Copy the code

SRC/main. Js file

import program from "commander"; import { VERSION } from "./utils/constants"; // Let actionMap={install:{alias:' I ',// Zf -cli I ', 'zf-cli install']}, config:{alias:'c', description:' config. zfclirc', examples:[ 'zf-cli config set<k> <v>',
            'zf-cli config get <k>',
            'zf-cli config remove <k>' ] }, '*':{ description:'not found', examples:[] } } Object.keys(actionMap).forEach(action=>{ program.command(action) .description(actionMap[action].description) .alias(actionMap[action].alias) .action(()=>{ console.log(action) }) }) Function help(){// console.log('123')// display example console.log('\r\n '+'how to use command'); Object.keys(actionMap).forEach(action=>{ actionMap[action].examples.forEach(example=>{ console.log(' - '+example) }) }) } program.on('-h',help); program.on('--help',help); program.version(VERSION, '-v --version').parse(process.argv);Copy the code

Create a new SRC /index.js file

let apply = () => {}
export default apply;
Copy the code

SRC/main. Js file

. Object.keys(actionMap).forEach(action=>{ program.command(action) .description(actionMap[action].description) Alias (actionMap[action].alias).action(()=>{//console.log(action) =>{if(action === 'config'){// Implement configuration file change / / the console. The log (process. Argv) / / the main array (the action and process. The argv. Slice (3)); }else if(action === 'install'){ } main() }) }) }) ...Copy the code

SRC/index. Js file

Import {betterRequire} from './utils/common'; Import {resolve} from 'path' let apply = (action,... args) => { // console.log(action, args) //babel-env export default=>module.exports={default:xxx} betterRequire(resolve(__dirname, `./${action}`))(... } export default apply;Copy the code

Create a new SRC /utils/common.js file

Export const betterRequire = (absPath) => {// Let module = require(absPath); if(module.default){ return module.default; } return module; }Copy the code

4.3 write help command

Listen to the help command to print help information

program.on('--help', () => {
  console.log('Examples');
  Object.keys(actionsMap).forEach((action) => {
    (actionsMap[action].examples || []).forEach((example) => {
      console.log(`  ${example}`);
    });
  });
});
Copy the code

4.4. Create command

The create command is used to pull a template from the Git repository and download the corresponding version to the local directory. If there is a template, the template will be rendered according to the information filled in by the user and generated in the directory where the command is run

Action (() => {// Action if (action === '*') {// If no action is matched, input error console.log(acitonMap[action].description); Resolve (__dirname, action))(// require(path.resolve(__dirname, action))(... process.argv.slice(3)); }}Copy the code

According to different actions, dynamically import files of corresponding modules and run zf -CLI create project to print project

5. Config command

The main function of creating config.js is to read and write the configuration file. Of course, if the configuration file does not exist and you need to provide default values, write the constants.js configuration first

Const home = process.env[process.platform === "win32"? "USERPROFILE" : "home "]; . / / the console log (process platform) / / win32 node running environment according to the kernel of the operating system / / process related information. The env. The USERPROFILE / / the current directory configuration file export const VERSION = version; export const RC = `${HOME}/.zfrc`; Export const DOWNLOAD = '${HOME}/.zf'; // export const DEFAULTS = {registry: "chef-template", type: "orgs", // ['orgs', 'users'] };Copy the code

Write config. Js

const fs = require('fs'); const { defaultConfig, configFile } = require('./util/constants'); Module. exports = (action, k, v) => {if (action === 'get') {console.log(' get'); } else if (action = = = 'set') {the console. The log (' Settings'); } / /... };Copy the code

Rc configuration files are in ini format.

repo=zhu-cli
register=github
Copy the code

Download the INI module parsing configuration file

npm i ini
Copy the code

The code here is very simple, nothing more than file operation

const fs = require('fs'); const { encode, decode } = require('ini'); const { defaultConfig, configFile } = require('./util/constants'); const fs = require('fs'); const { encode, decode } = require('ini'); const { defaultConfig, configFile } = require('./util/constants'); module.exports = (action, k, v) => { const flag = fs.existsSync(configFile); const obj = {}; If (flag) {const content = fs.readFileSync(configFile, 'utf8'); const c = decode(content); // Parse the file into object.assign (obj, c); } if (action === 'get') { console.log(obj[k] || defaultConfig[k]); } else if (action === 'set') { obj[k] = v; fs.writeFileSync(configFile, encode(obj)); Console. log(' ${k}=${v} '); } else if (action === 'getVal') { return obj[k]; }};Copy the code

The getVal method is used to retrieve configuration variables when the create command is executed

const config = require('./config');
const repoUrl = config('getVal', 'repo');
Copy the code

Now we can replace all the zb-cli values in the create method with the values obtained.

6. Write the install command

Download package

npm install ini 
npm install ora inquirer
npm install download-git-repo
Copy the code

Create the SRC /config.js file

Zf -cli config set key value import {get,set,remove,getAll} from './utils/rc.js'; zf-cli config set key value import {get,set,remove,getAll} from '. let config =async (action, k, v) => { // console.log(action, k, v) switch (action) { case 'get': if (k) { let key=await get(k); //console.log(key) } else { let obj=await getAll(); Object.keys(obj).forEach(key=>{ console.log(`${key}=${obj[key]}`); }) } break; case 'set': set(k, v); break; case 'remove': remove(k); break; } } export default config;Copy the code

SRC/utils/the js file

// Find the user's root directory const HOME = process.env[process.platform === 'win32'? "USERPROFILE" : "HOME"]; . / / the console log (process platform) / / win32 node running environment according to the kernel of the operating system / / process related information. The env. The USERPROFILE / / the current directory configuration file export RC = const  `${HOME}/.zfclirc`; // export const DEFAULTS = {registry: 'zhufeng-cli', type: 'orgs'}Copy the code

Create a new SRC /utils/rc.js file

Import {RC, DEFAULTS} from './constants. Js' //RC is configuration file DEFAULT is DEFAULT //promisify: Promisify import {decode,encode} from 'ini'// format analysis and serialization import {promisify} from 'util'; import fs from 'fs'; import { exit } from 'process'; let exists = promisify(fs.exists); // Let readFile = promisify(fs.readfile); let writeFile = promisify(fs.writeFile); export let get = async (k) => { //console.log(k) let has = await exists(RC); let opts; if (has) { opts = await readFile(RC, 'utf8'); opts = decode(opts); return opts[k]; } return ''; } export let set = async (k, v) => { let has = await exists(RC); let opts; if (has) { opts = await readFile(RC, 'utf8'); opts = decode(opts); Object.assign(opts, { [k]: v }); }else{ opts=Object.assign(DEFAULTS,{[k]:v}) } await writeFile(RC,encode(opts),'utf8'); } export let remove = async (k) => { let has=await exists(RC); let opts; if(has){ opts=await readFile(RC,'utf8'); opts=decode(opts); delete opts[k]; await writeFile(RC,encode(opts),'utf8') } } export let getAll = async () => { let has=await exists(RC); let opts; if(has){ opts=await readFile(RC,'utf8'); opts=decode(opts); return opts; } return {} }Copy the code

Create a new SRC /install.js file

Import {repoList, tagList, downloadLocal,} from './utils/git'; import ora from 'ora'; // progress bar import inquirer from 'inquirer'; Let install = async () => {let loading = ora('fetching template...... ' '); loading.start() let list = await repoList(); loading.succeed(); list = list.map(({name}) => name); //console.log(list); let answer = await inquirer.prompt([{ type: 'list', name: 'project', choices: list, questions: 'pleace choice template' }]); // console.log(answer.project); // Get the current project let project = answer.project; // Get the version number of the current project loading = origin('fetching tag...... '); loading.start(); list = await tagList(project); loading.succeed(); list = list.map(({name}) => name); let tag=answer.tag; //zf-cli init // download... loading=ora('download project ...... '); loading.start(); //console.log(project,tag); await downloadLocal(project,tag); loading.succeed(); } export default install; Export const DOWNLOAD= '${HOME}/. Template'; SRC /utils/git.js import request from 'request'; import { getAll } from './rc' import downLoadGit from 'download-git-repo'; import {DOWNLOAD} from './constants' let fetch = async () => { return new Promise((resolve, reject) => { let config = { url, method: 'get', headers: { 'user-agent': 'xxx' } } request(config, (err, response, body) => { if (err) { reject(err); Resolve (json.parse (body))})}) The https://api.github.com/repos/zhufeng-cli/vue-template/tags version export let tagList = async (repo) = > {let config = await getAll(); let api = `https://api.github.com/repos/${config.registry}/${repo}/tags`; Return await fetch(API)} The https://api.github.com/orgs/zhufeng-cli/repos project export let repoList = async () = > {let config = await getAll (); let api = `https://api.github.com/${config.type}/${config.registry}/repos`; return await fetch(api); } export let download = async (src, dest) => { return new Promise((resolve, reject) => { downLoadGit(src, dest, (err) => { if (err) { reject(err); } resolve(); Export let downloadLocal = async (project, version) => { let config=await getAll() let api =`${config.registry}/${project}`; if(version){ api += `#${version}`; } return await download(api,DOWNLOAD+'/'+project); }Copy the code

The command line selection we see is basically based on Inquirer, which allows for different queries

New SRC/utils. Js git. Js

npm i download-git-repo
Copy the code
const { promisify } = require('util');
const downLoadGit = require('download-git-repo');
downLoadGit = promisify(downLoadGit);
Copy the code

Node already provides you with a way to quickly convert asynchronous apis into promise forms. Find a temporary directory to store the downloaded files before downloading and continue to configure constants

const downloadDirectory = `${process.env[process.platform === 'darwin' ? 'HOME' : 'USERPROFILE']}/.template`;
Copy the code

Here we download the file to the. Template file of the current user. Because different directories on the system get the file differently,process.platform gets win32 on Windows Darwin, in accordance with the corresponding environment variables to obtain the user directory

const download = async (repo, tag) => { let api = `zf-cli/${repo}`; If (tag) {API += '#${tag}'; } const dest = `${downloadDirectory}/${repo}`; // Download the template to the appropriate directory await downLoadGit(API, dest); return dest; // return to download directory}; Const target = await wrapFetchAddLoding(download, 'download Template ')(repo, tag);Copy the code

For simple projects, you can copy the downloaded project directly to the current command directory.

SRC /utils.js common.js install NCP to copy files

npm i ncp
Copy the code

Like this:

let ncp = require('ncp'); ncp = promisify(ncp); // Copy the downloaded file to await NCP (target, path.join(path.resolve(), projectName)) in the directory where the command is currently executed;Copy the code

7. NPM release

7.1. First delivery

** You need to create an NPM account before publishing the package: NPM login Enter the account you created, password and email address –> login

7.2 This is not the first release

** NPM adduser prompts you to enter your account, password and email address, and then prompts you to create a successful NPM adduser

7.3. Release process

  • Login to NPM login in the project directory
  • The name and version of the package published by NPM are the same as the name and version in the package.json in the project
  • You can find the published App in NPM search

7.4 points to pay attention to

  • The name cannot be the same as that of an existing package. You can use the NPM search engine to check whether there is an existing package with the same name before sending the package
  • NPM restrictions on package names: cannot contain uppercase letters/Spaces/underscores
  • If there are private parts of your project that you don’t want to publish to NPM you can say.gitignore or.nPMignore and upload will be ignored.

Eight,The source code