Author: Iel

Original link: juejin.cn/post/702187…

Recently, I live with the village head teacher to share the construction of vue Devui open source component library with everyone. Phase 1-3, with tree component as the backbone 🌰, introduced how to design and develop Vue components:

  1. Vue DevUI Open Source Guide 01: Submit my first PR
  2. Vue DevUI Open Source Guide 02: Implement a Tree component that can render multiple nodes
  3. Vue DevUI Open Source Guide 03: How to add expansion/collapse to tree components

Starting with Issue 4, I will share with you some content related to component library engineering:

  1. Vue DevUI Open Source Guide 04: Build a Vue3 component library for TypeScript/JSX using Vite
  2. Vue DevUI Open Source Guide 05: Add VitePress documentation system to Vue3 component library

Subsequent live broadcasts will also split into two lines:

  1. One is the design and implementation of components
  2. Another is the engineering of component libraries

Welcome to continue to pay attention to ~

Review of the last issue

The last issue mainly shared two parts:

  • Use VitePress to build the documentation system of the component library
  • Add demo expansion/collapse to the document system

In this issue, we invite Mr. Lei from Vue DevUI team to share with you the realization principle of DevUI CLI tool.

Lei was an early contributor to our Vue DevUI and the owner of Toast component. In the early days, the Vue DevUI component library was still not perfect. The component files and the entry files of the component library were manually added

  1. In the firstdevuiCreate a stack of files in the directoryindex.ts/src/xx.tsxEtc.
  2. indevui/vue-devui.tsAdd the corresponding component export to the component library entry file
  3. insidebar.tsAdd the corresponding component configuration from the components menu on the left

And public files like vue-devui. Ts /sidebar. Ts are often modified by multiple field owners, often leading to conflicts.

Lei was keen to find this problem, and took the initiative to create a DevUI CLI tool to solve this problem. In this live broadcast, Lei shared with you the implementation principle of DevUI CLI.

This is just the first installment in a series of DevUI CLI tools that will continue to delve into how to use the DevUI CLI:

  • Generate entry files for the component library
  • Create the component directory structure
  • Automatically generates the left component menu file

Here is the transcript:

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
    • srcType a directory
  • Component output names are inconsistent
    • The prefixDnamed
    • unprefixedDnamed
    • Lowercase hump named
    • Name the capital hump
    • xxxServicenamed
    • useXxxServicenamed
  • 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
    • commanderThe plug-in provides command registration, parameter resolution, and callback execution
  • interaction
    • inquirerPlug-ins for command line interaction (Q&A)
  • Logical processing
    • fs-extraPlug-in is tonodejsfileApiFurther encapsulation for easy use
    • koloristPlugins 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.

To be continued

Look forward to more exciting sharing in the future!