最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • Vue CLI 插件开发实战——10 分钟实现组件自动生成

    正文概述 掘金(炽翎)   2020-12-26   262

    前言

    近期工作的过程中跟 Vue CLI插件打交道比较多,想了想自己在学校写项目的时候最烦的就是项目创建之后手动创建组件/页面和配置路由,于是突发奇想决定写一个脚手架的插件,自动实现创建组件/页面和配置路由的功能

    本文会一步一步教你如何编写一个自己的 Vue CLI 插件,并发布至 npm,为所有因为这个问题而烦恼的同学解放双手。

    本教程的插件完整代码放在了我的 GitHub 上,欢迎大家 Star:vue-cli-plugin-generators

    同时,我也将这个插件发布到了 npm,大家可以直接使用 npm 安装并体验添加组件的能力。

    PS:添加页面和配置路由的能力还在开发中。

    体验方式:

    1. 通过 npm 安装
    npm install vue-cli-plugin-generators -D
    vue invoke vue-cli-plugin-generators
    
    1. 通过 yarn 安装
    yarn add vue-cli-plugin-generators -D
    vue invoke vue-cli-plugin-generators
    
    1. 通过 Vue CLI 安装(推荐)
    vue add vue-cli-plugin-generators
    

    注意:一定要注意是复数形式的 generators,不是单数形式的 generatorgenerator 被前辈的占领了。

    废话不多说,我们直接开始吧!

    前置知识

    要做好一个 Vue CLI 插件,除了要了解 Vue CLI 插件的开发规范之外,我们还需要了解几个 npm 包:

    • chalk 让你的控制台输出好看一点,为文字或背景上色
    • glob 让你可以使用 Shell 脚本的方式匹配文件
    • inquirer 让你可以使用交互式的命令行来获取需要的信息

    主要出现的 npm 包就只有这三个,其他的都是基于 Node.js 的各种模块,比如 fspath,了解过 Node.js 的同学应该不陌生。

    项目初始化

    创建一个空的文件夹,名字最好就是你的插件的名字。

    这里我的名字是 vue-cli-plugin-generators,你可以取一个自己喜欢的名字,不过最好是见名知义的那种,比如 vue-cli-plugin-component-generator 或者 vue-cli-plugin-page-generator,一看就知道是组件生成器和页面生成器。

    至于为什么一定要带上 vue-cli-plugin 的前缀这个问题,可以看一下官方文档:命名和可发现性。

    然后初始化我们的项目:

    npm init
    

    输入一些基本的信息,这些信息会被写入 package.json 文件中。

    创建一个基本的目录结构:

    .
    ├── LICENSE
    ├── README.md
    ├── generator
    │   ├── index.js
    │   └── template
    │       └── component
    │           ├── jsx
    │           │   └── Template.jsx
    │           ├── sfc
    │           │   └── Template.vue
    │           ├── style
    │           │   ├── index.css
    │           │   ├── index.less
    │           │   ├── index.sass
    │           │   ├── index.scss
    │           │   └── index.styl
    │           └── tsx
    │               └── Template.tsx
    ├── index.js
    ├── package.json
    ├── src
    │   ├── add-component.js
    │   ├── add-page.js
    │   └── utils
    │       ├── log.js
    │       └── suffix.js
    └── yarn.lock
    

    目录结构创建好了之后就可以开始编码了。

    目录解析

    一些不重要的文件就不讲解了,主要讲解一下作为一个优秀的 Vue CLI 插件,需要哪些部分:

    .
    ├── README.md
    ├── generator.js  # Generator(可选)
    ├── index.js      # Service 插件
    ├── package.json
    ├── prompts.js    # Prompt 文件(可选)
    └── ui.js         # Vue UI 集成(可选)
    

    主要分为 4 个部分:Generator/Service/Prompt/UI

    其中,Service 是必须的,其他的部分都是可选项。

    先来讲一下各个部分的作用:

    Generator

    Generator 可以为你的项目创建文件、编辑文件、添加依赖

    Generator 应该放在根目录下,被命名为 generator.js 或者放在 generator 目录下,被命名为 index.js,它会在调用 vue add 或者 vue invoke 时被执行。

    来看下我们这个项目的 generator/index.js

    /**
     * @file Generator
     */
    'use strict';
    
    // 前置知识中提到的美化控制台输出的包
    const chalk = require('chalk');
    
    // 封装的打印函数
    const log = require('../src/utils/log');
    
    module.exports = (api) => {
      // 执行脚本
      const extendScript = {
        scripts: {
          'add-component': 'vue-cli-service add-component',
          'add-page': 'vue-cli-service add-page'
        }
      };
      // 拓展 package.json 为其中的 scripts 中添加 add-component 和 add-page 两条指令
      api.extendPackage(extendScript);
    
      // 插件安装成功后 输出一些提示 可以忽略
      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('');
    };
    

    所以,当我们执行 vue add vue-cli-plugin-generators 的时候,generator/index.js 会被执行,你就可以看到你的控制台输出了这样的指引信息:

    Vue CLI 插件开发实战——10 分钟实现组件自动生成

    同时你还会发现,执行了 vue add vue-cli-plugin-generators 的项目中,package.json 发生了变化:

    Vue CLI 插件开发实战——10 分钟实现组件自动生成

    添加了两条指令,让我们可以通过 yarn add-componentyarn add-page 去添加组件/页面。

    虽然添加了这两条指令,但是现在这两条指令还没有被注册到 vue-cli-service 中,这时候我们就需要开始编写 Service 了。

    Service

    Service 可以为你的项目修改 Webpack 配置、创建 vue-cli-service 命令、修改 vue-cli-service 命令

    Service 应该放在根目录下,被命名为 index.js,它会在调用 vue-cli-service 时被执行。

    来看一下我们这个项目的 index.js

    /**
     * @file Service 插件
     */
    'use strict';
    
    const addComponent = require('./src/add-component');
    const addPage = require('./src/add-page');
    
    module.exports = (api, options) => {
      // 向 vue-cli-service 中注册 add-component 指令
      api.registerCommand('add-component', async () => {
        await addComponent(api);
      });
    
      // 向 vue-cli-service 中注册 add-page 指令
      api.registerCommand('add-page', async () => {
        await addPage(api);
      });
    };
    

    为了代码的可读性,我们把 add-componentadd-page 指令的回调函数单独抽了出来,分别放在了 src/add-component.jssrc/add-page.js 中:

    前方代码量较大,建议先阅读注释理解思路。

    /**
     * @file Add Component 逻辑
     */
    '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) => {
      // 交互式命令行参数 获取组件信息
      // componentName {string} 组件名称 默认 HelloWorld
      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'
        }
      ]);
    
      // 组件名称校验
      if (!componentName.trim() || /[^A-Za-z0-9]/g.test(componentName)) {
        log.error(
          `Error: Please input a correct name. ${chalk.bold('( PascalCase )')}`
        );
        return;
      }
    
      // 项目中组件文件路径 Vue CLI 创建的项目中默认路径为 src/components
      const baseDir = `${api.getCwd()}/src/components`;
      // 遍历组件文件 返回组件路径列表
      const existComponent = glob.sync(`${baseDir}/*`);
    
      // 替换组件路径列表中的基础路径 返回组件名称列表
      const existComponentName = existComponent.map((name) =>
        name.replace(`${baseDir}/`, '')
      );
    
      // 判断组件是否已存在
      const isExist = existComponentName.some((name) => {
        // 正则表达式匹配从控制台输入的组件名称是否已经存在
        const reg = new RegExp(
          `^(${componentName}.[vue|jsx|tsx])$|^(${componentName})$`,
          'g'
        );
        return reg.test(name);
      });
    
      // 存在则报错并退出
      if (isExist) {
        log.error(`Error: Component ${chalk.bold(componentName)} already exists.`);
        return;
      }
    
      // 交互式命令行 获取组件信息
      // componentType {'sfc'|'tsx'|'jsx'} 组件类型 默认 sfc
      // componentStyleType {'.css'|'.scss'|'.sass'|'.less'|'.stylus'} 组件样式类型 默认 .scss
      // shouldMkdir {boolean} 是否需要为组件创建文件夹 默认 true
      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
        }
      ]);
    
      // 根据不同的组件类型 生成对应的 template 路径
      let src = path.resolve(
        __dirname,
        `../generator/template/component/${componentType}/Template${suffix(
          componentType
        )}`
      );
      // 组件目标路径 默认未生成组件文件夹
      let dist = `${baseDir}/${componentName}${suffix(componentType)}`;
      // 根据不同的组件样式类型 生成对应的 template 路径
      let styleSrc = path.resolve(
        __dirname,
        `../generator/template/component/style/index${componentStyleType}`
      );
      // 组件样式目标路径 默认未生成组件文件夹
      let styleDist = `${baseDir}/${componentName}${componentStyleType}`;
    
      // 需要为组件创建文件夹
      if (shouldMkdir) {
        try {
          // 创建组件文件夹
          fs.mkdirSync(`${baseDir}/${componentName}`);
          // 修改组件目标路径
          dist = `${baseDir}/${componentName}/${componentName}${suffix(
            componentType
          )}`;
          // 修改组件样式目标路径
          styleDist = `${baseDir}/${componentName}/index${componentStyleType}`;
        } catch (e) {
          log.error(e);
          return;
        }
      }
    
      // 生成 SFC/TSX/JSX 及 CSS/SCSS/Sass/Less/Stylus
      try {
        // 读取组件 template
        // 替换组件名称为控制台输入的组件名称
        const template = fs
          .readFileSync(src)
          .toString()
          .replace(/helloworld/gi, componentName);
        // 读取组件样式 template
        // 替换组件类名为控制台输入的组件名称
        const style = fs
          .readFileSync(styleSrc)
          .toString()
          .replace(/helloworld/gi, componentName);
        if (componentType === 'sfc') {
          // 创建的组件类型为 SFC 则将组件样式 template 注入 <style></style> 标签中并添加样式类型
          fs.writeFileSync(
            dist,
            template
              // 替换组件样式为 template 并添加样式类型
              .replace(
                /<style>\s<\/style>/gi,
                () =>
                  `<style${
                    // 当组件样式类型为 CSS 时不需要添加组件样式类型
                    componentStyleType !== '.css'
                      ? ` lang="${
                          // 当组件样式类型为 Stylus 时需要做一下特殊处理
                          componentStyleType === '.styl'
                            ? 'stylus'
                            : componentStyleType.replace('.', '')
                        }"`
                      : ''
                  }>\n${style}</style>`
              )
          );
        } else {
          // 创建的组件类型为 TSX/JSX 则将组件样式 template 注入单独的样式文件
          fs.writeFileSync(
            dist,
            template.replace(
              // 当不需要创建组件文件夹时 样式文件应该以 [组件名称].[组件样式类型] 的方式引入
              /import '\.\/index\.css';/gi,
              `import './${
                shouldMkdir ? 'index' : `${componentName}`
              }${componentStyleType}';`
            )
          );
          fs.writeFileSync(styleDist, style);
        }
        // 组件创建完成 打印组件名称和组件文件路径
        log.success(
          `Success: Component ${chalk.bold(
            componentName
          )} was created in ${chalk.bold(dist)}`
        );
      } catch (e) {
        log.error(e);
        return;
      }
    };
    

    上面的代码是 add-component 指令的执行逻辑,比较长,可以稍微有点耐心阅读一下。

    由于 add-page 指令的执行逻辑还在开发过程中,这里就不贴出来了,大家可以自己思考一下,欢迎有好想法的同学为这个仓库提 PR:vue-cli-plugin-generators

    现在我们可以来执行一下 yarn add-component 来体验一下功能了:

    Vue CLI 插件开发实战——10 分钟实现组件自动生成

    这里我们分别创建了 SFC/TSX/JSX 三种类型的组件,目录结构如下:

    .
    ├── HelloJSX
    │   ├── HelloJSX.jsx
    │   └── index.scss
    ├── HelloSFC
    │   └── HelloSFC.vue
    ├── HelloTSX
    │   ├── HelloTSX.tsx
    │   └── index.scss
    └── HelloWorld.vue
    

    其中 HelloWorld.vueVue CLI 创建时自动生成的。

    对应的文件中组件名称和组件样式类名也被替换了。

    到这里我们就算完成了一个能够自动生成组件的 Vue CLI 插件了。

    但是,还不够!

    Prompt

    Prompt 会在创建新的项目或者在项目中添加新的插件时输出交互式命令行,获取 Generator 需要的信息,这些信息会在用户输入完成后以 options 的形式传递给 Generator,供 Generator 中的 ejs 模板渲染。

    Prompt 应该放在根目录下,被命名为 prompt.js,它会在调用 vue add 或者 vue invoke 时被执行,执行顺序位于 Generator 前。

    在我们的插件中,我们并不需要在调用 vue add 或者 vue invoke 时就创建组件/页面,因此不需要在这个时候获取组件的相关信息。

    UI

    UI 会在使用 vue ui 指令打开图形化操作界面后给到用户一个图形化的插件配置功能。

    这个部分的内容比较复杂,讲解起来比较费劲,大家可以到官网上阅读:UI 集成。

    在我们的插件中,我们并不需要使用 vue ui 启动图形化操作界面,因此不需要编写 UI 相关的代码。

    深入学习

    我们可以到 Vue CLI 插件开发指南中查看更详细的指南,建议阅读英文文档,没有什么教程比官方文档更加合适了

    总结

    一个优秀的 Vue CLI 插件应该有四个部分:

    .
    ├── README.md
    ├── generator.js  # Generator(可选)
    ├── index.js      # Service 插件
    ├── package.json
    ├── prompts.js    # Prompt 文件(可选)
    └── ui.js         # Vue UI 集成(可选)
    
    • Generator 可以为你的项目创建文件、编辑文件、添加依赖

    • Service 可以为你的项目修改 Webpack 配置、创建 vue-cli-service 命令、修改 vue-cli-service 命令

    • Prompt 会在创建新的项目或者在项目中添加新的插件时输出交互式命令行,获取 Generator 需要的信息,这些信息会在用户输入完成后以 options 的形式传递给 Generator,供 Generator 中的 ejs 模板渲染。

    • UI 会在使用 vue ui 指令打开图形化操作界面后给到用户一个图形化的插件配置功能。

    四个部分各司其职才能更好地实现一个完美的插件!

    本教程的插件完整代码放在了我的 GitHub 上,欢迎大家 Star:vue-cli-plugin-generators

    也欢迎大家通过 npm/yarn 安装到自己的项目中体验~

    参考资料

    • Vue CLI 插件开发指南

    起源地下载网 » Vue CLI 插件开发实战——10 分钟实现组件自动生成

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元