Scaffolding is to ensure the smooth construction process and set up the work platform. According to the location of the installation is divided into external scaffolding, scaffolding; According to different materials can be divided into wood scaffolding, bamboo scaffolding, steel pipe scaffolding; According to the structural form is divided into pole type scaffold, bridge type scaffold, portal type scaffold, suspension type scaffold, hanging type scaffold, carry type scaffold, climbing type scaffold. — Baidu Encyclopedia
In essence, it is a convenient tool for some special or tedious work to provide assistance. What we need to develop here is a command-line tool, which is replaced by cli in the following article.
Why develop a scaffold?
The following are the problems encountered in the collaborative development of the initial component library:
- The component directory structure is inconsistent
- Flatten the directory
src
Type a directory
- Component output names are inconsistent
- The prefix
D
named - unprefixed
D
named - Lowercase hump named
- Name the capital hump
xxxService
nameduseXxxService
named
- The prefix
- Component entry files often collide
To address these issues, and in the spirit of making a contribution to the open source community, it’s time to make a difference, and after communicating with project organizer Kagol, the scaffolding solution has been successfully implemented by Prefect!
TODO
- Create a unified component structure
- Create the component library entry file
Technology selection
Scaffolding = command + interaction + logical processing
- The command
commander
The plug-in provides command registration, parameter resolution, and callback execution
- interaction
inquirer
Plug-ins for command line interaction (Q&A)
- Logical processing
fs-extra
Plug-in is tonodejs
fileApi
Further encapsulation for easy usekolorist
Plugins are used to output color information for friendly prompts
Initialize the cli
Step1 create a cli directory
Mkdir devui-cli // Create scaffold directory CD devui-cli // go to scaffold directory // initialize a Node project NPM init // or YARN initCopy the code
The first step is to create a directory to store the scaffolding we are going to develop as a NodeJS package. If you need information about the NPM or YARN initialization package, press Enter. The generated directory structure is shown in the following figure.
Step2 create an entry file
mkdir src
echo 'console.log("hello devui-cli")' > src/index.js
Copy the code
Step3 install required dependencies
npm i -D commander inquirer fs-extra kolorist
// or
yarn add -D commander inquirer fs-extra kolorist
Copy the code
Developing command scripts
Here is the cli execution process: Enter devui-cli -> command line interaction -> Perform different operations based on different parameters.
Here you might ask, how does the command line recognize devui-CLI? How is the interaction performed?
SRC /index.js SRC /index.js SRC /index.js SRC /index.js SRC /index.js SRC /index.js SRC /index.js But the first is more convenient and friendly. So can we just do it? The answer is definitely not, you need to configure the bin property in package.json to indicate an entry to the script.
After preparations, the CLI script is written.
Configure the environment interpreter
#! /usr/bin/env node
Copy the code
Some observers may wonder what is the use of this sentence?
Shebang is the name of a symbol #! . This symbol is usually found at the beginning of the first line in Unix systems to indicate the interpreter for this script file, #! The purpose of /usr/bin/env node is to tell the operating system to find the Node interpreter in the /usr/bin ring configuration and execute it when executing this script.
Register the command
With the environment interpreter configured, we can write our command logic.
First, let’s register some of the commands we need to execute and some of the command parameters.
#! /usr/bin/env node
import { Command } from 'commander'
import { onCreate } from './commands/create'
// Create a command object
const program = new Command()
// Register commands, parameters, callbacks
program
// Register the create command
.command('create')
// Add the command description
.description('Create a component template or configuration file')
/ / add the command parameters - t | - type < type >, < type > says the parameters required, optional [type]
.option('-t --type <type>'.'Create type, optional value: Component, lib-entry')
// Register the command callback
.action(onCreate)
// Execute command line argument parsing
program.parse()
Copy the code
This section describes how to create a command directory for unified management.
Echo 'export function onCreate() {}' > SRC /commands/create.js create the create command file and export the callback functionCopy the code
Test script commands
We can start by printing the parameters that we receive in onCreate.
export function onCreate (cmd) {
console.log(cmd)
}
Copy the code
Execute the script.
node src/index.js
Copy the code
Error reported!! Are we falling apart when we get it wrong at the start?
Calm down. We’re not expecting anything. This is because we are writing a Node program and should have used commonJS for CJS, which is used in require and exports to launch node xxx.js properly. But we are using the new esModule format for short, so Node face blind! So what can be done?
Solution 1: Change.js to.mjs. why? It is obvious that ESM and CJS are loaded differently, so in order to better distinguish between the two loading methods, we created a.mjs file type aimed at Module javascript. .mjs means that the current file is loaded in ESM mode. If it is a common.js file, it is loaded in CJS mode.
Solution two: Use some module packers to convert to the CJS format that Node is familiar with, and then develop.
The second option is chosen because with a packer you can do other things to your code, such as compress, transform, and so on.
The reason for using esbuild as a module packer is that it is quick and convenient.
npm i -D esbuild
// or
yarn add -D esbuild
Copy the code
After installation, look at the command line help documentation.
npx esbuild -h
// or
yarn esbuild -h
Copy the code
The following help information is displayed after the execution.
After reading the help information, we added the following command:
{
// --bundle identifies the packaged entry file
// --format converts to the target format code
// --platform Target platform, default browser
// --outdir output directory
// Compile in real time
"dev": "esbuild --bundle ./src/index.js --format=cjs --platform=node --outdir=./lib --watch".// Package command
"build": "esbuild --bundle ./src/index.js --format=cjs --platform=node --outdir=./lib".// Run the create command. If there are multiple commands, delete the create command and pass it in
"cli": "node ./lib/index.js create"
}
Copy the code
Run the dev command, open a new shell, and then run the CLI command.
yarn cli
// or
npm run cli
Copy the code
This is the CMD entry we printed. We didn’t fill in any arguments, so it’s an empty object after parsing. We pass in the type argument and see.
Yarn cli -t component // -t is the alias of --type // or NPM run cli -- -t component // -- Is required for parameter transmission in the NPM run script, similar to transparent parameter transmission in the scriptCopy the code
Now the command parameters can be obtained normally, which proves that the command is successfully registered, and we can continue to implement our interactive logic later.
Perfecting the create command
It’s time to further refine our command interaction, using component as an example:
import inquirer from 'inquirer'
import { red } from 'kolorist'
// Create type Support item
const CREATE_TYPES = ['component'.'lib-entry']
// Document classification
const DOCS_CATEGORIES = ['general'.'navigation'.'feedback'.'Data entry'.'Data Presentation'.'layout']
export async function onCreate(cmd = {}) {
let { type } = cmd
// If type is not entered in the command argument, then ask once
if(! type) {const result = await inquirer.prompt([
{
// The name of the property used after the fetch
name: 'type'.// The interaction mode is a list option
type: 'list'.// Prompt message
message: '(Required) Please select create type:'.// List of options
choices: CREATE_TYPES,
// The default value is the index subscript
default: 0}])/ / assignment type
type = result.type
}
// If the type is not supported, print an error message and reselect
if (CREATE_TYPES.every((t) = >type ! == t)) {console.log(
red('The current type only supports:${CREATE_TYPES.join(', ')}, received not supported"${type}", please choose again! `))return onCreate()
}
try {
switch (type) {
case 'component':
// If it is a component, we also need to collect some information
const info = await inquirer.prompt([
{
name: 'name'.type: 'input'.message: '(Required) Please enter component name, to be used as directory and filename:'.validate: (value) = > {
if (value.trim() === ' ') {
return 'Component name is mandatory! '
}
return true}}, {name: 'title'.type: 'input'.message: '(Required) Please enter the Chinese name of the component, which will be used for document list display:'.validate: (value) = > {
if (value.trim() === ' ') {
return 'Component name is mandatory! '
}
return true}}, {name: 'category'.type: 'list'.message: '(Required) Please select component category to be used as document list category:'.choices: DOCS_CATEGORIES,
default: 0
}
])
createComponent(info)
break
case 'lib-entry':
createLibEntry()
break
default:
break}}catch (e) {
console.log(red('✖) + e.toString())
process.exit(1)}}function createComponent(info) {
// Displays the collected component information
console.log(info)
}
function createLibEntry() {
console.log('create lib-entry file.')}Copy the code
Ok, let’s try running our script.
Try the error type first:
yarn cli -t error
// or
npm run cli -- -t error
Copy the code
Error message as we expected and asked us to re-select the type.
Next try the correct type:
yarn cli -t component
// or
npm run cli -- -t component
Copy the code
Because the type is specified as component, you now need to gather information about the component to be created.
Complete the input step by step according to the prompt information and finally obtain the data we need. The next step is to generate the template.