preface

Recently, I have been dealing with a lot of plug-ins of Vue CLI. I thought that the most annoying thing when I was writing a project in school was manually creating components/pages and configuring routes after the project was created. Therefore, I decided to write a scaffold plug-in to automatically realize the functions of creating components/pages and configuring routes.

This article will teach you step by step how to write your own Vue CLI plug-in, and publish to NPM, for all the students who are troubled by this problem free hands.

Follow “Hello FE” for more practical tutorials, just recently in the lucky draw, check out the history article to get lucky draw methods ~

Star: vuE-CLI-plugin-generators Star: VUE-cli-plugin-generators

I also released the plugin to NPM, where you can install and experience the ability to add components directly.

PS: The ability to add pages and configure routes is still in development.

Experience mode:

  1. throughnpmThe installation
npm install vue-cli-plugin-generators -D
vue invoke vue-cli-plugin-generators
Copy the code
  1. throughyarnThe installation
yarn add vue-cli-plugin-generators -D
vue invoke vue-cli-plugin-generators
Copy the code
  1. throughVue CLIInstallation (recommended)
vue add vue-cli-plugin-generators
Copy the code

Note: Be sure to use the plural formgenerators“, not singulargenerator.generatorIt was taken over by the old guard.

Without further ado, let’s get right to it!

Front knowledge

To make a good Vue CLI plug-in, in addition to understanding the development specification of the Vue CLI plug-in, we also need to understand several NPM packages:

  • chalkMake your console output look nice by coloring the text or background
  • globSo you can use itShellThe way the script matches the file
  • inquirerAllows you to use the interactive command line to get the information you need

There are only three main NPM packages, the other are based on node.js modules, such as FS and PATH, should be familiar to those who know Node.js.

Project initialization

Create an empty folder, preferably with the name of your plugin.

Here my name is vuE-cli-plugin-generators, you can call it what you like, but it’s best to call it by its name, Vue-cli-plugin-component-generator or vue-cli-plugin-page-generator are both component and page generators.

As to why the vue-cli-plugin prefix is necessary, take a look at the official documentation: Naming and Discoverability.

Then initialize our project:

npm init
Copy the code

Enter some basic information that will be written to the package.json file.

Create a basic directory structure:

. ├ ─ ─ LICENSE ├ ─ ─ the README. Md ├ ─ ─ the generator │ ├ ─ ─ index. The js │ └ ─ ─ the template │ └ ─ ─ component │ ├ ─ ─ JSX │ │ └ ─ ─ the template. The JSX │ ├ ─ ─ SFC │ │ └ ─ ─ the Template. The vue │ ├ ─ ─ style │ │ ├ ─ ─ index. The CSS │ │ ├ ─ ─ but less │ │ ├ ─ ─ index. The sass │ │ ├ ─ ─ index. The SCSS │ │ └ ─ ─ index. Styl │ └ ─ ─ TSX │ └ ─ ─ the Template. The TSX ├ ─ ─ index. The js ├ ─ ─ package. The json ├ ─ ─ the SRC │ ├ ─ ─ the add - component. Js │ ├ ─ ─ Add - page. Js │ └ ─ ─ utils │ ├ ─ ─ the js │ └ ─ ─ suffix. Js └ ─ ─ yarn. The lockCopy the code

Once the directory structure is created, you can start coding.

Directory parse

I’m going to skip some unimportant files and focus on what it takes to be a good Vue CLI plug-in:

. ├ ─ ─ the README. Md ├ ─ ─ the generator. The js # generator (optional) ├ ─ ─ index. The js # Service plug-in ├ ─ ─ package. The json ├ ─ ─ prompts. Js # Prompt └─ └─ imp. Js #Copy the code

Mainly divided into four parts: the Generator/Service/Prompt/UI.

Service is required, and the rest is optional.

Here’s what each part does:

Generator

Generator can create files, edit files, and add dependencies for your project.

The Generator should be placed in the root directory named Generator.js or in the Generator directory named index.js and will be executed when vue add or Vue Invoke is called.

Take a look at the generator/index.js for our project:

/ * * *@file Generator* /
'use strict';

// Package to beautify console output mentioned in preknowledge
const chalk = require('chalk');

// Encapsulates the print function
const log = require('.. /src/utils/log');

module.exports = (api) = > {
  // Execute the script
  const extendScript = {
    scripts: {
      'add-component': 'vue-cli-service add-component'.'add-page': 'vue-cli-service add-page'}};// Extend package.json to add add-component and add-page directives to scripts
  api.extendPackage(extendScript);

  // After the plug-in is installed successfully, some messages can be ignored
  console.log(' ');
  log.success(`Success: Add plugin success.`);
  console.log(' ');
  console.log('You can use it with:');
  console.log(' ');
  console.log(`   ${chalk.cyan('yarn add-component')}`);
  console.log(' or');
  console.log(`   ${chalk.cyan('yarn add-page')}`);
  console.log(' ');
  console.log('to create a component or page.');
  console.log(' ');
  console.log(`${chalk.green.bold('Enjoy it! ')}`);
  console.log(' ');
};
Copy the code

So when we call vue add vuE-cli-plugin-generators, generator/index.js will be executed and you will see your console output the following instructions:

/ / Package. json (vue add vuE-cli-plugin-generators) / / package.json (vue add vue-cli-plugin-generators)

Two directives have been added that allow you to add components/pages via YARN Add-component and YARN Add-page.

Although these two directives have been added, they are not registered with vue-cli-service yet, so we need to start writing the service.

Service

Service You can modify the Webpack configuration, create vue-cli-service commands, and modify vue-cli-service commands for your project.

The Service should be placed in the root directory, named index.js, and will be executed when vue-cli-service is called.

Take a look at our index.js for this project:

/ * * *@file Service plug-in * /
'use strict';

const addComponent = require('./src/add-component');
const addPage = require('./src/add-page');

module.exports = (api, options) = > {
  // Register the Add-Component directive with vue-cli-service
  api.registerCommand('add-component'.async() = > {await addComponent(api);
  });

  // Register the add-page directive with vue-cli-service
  api.registerCommand('add-page'.async() = > {await addPage(api);
  });
};
Copy the code

For readability, we separate the add-component and add-page callbacks into SRC/Add-component. js and SRC /add-page.js respectively:

There is a large amount of code ahead, so it is recommended to read the comments first to understand the idea.

/ * * *@file Add Component logic */
'use strict';

const fs = require('fs');
const path = require('path');
const glob = require('glob');
const chalk = require('chalk');
const inquirer = require('inquirer');

const log = require('./utils/log');
const suffix = require('./utils/suffix');

module.exports = async (api) => {
  // Interactive command line arguments get component information
  // componentName {string} componentName HelloWorld by default
  const { componentName } = await inquirer.prompt([
    {
      name: 'componentName'.type: 'input'.message: `Please input your component name. ${chalk.yellow(
        '( PascalCase )'
      )}`.description: `You should input a ${chalk.yellow(
        'PascalCase'
      )}, it will be used to name new component.`.default: 'HelloWorld'}]);// Check component name
  if(! componentName.trim() ||/[^A-Za-z0-9]/g.test(componentName)) {
    log.error(
      `Error: Please input a correct name. ${chalk.bold('( PascalCase )')}`
    );
    return;
  }

  // Component file path in the project The default path in the project created by the Vue CLI is SRC /components
  const baseDir = `${api.getCwd()}/src/components`;
  // Traversing the component file returns a list of component paths
  const existComponent = glob.sync(`${baseDir}/ * `);

  // Replacing the base path in the component path list returns the list of component names
  const existComponentName = existComponent.map((name) = >
    name.replace(`${baseDir}/ `.' '));// Check whether the component already exists
  const isExist = existComponentName.some((name) = > {
    // The regular expression matches whether the component name entered from the console already exists
    const reg = new RegExp(
      ` ^ (${componentName}.[vue|jsx|tsx])$|^(${componentName}) $`.'g'
    );
    return reg.test(name);
  });

  // If it exists, an error is reported and exit
  if (isExist) {
    log.error(`Error: Component ${chalk.bold(componentName)} already exists.`);
    return;
  }

  // Interactive command line to get component information
  / / the pop3access.componenttype {' SFC '|' benchmark '|' JSX} component type SFC by default
  / / componentStyleType {' CSS '|'. The SCSS '|'. Sass '|' less '|'. Stylus'} component style type By default, SCSS
  // shouldMkdir {Boolean} Whether to create a folder for the component is true by default
  const {
    componentType,
    componentStyleType,
    shouldMkdir
  } = await inquirer.prompt([
    {
      name: 'componentType'.type: 'list'.message: `Please select your component type. ${chalk.yellow(
        '( .vue / .tsx / .jsx )'
      )}`.choices: [{name: 'SFC (.vue)'.value: 'sfc' },
        { name: 'TSX (.tsx)'.value: 'tsx' },
        { name: 'JSX (.jsx)'.value: 'jsx'}].default: 'sfc'
    },
    {
      name: 'componentStyleType'.type: 'list'.message: `Please select your component style type. ${chalk.yellow(
        '( .css / .sass / .scss / .less / .styl )'
      )}`.choices: [{name: 'CSS (.css)'.value: '.css' },
        { name: 'SCSS (.scss)'.value: '.scss' },
        { name: 'Sass (.sass)'.value: '.sass' },
        { name: 'Less (.less)'.value: '.less' },
        { name: 'Stylus (.styl)'.value: '.styl'}].default: '.scss'
    },
    {
      name: 'shouldMkdir'.type: 'confirm'.message: `Should make a directory for new component? ${chalk.yellow(
        '( Suggest to create. )'
      )}`.default: true}]);// Generate the corresponding template path for different component types
  let src = path.resolve(
    __dirname,
    `.. /generator/template/component/${componentType}/Template${suffix( componentType )}`
  );
  // Component target path does not generate component folder by default
  let dist = `${baseDir}/${componentName}${suffix(componentType)}`;
  // Generate the corresponding template path according to the different component style types
  let styleSrc = path.resolve(
    __dirname,
    `.. /generator/template/component/style/index${componentStyleType}`
  );
  // Component style target path does not generate component folders by default
  let styleDist = `${baseDir}/${componentName}${componentStyleType}`;

  // You need to create folders for the components
  if (shouldMkdir) {
    try {
      // Create a component folder
      fs.mkdirSync(`${baseDir}/${componentName}`);
      // Modify the component target path
      dist = `${baseDir}/${componentName}/${componentName}${suffix( componentType )}`;
      // Modify the component style target path
      styleDist = `${baseDir}/${componentName}/index${componentStyleType}`;
    } catch (e) {
      log.error(e);
      return; }}/ / generated SFC/TSX composite/JSX and CSS/SCSS/Sass/Less/Stylus
  try {
    // Read the component template
    // Replace component name with the component name entered by the console
    const template = fs
      .readFileSync(src)
      .toString()
      .replace(/helloworld/gi, componentName);
    // Read the component style template
    // Replace the component class name entered by the console
    const style = fs
      .readFileSync(styleSrc)
      .toString()
      .replace(/helloworld/gi, componentName);
    if (componentType === 'sfc') {
      // If the component type is SFC, inject the component style template into the  tag and add the style type
      fs.writeFileSync(
        dist,
        template
          // Replace the component style with template and add the style type
          .replace(
            /<style>\s<\/style>/gi.() = >
              `<styleThe ${//You do not need to add the componentStyleType componentStyleType when the componentStyleType is CSS. = ='.css'
                  ? ` lang="The ${//A special treatment is required for componentStyleType Stylus (componentStyleType ==='.styl'
                        ? 'stylus'
                        : componentStyleType.replace('. '.' ')}"`
                  : ' '
              }>\n${style}</style>`)); }else {
      // Create a component of type TSX/JSX and inject the component style template into a separate style file
      fs.writeFileSync(
        dist,
        template.replace(
          // Style files should be introduced as [component name].[Component style type] when there is no need to create component folders
          /import '\.\/index\.css'; /gi.`import './${
            shouldMkdir ? 'index' : `${componentName}`
          }${componentStyleType}'; `)); fs.writeFileSync(styleDist, style); }// After the component is created, the component name and component file path are displayed
    log.success(
      `Success: Component ${chalk.bold( componentName )} was created in ${chalk.bold(dist)}`
    );
  } catch (e) {
    log.error(e);
    return; }};Copy the code

The code above is the execution logic for the Add-Component directive. It’s a long one to read with a little patience.

Due to add – page instruction execution logic is still in the development process, is not posted here, you can think about it, welcome to give students have good ideas for this warehouse PR: vue – cli – plugin – generators.

Now we can execute yarn Add-Component to see what it does:

Here we have created three types of SFC/TSX/JSX components respectively. The directory structure is as follows:

.Heavy Exercises ── heavy Exercises ─ heavy exercises ─ heavy exercises ─ heavy exercises ─ heavy exercises ─ heavy exercises ─ heavy exercises ─ heavy exercises ─ heavy exercises ─ heavy exercises The index, SCSS └ ─ ─ the HelloWorld. VueCopy the code

Helloworld. vue is automatically generated when vue CLI is created.

The component name and component style class name in the corresponding file have also been replaced.

Here we have a Vue CLI plug-in that can automatically generate components.

But not enough!

Prompt

Prompt prints an interactive command line when a new project is created or a new plug-in is added to the project, fetching information needed by the Generator, which is passed to the Generator in the form of options after user input for rendering by the EJS template in the Generator.

Prompt should be in the root directory, named Prompt. Js, and will be executed when vue add or Vue Invoke is called, in the order before the Generator.

In our plug-in, we don’t need to create a component/page when we call Vue Add or Vue Invoke, so we don’t need to get information about the component at this point.

UI

UI will give the user a graphical plug-in configuration function after opening the graphical operation interface using the Vue UI command.

This section is a bit complicated and difficult to explain, but you can read it on the website: UI Integration.

In our plug-in, we don’t need to use the Vue UI to launch the graphical operation interface, so we don’t need to write UI-related code.

Learn more

You can check out the Vue CLI plug-in Development Guide for a more detailed guide. It is recommended to read the English documentation. There is no better tutorial than the official documentation.

conclusion

A good Vue CLI plug-in should have four parts:

. ├ ─ ─ the README. Md ├ ─ ─ the generator. The js # generator (optional) ├ ─ ─ index. The js # Service plug-in ├ ─ ─ package. The json ├ ─ ─ prompts. Js # Prompt └─ └─ imp. Js #Copy the code
  • Generator can create files, edit files, and add dependencies for your project.

  • Service You can modify the Webpack configuration, create vue-cli-service commands, and modify vue-cli-service commands for your project.

  • Prompt prints an interactive command line when a new project is created or a new plug-in is added to the project, fetching information needed by the Generator, which is passed to the Generator in the form of options after user input for rendering by the EJS template in the Generator.

  • UI will give the user a graphical plug-in configuration function after opening the graphical operation interface using the Vue UI command.

The best way to achieve a perfect plug-in is to have four parts each doing their job!

Star: vuE-CLI-plugin-generators Star: VUE-cli-plugin-generators

You are also welcome to install NPM/YARN in your own projects

Check out “Hello FE” for more hands-on tutorials

The resources

  • Vue CLIPlug-in Development Guide