Skill: baozaibuding/vue-component-analyzer (version 1.0.0)

--- name: vue-component-analyzer description: 分析 Vue 前端项目的组件依赖关系，从入口文件开始递归分析所有组件，生成组件层级关系图。支持 Vue 2 和 Vue 3 项目，输出包含组件名称、文件路径和所需属性。使用当用户需要分析 Vue 组件结构、排查组件依赖关系或理解项目架构时。 --- # Vue 组件依赖分析器 ## 功能概述 分析 Vue 项目的组件依赖关系，从入口文件(main.js/main.ts/App.vue)开始递归分析，生成完整的组件层级关系图。 ## 支持特性 - Vue 2 和 Vue 3 项目兼容 - 支持 Options API 和 Composition API - 识别组件名称、文件路径、props 属性 - 多种输出格式：Markdown 树形、JSON、Mermaid 图表 ## 分析流程 1. **定位入口文件** - 查找 main.js / main.ts / app.js / app.ts - 定位根组件 App.vue 2. **递归分析组件** - 解析 `<template>` 中的组件引用 - 提取 `import` 语句中的组件依赖 - 识别 `components` 选项中注册的组件 - 递归分析子组件 3. **提取组件信息** - 组件名称（name 属性或文件名） - 组件文件绝对/相对路径 - Props 定义（名称、类型、是否必填、默认值） 4. **生成输出** - Markdown 树形结构 - JSON 结构化数据 - Mermaid 流程图 ## 输出格式 ### 1. Markdown 树形结构 ``` 📦 App (src/App.vue) ├── 📁 components/ │ ├── 📄 Header (src/components/Header.vue) │ │ ├── props: title(String), showLogo(Boolean) │ │ └── 📄 UserAvatar (src/components/UserAvatar.vue) │ │ └── props: src(String), size(Number) │ └── 📄 Sidebar (src/components/Sidebar.vue) │ └── props: menuItems(Array), collapsed(Boolean) └── 📄 Footer (src/components/Footer.vue) └── props: copyright(String) ``` ### 2. JSON 格式 ```json { "name": "App", "path": "src/App.vue", "props": [], "children": [ { "name": "Header", "path": "src/components/Header.vue", "props": [ { "name": "title", "type": "String", "required": true }, { "name": "showLogo", "type": "Boolean", "default": true } ], "children": [...] } ] } ``` ### 3. Mermaid 图表 ```mermaid graph TD A[App<br/>src/App.vue] --> B[Header<br/>src/components/Header.vue] A --> C[Sidebar<br/>src/components/Sidebar.vue] B --> D[UserAvatar<br/>src/components/UserAvatar.vue] A --> E[Footer<br/>src/components/Footer.vue] ``` ## 使用方法 分析当前 Vue 项目的组件结构： 1. 确定项目根目录 2. 查找入口文件和根组件 3. 递归分析所有组件依赖 4. 生成三种格式的分析报告 ## 组件识别规则 ### Vue 2 - 识别 `components: { ComponentName }` 选项 - 解析 `import ComponentName from './path'` - 识别 `<component-name>` 和 `<ComponentName>` 标签 ### Vue 3 - 支持 `<script setup>` 语法 - 识别 `import ComponentName from './path'` - 解析动态组件 `<component :is="...">` ### Props 提取 - 识别 `props: ['prop1', 'prop2']` 数组形式 - 解析 `props: { prop1: String, prop2: { type: Object, required: true } }` 对象形式 - 支持 TypeScript 类型定义 ## 注意事项 - 忽略第三方库组件（如 Element UI、Ant Design Vue） - 处理循环依赖（A 引用 B，B 引用 A） - 支持别名路径解析（@/components/...） - 异步组件标记为 `(async)`

path: examples.md
content: # Vue 组件分析示例

## 示例 1：简单 Vue 3 项目

### 项目结构
```
src/
├── main.js
├── App.vue
└── components/
    ├── Header.vue
    ├── Sidebar.vue
    └── UserAvatar.vue
```

### App.vue
```vue
<template>
  <div id="app">
    <Header :title="pageTitle" :show-logo="true" />
    <Sidebar :menu-items="menus" :collapsed="isCollapsed" />
    <router-view />
  </div>
</template>

<script setup>
import { ref } from 'vue'
import Header from './components/Header.vue'
import Sidebar from './components/Sidebar.vue'

const pageTitle = ref('My App')
const menus = ref([...])
const isCollapsed = ref(false)
</script>
```

### Header.vue
```vue
<template>
  <header>
    <UserAvatar :src="userAvatar" :size="40" />
    <h1>{{ title }}</h1>
  </header>
</template>

<script setup>
import UserAvatar from './UserAvatar.vue'

defineProps({
  title: { type: String, required: true },
  showLogo: { type: Boolean, default: true }
})
</script>
```

### 分析输出

#### Markdown 树形
```
📦 App (src/App.vue)
├── 📄 Header (src/components/Header.vue)
│   ├── props: title(String, required), showLogo(Boolean, default: true)
│   └── 📄 UserAvatar (src/components/UserAvatar.vue)
│       └── props: src(String, required), size(Number, default: 32)
├── 📄 Sidebar (src/components/Sidebar.vue)
│   └── props: menuItems(Array, required), collapsed(Boolean, default: false)
└── 📄 RouterView (外部组件)
```

#### JSON
```json
{
  "name": "App",
  "path": "src/App.vue",
  "props": [],
  "children": [
    {
      "name": "Header",
      "path": "src/components/Header.vue",
      "props": [
        { "name": "title", "type": "String", "required": true },
        { "name": "showLogo", "type": "Boolean", "default": true }
      ],
      "children": [
        {
          "name": "UserAvatar",
          "path": "src/components/UserAvatar.vue",
          "props": [
            { "name": "src", "type": "String", "required": true },
            { "name": "size", "type": "Number", "default": 32 }
          ],
          "children": []
        }
      ]
    },
    {
      "name": "Sidebar",
      "path": "src/components/Sidebar.vue",
      "props": [
        { "name": "menuItems", "type": "Array", "required": true },
        { "name": "collapsed", "type": "Boolean", "default": false }
      ],
      "children": []
    }
  ]
}
```

#### Mermaid
```mermaid
graph TD
    A[App<br/>src/App.vue] --> B[Header<br/>src/components/Header.vue]
    A --> C[Sidebar<br/>src/components/Sidebar.vue]
    B --> D[UserAvatar<br/>src/components/UserAvatar.vue]
    
    style A fill:#f9f,stroke:#333
    style B fill:#bbf,stroke:#333
    style C fill:#bbf,stroke:#333
    style D fill:#bbf,stroke:#333
```

---

## 示例 2：Vue 2 Options API 项目

### App.vue
```vue
<template>
  <div id="app">
    <app-header :title="pageTitle"></app-header>
    <app-sidebar v-bind="sidebarProps"></app-sidebar>
  </div>
</template>

<script>
import AppHeader from './components/Header.vue'
import AppSidebar from './components/Sidebar.vue'

export default {
  name: 'App',
  components: {
    AppHeader,
    AppSidebar
  },
  data() {
    return {
      pageTitle: 'Vue 2 App'
    }
  },
  computed: {
    sidebarProps() {
      return {
        items: this.menuItems,
        collapsible: true
      }
    }
  }
}
</script>
```

### 分析输出

#### Markdown 树形
```
📦 App (src/App.vue)
├── 📄 AppHeader (src/components/Header.vue)
│   └── props: title(String)
└── 📄 AppSidebar (src/components/Sidebar.vue)
    └── props: items(Array), collapsible(Boolean)
```

---

## 示例 3：带异步组件的项目

### App.vue
```vue
<template>
  <div>
    <SyncComponent />
    <AsyncComponent v-if="showAsync" />
  </div>
</template>

<script>
import SyncComponent from './SyncComponent.vue'

export default {
  components: {
    SyncComponent,
    AsyncComponent: () => import('./AsyncComponent.vue')
  }
}
</script>
```

### 分析输出
```
📦 App (src/App.vue)
├── 📄 SyncComponent (src/components/SyncComponent.vue)
└── 📄 AsyncComponent (src/components/AsyncComponent.vue) (async)
```

---

## 示例 4：带循环依赖的处理

当 A.vue 引用 B.vue，B.vue 又引用 A.vue 时：

```
📦 ComponentA (src/components/A.vue)
└── 📄 ComponentB (src/components/B.vue)
    └── 📄 ComponentA (src/components/A.vue) [循环依赖 - 已截断]
```

JSON 中标记：
```json
{
  "name": "ComponentB",
  "path": "src/components/B.vue",
  "children": [
    {
      "name": "ComponentA",
      "path": "src/components/A.vue",
      "circular": true,
      "children": []
    }
  ]
}
```


path: implementation.md
content: # Vue 组件分析实现指南

## 分析步骤

### 步骤 1：定位入口文件

```javascript
// 可能的入口文件
const entryFiles = [
  'src/main.js',
  'src/main.ts',
  'src/app.js',
  'src/app.ts',
  'main.js',
  'main.ts'
]

// 读取入口文件，找到根组件导入
// 通常形式：import App from './App.vue'
```

### 步骤 2：解析 Vue 单文件组件

```javascript
// 解析 .vue 文件结构
const parseVueFile = (filePath) => {
  const content = fs.readFileSync(filePath, 'utf-8')
  
  // 提取 template 部分
  const templateMatch = content.match(/<template>([\s\S]*?)<\/template>/)
  const template = templateMatch ? templateMatch[1] : ''
  
  // 提取 script 部分
  const scriptMatch = content.match(/<script[^>]*>([\s\S]*?)<\/script>/)
  const script = scriptMatch ? scriptMatch[1] : ''
  
  return { template, script }
}
```

### 步骤 3：识别组件引用

#### Vue 3 `<script setup>` 方式
```javascript
// 识别导入的组件
const importRegex = /import\s+(\w+)\s+from\s+['"]([^'"]+)['"]/g
// 匹配：import ComponentName from './path'

// 识别 template 中使用的组件
// 通过解析 template 中的标签名，与导入的组件名匹配
```

#### Vue 2 / Options API 方式
```javascript
// 识别 components 选项
const componentsRegex = /components:\s*\{([^}]+)\}/
// 匹配：components: { ComponentName }

// 识别 import 语句（同上）
```

### 步骤 4：提取 Props 定义

```javascript
// 数组形式：props: ['prop1', 'prop2']
const arrayPropsRegex = /props:\s*\[([^\]]+)\]/

// 对象形式
const objectPropsRegex = /props:\s*\{([^}]+)\}/

// defineProps (Vue 3)
const definePropsRegex = /defineProps\(([^)]+)\)/

// 解析每个 prop 的详细信息
const parseProp = (propString) => {
  // 提取名称、类型、required、default
}
```

### 步骤 5：递归分析

```javascript
const analyzeComponent = (filePath, visited = new Set()) => {
  // 防止循环依赖
  if (visited.has(filePath)) {
    return { circular: true, path: filePath }
  }
  visited.add(filePath)
  
  const { template, script } = parseVueFile(filePath)
  const imports = extractImports(script)
  const usedComponents = extractUsedComponents(template, imports)
  const props = extractProps(script)
  
  const children = usedComponents.map(comp => {
    const childPath = resolvePath(comp.path, filePath)
    return analyzeComponent(childPath, new Set(visited))
  })
  
  return {
    name: extractComponentName(script, filePath),
    path: filePath,
    props,
    children
  }
}
```

### 步骤 6：路径解析

```javascript
// 处理别名路径
const resolvePath = (importPath, currentFile) => {
  // @/components/Foo -> src/components/Foo.vue
  // ./Foo -> relative to current file
  // ../Foo -> relative to parent
  
  if (importPath.startsWith('@/')) {
    return importPath.replace('@/', 'src/') + '.vue'
  }
  
  if (importPath.startsWith('./') || importPath.startsWith('../')) {
    return path.resolve(path.dirname(currentFile), importPath) + '.vue'
  }
  
  return importPath
}
```

## 输出生成

### Markdown 树形
```javascript
const generateMarkdown = (tree, level = 0) => {
  const indent = '│   '.repeat(level)
  const prefix = level === 0 ? '📦' : (level === 1 ? '├──' : '│   └──')
  
  let result = `${indent}${prefix} ${tree.name} (${tree.path})\n`
  
  if (tree.props && tree.props.length > 0) {
    const propsStr = tree.props.map(p => {
      let str = `${p.name}(${p.type})`
      if (p.required) str += ', required'
      if (p.default !== undefined) str += `, default: ${p.default}`
      return str
    }).join(', ')
    result += `${indent}│   └── props: ${propsStr}\n`
  }
  
  if (tree.children) {
    tree.children.forEach(child => {
      result += generateMarkdown(child, level + 1)
    })
  }
  
  return result
}
```

### Mermaid 图表
```javascript
const generateMermaid = (tree, nodes = [], edges = []) => {
  const nodeId = tree.name.replace(/[^a-zA-Z0-9]/g, '_')
  nodes.push(`${nodeId}[${tree.name}<br/>${tree.path}]`)
  
  if (tree.children) {
    tree.children.forEach(child => {
      const childId = child.name.replace(/[^a-zA-Z0-9]/g, '_')
      edges.push(`${nodeId} --> ${childId}`)
      generateMermaid(child, nodes, edges)
    })
  }
  
  return `graph TD\n    ${[...nodes, ...edges].join('\n    ')}`
}
```

## 边界情况处理

1. **第三方组件**：路径包含 node_modules 或已知名称（如 ElButton）时跳过
2. **动态组件**：`<component :is="dynamicComponent">` 标记为动态
3. **递归组件**：组件引用自身时标记为 recursive
4. **未找到文件**：组件路径无法解析时标记为 missing
5. **TypeScript**：支持 .vue 文件中的 TS 类型定义解析


Security scan verdicts — ClawScan: clean | static analysis: clean | VirusTotal: clean | SkillSpector: clean (LOW)