Vue CLI 插件开发完全指南:从原理到实战

在这里插入图片描述

文章目录

    • [1. Vue CLI 插件概述](#1. Vue CLI 插件概述)
      • [1.1 什么是 Vue CLI 插件?](#1.1 什么是 Vue CLI 插件?)
      • [1.2 插件的作用和价值](#1.2 插件的作用和价值)
    • [2. Vue CLI 插件架构与工作原理](#2. Vue CLI 插件架构与工作原理)
      • [2.1 插件系统架构](#2.1 插件系统架构)
      • [2.2 核心概念解析](#2.2 核心概念解析)
    • [3. 插件开发环境搭建](#3. 插件开发环境搭建)
      • [3.1 创建插件项目结构](#3.1 创建插件项目结构)
      • [3.2 基础 package.json 配置](#3.2 基础 package.json 配置)
    • [4. Service Plugin 开发](#4. Service Plugin 开发)
      • [4.1 基础 Service Plugin 结构](#4.1 基础 Service Plugin 结构)
      • [4.2 自定义命令实现](#4.2 自定义命令实现)
    • [5. Generator API 开发](#5. Generator API 开发)
      • [5.1 Generator 核心实现](#5.1 Generator 核心实现)
      • [5.2 模板文件示例](#5.2 模板文件示例)
    • [6. Prompts 用户交互](#6. Prompts 用户交互)
      • [6.1 交互式提示配置](#6.1 交互式提示配置)
      • [6.2 基于用户选择的逻辑处理](#6.2 基于用户选择的逻辑处理)
    • [7. UI 界面集成](#7. UI 界面集成)
      • [7.1 Vue CLI UI 插件配置](#7.1 Vue CLI UI 插件配置)
    • [8. 完整插件示例:Analytics Plugin](#8. 完整插件示例:Analytics Plugin)
      • [8.1 插件结构](#8.1 插件结构)
      • [8.2 Service Plugin 实现](#8.2 Service Plugin 实现)
      • [8.3 分析命令实现](#8.3 分析命令实现)
    • [9. 插件测试和调试](#9. 插件测试和调试)
      • [9.1 本地测试配置](#9.1 本地测试配置)
      • [9.2 集成测试示例](#9.2 集成测试示例)
    • [10. 插件发布和维护](#10. 插件发布和维护)
      • [10.1 发布准备](#10.1 发布准备)
      • [10.2 文档和示例](#10.2 文档和示例)
    • 配置
    • 使用
    • [API 参考](#API 参考)
      • [Plugin Options](#Plugin Options)
      • [Global Methods](#Global Methods)
    • 许可证

本文深入探讨 Vue CLI 插件开发的全过程,包含详细的工作原理、开发流程、实际案例和最佳实践,帮助您掌握自定义 Vue CLI 插件的核心技能。

1. Vue CLI 插件概述

1.1 什么是 Vue CLI 插件?

Vue CLI 插件是一个 npm 包,可以为 Vue CLI 创建的项目添加额外功能。它可以:

  • 添加新的 CLI 命令
  • 修改 webpack 配置
  • 添加新的 UI 界面
  • 注入额外的依赖
  • 修改项目文件结构
  • 集成第三方工具和服务

1.2 插件的作用和价值

解决的问题:

  • 重复配置的自动化
  • 团队工具链的统一
  • 项目最佳实践的标准化
  • 复杂功能的模块化封装

典型应用场景:

  • UI 组件库集成(如 Element UI、Vant)
  • 状态管理方案(如 Vuex、Pinia)
  • 测试框架配置(如 Jest、Cypress)
  • 微前端架构支持
  • 部署和 DevOps 集成

2. Vue CLI 插件架构与工作原理

2.1 插件系统架构

Vue CLI Service Plugin API Generator API Service Plugin API Prompt API 文件操作 依赖管理 模板渲染 Webpack 配置 ChainWebpack 环境变量 用户交互 条件逻辑 项目文件修改 构建配置

2.2 核心概念解析

Service Plugin:

  • 运行时插件
  • 修改 webpack 配置
  • 添加环境变量
  • 注册 CLI 命令

Generator:

  • 项目创建时执行
  • 修改项目文件结构
  • 注入依赖包
  • 渲染模板文件

Prompts:

  • 用户交互收集信息
  • 条件性功能启用
  • 动态配置生成

3. 插件开发环境搭建

3.1 创建插件项目结构

bash 复制代码
# 创建插件目录
mkdir vue-cli-plugin-my-plugin
cd vue-cli-plugin-my-plugin

# 初始化 package.json
npm init -y

项目结构:

复制代码
vue-cli-plugin-my-plugin/
├── package.json
├── index.js                 # Service Plugin
├── generator.js            # Generator API
├── prompts.js              # 用户提示
├── ui.js                   # Vue CLI UI 集成
├── templates/              # 模板文件
│   ├── src/
│   │   ├── plugins/
│   │   │   └── my-plugin.js
│   │   └── views/
│   │       └── About.vue
│   └── tests/
│       └── e2e/
│           └── my-plugin.spec.js
└── README.md

3.2 基础 package.json 配置

json 复制代码
{
  "name": "vue-cli-plugin-my-plugin",
  "version": "1.0.0",
  "description": "A custom Vue CLI plugin for demo purposes",
  "main": "index.js",
  "keywords": [
    "vue",
    "vue-cli",
    "plugin"
  ],
  "author": "Your Name",
  "license": "MIT",
  "repository": {
    "type": "git",
    "url": "https://github.com/your-username/vue-cli-plugin-my-plugin"
  },
  "engines": {
    "node": ">=8.9",
    "npm": ">=5.0.0"
  },
  "peerDependencies": {
    "@vue/cli-service": "^3.0.0 || ^4.0.0 || ^5.0.0"
  },
  "devDependencies": {
    "@vue/cli-service": "^5.0.0"
  }
}

4. Service Plugin 开发

4.1 基础 Service Plugin 结构

javascript 复制代码
// index.js
module.exports = (api, options) => {
  // 检查 Vue CLI 版本
  if (api.version && parseInt(api.version.split('.')[0]) < 3) {
    throw new Error(
      `vue-cli-plugin-my-plugin 需要 Vue CLI 3 或更高版本。当前版本: ${api.version}`
    );
  }

  // 注册 CLI 命令
  api.registerCommand(
    'my-command',
    {
      description: '自定义插件命令演示',
      usage: 'vue-cli-service my-command [options]',
      options: {
        '--mode': '指定环境模式 (默认: development)',
        '--debug': '启用调试模式'
      }
    },
    (args) => {
      // 命令实现
      require('./commands/my-command')(args, api, options);
    }
  );

  // 修改 webpack 配置
  api.chainWebpack((webpackConfig) => {
    // 根据环境进行配置
    if (process.env.NODE_ENV === 'production') {
      configureForProduction(webpackConfig, options);
    } else {
      configureForDevelopment(webpackConfig, options);
    }
  });

  // 配置开发服务器
  api.configureDevServer((app, server) => {
    // 添加自定义中间件
    app.use('/api/my-plugin', require('./dev-server-middleware'));
  });

  // 监听文件变化
  api.registerCommandHooks((hooks) => {
    hooks.afterBuild.tap('my-plugin', (stats) => {
      console.log('构建完成,执行插件后处理...');
    });
  });
};

// 生产环境配置
function configureForProduction(webpackConfig, options) {
  webpackConfig
    .plugin('my-plugin-banner')
    .use(require('webpack').BannerPlugin, [
      {
        banner: `My Plugin v${require('./package.json').version}\nBuild time: ${new Date().toISOString()}`,
      },
    ]);
}

// 开发环境配置
function configureForDevelopment(webpackConfig, options) {
  webpackConfig
    .devServer
    .set('before', (app) => {
      app.get('/__my-plugin__/status', (req, res) => {
        res.json({ status: 'ok', timestamp: Date.now() });
      });
    });
}

4.2 自定义命令实现

javascript 复制代码
// commands/my-command.js
module.exports = (args, api, options) => {
  const chalk = require('chalk');
  const { log, error, warn } = require('@vue/cli-shared-utils');
  
  const fs = require('fs');
  const path = require('path');

  log(`${chalk.cyan('My Plugin Command')} - 开始执行...`);

  try {
    const projectRoot = api.resolve('.');
    const packageJsonPath = path.join(projectRoot, 'package.json');
    
    if (!fs.existsSync(packageJsonPath)) {
      error('未找到 package.json 文件');
      process.exit(1);
    }

    const packageJson = require(packageJsonPath);
    
    // 显示项目信息
    log(`项目名称: ${chalk.green(packageJson.name)}`);
    log(`项目版本: ${chalk.green(packageJson.version)}`);
    log(`Vue CLI 版本: ${chalk.green(api.version)}`);
    
    // 根据参数执行不同操作
    if (args.debug) {
      log(`${chalk.yellow('调试模式已启用')}`);
      log('参数:', args);
      log('插件选项:', options.pluginOptions || {});
    }

    // 执行插件特定逻辑
    if (args.mode) {
      log(`运行模式: ${chalk.blue(args.mode)}`);
    }

    log(`${chalk.green('✓')} 命令执行完成`);

  } catch (err) {
    error(`命令执行失败: ${err.message}`);
    process.exit(1);
  }
};

5. Generator API 开发

5.1 Generator 核心实现

javascript 复制代码
// generator.js
module.exports = (api, options, rootOptions) => {
  // 插件选项
  const pluginOptions = options.pluginOptions && options.pluginOptions.myPlugin 
    ? options.pluginOptions.myPlugin 
    : {};

  // 添加依赖
  api.extendPackage({
    dependencies: {
      'axios': '^1.0.0',
      'lodash': '^4.17.21'
    },
    devDependencies: {
      '@types/lodash': '^4.14.182'
    },
    scripts: {
      'my-plugin:analyze': 'vue-cli-service my-command --analyze',
      'my-plugin:build': 'vue-cli-service my-command --mode production'
    },
    // 添加 ESLint 配置
    eslintConfig: {
      rules: {
        'my-plugin/custom-rule': 'warn'
      }
    }
  });

  // 渲染模板文件
  api.render('./templates', {
    ...options,
    pluginOptions,
    hasTypeScript: api.hasPlugin('typescript'),
    hasRouter: api.hasPlugin('router'),
    hasVuex: api.hasPlugin('vuex')
  });

  // 修改 main.js
  api.injectImports(api.entryFile, `import './plugins/my-plugin'`);

  // 条件性文件操作
  if (pluginOptions.addExampleComponent) {
    api.render({
      './src/components/ExampleComponent.vue': './templates/src/components/ExampleComponent.vue'
    });
  }

  // 项目创建完成后的回调
  api.onCreateComplete(() => {
    const fs = require('fs');
    const path = require('path');
    
    const eslintrcPath = api.resolve('.eslintrc.js');
    if (fs.existsSync(eslintrcPath)) {
      // 修改 ESLint 配置
      const content = fs.readFileSync(eslintrcPath, 'utf-8');
      const updatedContent = content.replace(
        'rules: {}',
        `rules: {
          'my-plugin/custom-rule': 'warn'
        }`
      );
      fs.writeFileSync(eslintrcPath, updatedContent);
    }
  });
};

5.2 模板文件示例

插件入口文件:

javascript 复制代码
// templates/src/plugins/my-plugin.js
import axios from 'axios';
import _ from 'lodash';

class MyPlugin {
  constructor(options = {}) {
    this.options = {
      baseURL: process.env.VUE_APP_API_BASE_URL || '/api',
      timeout: 10000,
      ...options
    };
    
    this.axiosInstance = axios.create({
      baseURL: this.options.baseURL,
      timeout: this.options.timeout
    });
    
    this.setupInterceptors();
    this.installHelpers();
  }

  setupInterceptors() {
    // 请求拦截器
    this.axiosInstance.interceptors.request.use(
      (config) => {
        console.log(`[My Plugin] 发送请求: ${config.method?.toUpperCase()} ${config.url}`);
        return config;
      },
      (error) => {
        console.error('[My Plugin] 请求错误:', error);
        return Promise.reject(error);
      }
    );

    // 响应拦截器
    this.axiosInstance.interceptors.response.use(
      (response) => {
        console.log(`[My Plugin] 收到响应: ${response.status} ${response.config.url}`);
        return response;
      },
      (error) => {
        console.error('[My Plugin] 响应错误:', error);
        return Promise.reject(error);
      }
    );
  }

  installHelpers() {
    // 添加全局工具方法
    if (window && !window.$myPlugin) {
      window.$myPlugin = {
        deepClone: (obj) => _.cloneDeep(obj),
        debounce: (func, wait) => _.debounce(func, wait),
        request: this.axiosInstance
      };
    }
  }

  install(Vue) {
    // 添加 Vue 实例方法
    Vue.prototype.$myPlugin = this;
    
    // 添加全局混入
    Vue.mixin({
      created() {
        if (this.$options.myPluginOptions) {
          console.log('[My Plugin] 组件创建:', this.$options.name);
        }
      }
    });

    // 添加全局组件
    // Vue.component('MyPluginComponent', ...);
  }
}

// 创建插件实例
const myPlugin = new MyPlugin();

export default myPlugin;

示例组件模板:

vue 复制代码
<!-- templates/src/components/ExampleComponent.vue -->
<template>
  <div class="example-component">
    <h3>{{ title }}</h3>
    <div class="content">
      <p>这是一个通过 My Plugin 自动生成的示例组件</p>
      <button @click="handleClick" class="demo-button">
        点击次数: {{ count }}
      </button>
      <div v-if="data" class="data-display">
        <h4>示例数据:</h4>
        <pre>{{ formattedData }}</pre>
      </div>
    </div>
  </div>
</template>

<script>
export default {
  name: 'ExampleComponent',
  data() {
    return {
      title: 'My Plugin 示例组件',
      count: 0,
      data: null
    }
  },
  computed: {
    formattedData() {
      return this.data ? JSON.stringify(this.data, null, 2) : '暂无数据';
    }
  },
  mounted() {
    this.fetchData();
  },
  methods: {
    handleClick() {
      this.count++;
      this.$emit('button-click', this.count);
    },
    async fetchData() {
      try {
        if (this.$myPlugin) {
          const response = await this.$myPlugin.request.get('/example');
          this.data = response.data;
        }
      } catch (error) {
        console.error('获取数据失败:', error);
      }
    }
  }
}
</script>

<style scoped>
.example-component {
  border: 1px solid #eaeaea;
  border-radius: 8px;
  padding: 20px;
  margin: 20px 0;
  background: #f9f9f9;
}

.example-component h3 {
  color: #2c3e50;
  margin-bottom: 15px;
}

.demo-button {
  background-color: #3498db;
  color: white;
  border: none;
  padding: 10px 20px;
  border-radius: 4px;
  cursor: pointer;
  font-size: 14px;
  transition: background-color 0.3s;
}

.demo-button:hover {
  background-color: #2980b9;
}

.data-display {
  margin-top: 15px;
  padding: 10px;
  background: white;
  border-radius: 4px;
  border: 1px solid #ddd;
}

.data-display pre {
  margin: 0;
  font-size: 12px;
  white-space: pre-wrap;
}
</style>

6. Prompts 用户交互

6.1 交互式提示配置

javascript 复制代码
// prompts.js
module.exports = [
  {
    name: 'features',
    type: 'checkbox',
    message: '选择要启用的功能:',
    choices: [
      {
        name: '添加示例组件',
        value: 'addExampleComponent',
        description: '添加一个演示用的 Vue 组件'
      },
      {
        name: '集成 API 服务',
        value: 'addApiService',
        description: '添加 Axios 配置和 API 服务示例'
      },
      {
        name: '添加工具函数',
        value: 'addUtils',
        description: '添加常用的工具函数库'
      },
      {
        name: '集成代码规范',
        value: 'addLinting',
        description: '添加 ESLint 规则和代码规范配置'
      }
    ],
    default: ['addExampleComponent', 'addApiService']
  },
  {
    name: 'apiBaseUrl',
    type: 'input',
    message: '请输入 API 基础 URL:',
    default: '/api',
    when: answers => answers.features.includes('addApiService'),
    validate: input => input ? true : '请输入有效的 URL'
  },
  {
    name: 'useTypeScript',
    type: 'confirm',
    message: '是否使用 TypeScript?',
    default: false,
    when: () => {
      try {
        require.resolve('typescript');
        return true;
      } catch (e) {
        return false;
      }
    }
  },
  {
    name: 'addDemoPage',
    type: 'confirm',
    message: '是否添加演示页面?',
    default: true,
    when: answers => answers.features.includes('addExampleComponent')
  },
  {
    name: 'themeColor',
    type: 'list',
    message: '选择主题颜色:',
    choices: [
      { name: '蓝色主题', value: 'blue' },
      { name: '绿色主题', value: 'green' },
      { name: '紫色主题', value: 'purple' },
      { name: '橙色主题', value: 'orange' }
    ],
    default: 'blue',
    when: answers => answers.features.includes('addExampleComponent')
  }
];

6.2 基于用户选择的逻辑处理

javascript 复制代码
// generator.js - 增强版本
module.exports = (api, options, rootOptions, answers = {}) => {
  const pluginOptions = {
    ...(options.pluginOptions && options.pluginOptions.myPlugin),
    ...answers
  };

  // 根据用户选择添加依赖
  const packageExtend = {
    dependencies: {},
    devDependencies: {},
    scripts: {}
  };

  // 基础依赖
  packageExtend.dependencies.axios = '^1.0.0';

  // 条件依赖
  if (pluginOptions.features && pluginOptions.features.includes('addUtils')) {
    packageExtend.dependencies.lodash = '^4.17.21';
    if (pluginOptions.useTypeScript) {
      packageExtend.devDependencies['@types/lodash'] = '^4.14.182';
    }
  }

  if (pluginOptions.features && pluginOptions.features.includes('addLinting')) {
    packageExtend.devDependencies['eslint-plugin-my-plugin'] = '^1.0.0';
    packageExtend.eslintConfig = {
      extends: ['plugin:my-plugin/recommended']
    };
  }

  api.extendPackage(packageExtend);

  // 渲染模板
  api.render('./templates', {
    ...options,
    pluginOptions,
    hasTypeScript: pluginOptions.useTypeScript || api.hasPlugin('typescript')
  });

  // 根据主题颜色修改配置
  if (pluginOptions.themeColor) {
    api.injectImports(api.entryFile, `import './styles/theme-${pluginOptions.themeColor}.css'`);
  }

  // 添加演示页面路由
  if (pluginOptions.addDemoPage && api.hasPlugin('router')) {
    api.injectImports(
      api.resolve('./src/router/index.js'),
      `import ExamplePage from '@/views/ExamplePage.vue'`
    );
    
    api.injectRootOptions(
      api.resolve('./src/router/index.js'),
      `routes: [
        // 其他路由...
        {
          path: '/example',
          name: 'ExamplePage',
          component: ExamplePage
        }
      ]`
    );
  }
};

7. UI 界面集成

7.1 Vue CLI UI 插件配置

javascript 复制代码
// ui.js
const { openBrowser } = require('@vue/cli-shared-utils');

module.exports = (api, options) => {
  // 添加任务
  api.addTask({
    name: 'my-plugin-task',
    command: 'vue-cli-service my-command',
    description: '运行 My Plugin 自定义任务',
    link: 'https://github.com/your-username/vue-cli-plugin-my-plugin#readme'
  });

  // 添加配置面板
  api.addClientAddon({
    id: 'my-plugin.config',
    url: 'https://unpkg.com/vue-cli-plugin-my-plugin/dist/config.js'
  });

  // 共享数据
  api.onViewOpen((view) => {
    if (view.id === 'my-plugin-view') {
      console.log('My Plugin 视图已打开');
    }
  });

  // 项目文件变化监听
  api.onProjectFileChange((file) => {
    if (file.endsWith('my-plugin.config.js')) {
      api.emit('my-plugin-config-changed');
    }
  });
};

// 配置界面组件
module.exports.config = {
  components: {
    'my-plugin-config': {
      template: `
        <div class="my-plugin-config">
          <h3>My Plugin 配置</h3>
          <div class="config-section">
            <div class="form-group">
              <label>API 基础 URL</label>
              <input 
                v-model="config.apiBaseUrl" 
                type="text" 
                placeholder="/api"
                class="form-control"
              >
            </div>
            <div class="form-group">
              <label>
                <input 
                  v-model="config.enableDebug" 
                  type="checkbox"
                >
                启用调试模式
              </label>
            </div>
            <div class="form-group">
              <label>主题颜色</label>
              <select v-model="config.themeColor" class="form-control">
                <option value="blue">蓝色</option>
                <option value="green">绿色</option>
                <option value="purple">紫色</option>
                <option value="orange">橙色</option>
              </select>
            </div>
            <button @click="saveConfig" class="btn btn-primary">
              保存配置
            </button>
          </div>
        </div>
      `,
      data() {
        return {
          config: {
            apiBaseUrl: '/api',
            enableDebug: false,
            themeColor: 'blue'
          }
        };
      },
      methods: {
        async saveConfig() {
          try {
            await this.$callPluginAction('my-plugin/save-config', this.config);
            this.$notify({
              title: '配置保存成功',
              type: 'success'
            });
          } catch (error) {
            this.$notify({
              title: '保存失败',
              text: error.message,
              type: 'error'
            });
          }
        }
      },
      async created() {
        try {
          const config = await this.$callPluginAction('my-plugin/load-config');
          if (config) {
            this.config = { ...this.config, ...config };
          }
        } catch (error) {
          console.error('加载配置失败:', error);
        }
      }
    }
  }
};

8. 完整插件示例:Analytics Plugin

8.1 插件结构

复制代码
vue-cli-plugin-analytics/
├── package.json
├── index.js
├── generator.js
├── prompts.js
├── commands/
│   └── analytics.js
├── templates/
│   ├── src/
│   │   ├── plugins/
│   │   │   └── analytics.js
│   │   └── components/
│   │       └── AnalyticsDashboard.vue
│   └── tests/
│       └── e2e/
│           └── analytics.spec.js
└── README.md

8.2 Service Plugin 实现

javascript 复制代码
// index.js
module.exports = (api, options) => {
  const analyticsOptions = options.pluginOptions && options.pluginOptions.analytics 
    ? options.pluginOptions.analytics 
    : {};

  // 注册分析命令
  api.registerCommand(
    'analytics',
    {
      description: '生成和分析项目数据',
      usage: 'vue-cli-service analytics [options]',
      options: {
        '--report': '生成详细报告',
        '--export [format]': '导出数据 (json|csv)',
        '--watch': '监听文件变化'
      }
    },
    (args) => {
      require('./commands/analytics')(args, api, options);
    }
  );

  // 添加环境变量
  if (analyticsOptions.trackingId) {
    process.env.VUE_APP_ANALYTICS_TRACKING_ID = analyticsOptions.trackingId;
  }

  // 修改 webpack 配置
  api.chainWebpack((webpackConfig) => {
    // 添加分析插件
    if (process.env.NODE_ENV === 'production' && analyticsOptions.bundleAnalyzer) {
      const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
      webpackConfig
        .plugin('bundle-analyzer')
        .use(BundleAnalyzerPlugin, [{
          analyzerMode: 'static',
          openAnalyzer: false
        }]);
    }

    // 添加自定义 loader 用于分析
    webpackConfig.module
      .rule('analytics')
      .test(/\.vue$/)
      .pre()
      .use('analytics')
      .loader(require.resolve('./loaders/analytics-loader'))
      .options(analyticsOptions);
  });

  // 开发服务器配置
  api.configureDevServer((app) => {
    app.get('/_analytics/data', (req, res) => {
      res.json({
        project: {
          name: options.projectName,
          version: require(api.resolve('package.json')).version
        },
        components: getComponentStats(api),
        dependencies: getDependencyStats(api)
      });
    });
  });
};

function getComponentStats(api) {
  const fs = require('fs');
  const path = require('path');
  
  const componentsDir = api.resolve('./src/components');
  let components = [];
  
  if (fs.existsSync(componentsDir)) {
    components = fs.readdirSync(componentsDir)
      .filter(file => file.endsWith('.vue'))
      .map(file => {
        const filePath = path.join(componentsDir, file);
        const stats = fs.statSync(filePath);
        const content = fs.readFileSync(filePath, 'utf-8');
        
        return {
          name: file.replace('.vue', ''),
          size: stats.size,
          lines: content.split('\n').length,
          hasScript: content.includes('<script'),
          hasStyle: content.includes('<style')
        };
      });
  }
  
  return components;
}

function getDependencyStats(api) {
  const packageJson = require(api.resolve('package.json'));
  
  return {
    dependencies: Object.keys(packageJson.dependencies || {}).length,
    devDependencies: Object.keys(packageJson.devDependencies || {}).length,
    total: Object.keys(packageJson.dependencies || {}).length + 
           Object.keys(packageJson.devDependencies || {}).length
  };
}

8.3 分析命令实现

javascript 复制代码
// commands/analytics.js
const chalk = require('chalk');
const { log, error, warn, info } = require('@vue/cli-shared-utils');
const fs = require('fs');
const path = require('path');

module.exports = (args, api, options) => {
  const projectRoot = api.resolve('.');
  
  log(`${chalk.cyan('Vue CLI Analytics')} - 项目分析工具\n`);

  try {
    // 收集项目数据
    const projectData = collectProjectData(projectRoot);
    
    // 显示基础信息
    displayBasicInfo(projectData);
    
    // 生成报告
    if (args.report) {
      generateReport(projectData, args);
    }
    
    // 导出数据
    if (args.export) {
      exportData(projectData, args.export);
    }

  } catch (err) {
    error(`分析失败: ${err.message}`);
    process.exit(1);
  }
};

function collectProjectData(projectRoot) {
  const packageJson = require(path.join(projectRoot, 'package.json'));
  
  // 收集组件信息
  const components = collectComponents(projectRoot);
  
  // 收集路由信息
  const routes = collectRoutes(projectRoot);
  
  // 收集依赖信息
  const dependencies = collectDependencies(packageJson);
  
  return {
    project: {
      name: packageJson.name,
      version: packageJson.version,
      description: packageJson.description
    },
    components,
    routes,
    dependencies,
    stats: {
      totalComponents: components.length,
      totalRoutes: routes.length,
      totalDependencies: dependencies.total
    }
  };
}

function collectComponents(projectRoot) {
  const componentsDir = path.join(projectRoot, 'src/components');
  const components = [];
  
  if (fs.existsSync(componentsDir)) {
    const walk = (dir) => {
      const files = fs.readdirSync(dir);
      
      files.forEach(file => {
        const filePath = path.join(dir, file);
        const stat = fs.statSync(filePath);
        
        if (stat.isDirectory()) {
          walk(filePath);
        } else if (file.endsWith('.vue')) {
          const content = fs.readFileSync(filePath, 'utf-8');
          const lines = content.split('\n').length;
          
          components.push({
            name: file.replace('.vue', ''),
            path: path.relative(projectRoot, filePath),
            size: stat.size,
            lines,
            hasScript: content.includes('<script'),
            hasStyle: content.includes('<style'),
            hasTemplate: content.includes('<template')
          });
        }
      });
    };
    
    walk(componentsDir);
  }
  
  return components;
}

function displayBasicInfo(data) {
  info(`项目: ${chalk.green(data.project.name)} v${data.project.version}`);
  info(`组件数量: ${chalk.blue(data.stats.totalComponents)}`);
  info(`路由数量: ${chalk.blue(data.stats.totalRoutes)}`);
  info(`依赖数量: ${chalk.blue(data.stats.totalDependencies)}`);
  
  // 显示最大的组件
  const largestComponent = data.components
    .sort((a, b) => b.size - a.size)[0];
  
  if (largestComponent) {
    warn(`最大的组件: ${chalk.yellow(largestComponent.name)} (${(largestComponent.size / 1024).toFixed(2)} KB)`);
  }
}

9. 插件测试和调试

9.1 本地测试配置

json 复制代码
// package.json - 测试脚本
{
  "scripts": {
    "test:unit": "jest",
    "test:e2e": "cypress run",
    "test:integration": "node test/integration.js",
    "dev": "node test/dev-server.js",
    "link:local": "npm link && cd test-project && npm link vue-cli-plugin-my-plugin"
  },
  "devDependencies": {
    "jest": "^27.0.0",
    "cypress": "^9.0.0",
    "@vue/test-utils": "^2.0.0"
  }
}

9.2 集成测试示例

javascript 复制代码
// test/integration.test.js
const { runCLI } = require('@vue/cli-test-utils');
const fs = require('fs');
const path = require('path');
const { execSync } = require('child_process');

describe('vue-cli-plugin-my-plugin', () => {
  const projectName = 'test-project';
  const projectPath = path.join(__dirname, '..', projectName);
  
  beforeAll(async () => {
    // 创建测试项目
    await runCLI(['create', projectName, '--preset', 'default'], {
      cwd: path.join(__dirname, '..')
    });
  });
  
  afterAll(() => {
    // 清理测试项目
    if (fs.existsSync(projectPath)) {
      fs.rmSync(projectPath, { recursive: true });
    }
  });
  
  test('插件安装成功', async () => {
    // 安装插件
    await runCLI(['add', 'vue-cli-plugin-my-plugin'], {
      cwd: projectPath
    });
    
    // 检查文件是否生成
    const pluginFile = path.join(projectPath, 'src/plugins/my-plugin.js');
    expect(fs.existsSync(pluginFile)).toBe(true);
    
    // 检查 package.json 是否更新
    const packageJson = require(path.join(projectPath, 'package.json'));
    expect(packageJson.dependencies).toHaveProperty('axios');
  });
  
  test('自定义命令工作正常', async () => {
    const result = await runCLI(['my-command'], {
      cwd: projectPath
    });
    
    expect(result.stdout).toContain('My Plugin Command');
  });
});

10. 插件发布和维护

10.1 发布准备

json 复制代码
// package.json - 发布配置
{
  "name": "vue-cli-plugin-my-plugin",
  "version": "1.0.0",
  "description": "A feature-rich Vue CLI plugin for analytics and project management",
  "keywords": [
    "vue",
    "vue-cli",
    "plugin",
    "analytics",
    "project-management"
  ],
  "homepage": "https://github.com/your-username/vue-cli-plugin-my-plugin#readme",
  "bugs": {
    "url": "https://github.com/your-username/vue-cli-plugin-my-plugin/issues"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/your-username/vue-cli-plugin-my-plugin.git"
  },
  "license": "MIT",
  "author": "Your Name <your.email@example.com>",
  "files": [
    "index.js",
    "generator.js",
    "prompts.js",
    "ui.js",
    "commands",
    "templates",
    "README.md"
  ],
  "peerDependencies": {
    "@vue/cli-service": "^3.0.0 || ^4.0.0 || ^5.0.0"
  },
  "devDependencies": {
    "@vue/cli-service": "^5.0.0",
    "jest": "^27.0.0"
  },
  "engines": {
    "node": ">=12.0.0"
  }
}

10.2 文档和示例

markdown 复制代码
# Vue CLI Plugin My Plugin

一个功能丰富的 Vue CLI 插件,提供项目分析、工具集成和开发效率提升功能。

## 功能特性

- 📊 项目分析和统计
- 🔧 自动化工具配置
- 🎨 主题和样式管理
- 📈 性能监控和优化
- 🔌 可扩展的插件架构

## 安装

```bash
vue add my-plugin

配置

vue.config.js 中配置插件选项:

javascript 复制代码
module.exports = {
  pluginOptions: {
    myPlugin: {
      apiBaseUrl: '/api',
      enableDebug: true,
      themeColor: 'blue'
    }
  }
}

使用

命令行使用

bash 复制代码
# 运行分析
vue-cli-service analytics --report

# 导出数据
vue-cli-service analytics --export json

# 自定义命令
vue-cli-service my-command --debug

在代码中使用

javascript 复制代码
// 在 Vue 组件中
export default {
  mounted() {
    if (this.$myPlugin) {
      this.$myPlugin.request.get('/data')
        .then(response => {
          console.log('Data:', response.data);
        });
    }
  }
}

API 参考

Plugin Options

  • apiBaseUrl (String): API 基础 URL
  • enableDebug (Boolean): 启用调试模式
  • themeColor (String): 主题颜色

Global Methods

  • $myPlugin.request: Axios 实例
  • $myPlugin.deepClone: 深度克隆工具
  • $myPlugin.debounce: 防抖函数

许可证

MIT

复制代码
## 11. 总结

通过本文的详细讲解,您应该已经掌握了:

1. **Vue CLI 插件架构**:理解 Service Plugin、Generator、Prompts 的核心概念
2. **插件开发流程**:从环境搭建到发布维护的完整流程
3. **高级功能实现**:UI 集成、自定义命令、模板渲染等
4. **实战经验**:通过完整示例学习实际开发技巧
5. **最佳实践**:测试、调试、文档和维护的最佳方法

Vue CLI 插件生态系统为 Vue.js 开发提供了强大的扩展能力,掌握插件开发技能将极大提升您的开发效率和项目质量。

---

**扩展学习资源:**
- [Vue CLI 官方文档](https://cli.vuejs.org/)
- [Plugin Development Guide](https://cli.vuejs.org/dev-guide/plugin-dev.html)
- [Webpack Configuration](https://webpack.js.org/configuration/)
- [Node.js API Documentation](https://nodejs.org/api/)

开始创建您自己的 Vue CLI 插件,为 Vue.js 生态系统贡献力量!
相关推荐
小蜜蜂dry3 小时前
JavaScript 原型
前端·javascript
用户90443816324603 小时前
前端也能玩 AI?用 brain.js 在浏览器里训个 "前后端分类大师",后端同事看了都沉默!
前端
祈祷苍天赐我java之术3 小时前
什么是Nginx?:掌握高性能 Web 服务器核心技术
服务器·前端·nginx
Achieve前端实验室4 小时前
【每日一面】async/await 的原理
前端·javascript·面试
姜至4 小时前
el-calendar实现自定义展示效果
前端·vue.js
烛阴4 小时前
Lua中的三个点(...):解锁函数参数的无限可能
前端·lua
拉不动的猪4 小时前
webpack分包优化简单分析
前端·vue.js·webpack
德莱厄斯4 小时前
没开玩笑,全框架支持的 dialog 组件,支持响应式
前端·javascript·github
非凡ghost4 小时前
Affinity Photo(图像编辑软件) 多语便携版
前端·javascript·后端