Skip to content

Vite Vue3 Chrome 浏览器插件开发完整教程

📚 教程概述

本教程将带你从零开始构建一个功能完整的 Chrome 浏览器插件,使用现代化的技术栈:Vue3 + Vite + Chrome Extension API。

🎯 学习目标

  • 掌握 Chrome 插件开发基础
  • 学会使用 Vue3 构建插件界面
  • 理解 Vite 构建工具配置
  • 掌握模块化架构设计
  • 学会调试和部署插件

🛠️ 技术栈

  • 前端框架: Vue3 + Vue Router
  • 构建工具: Vite
  • 开发语言: JavaScript (ES6+)
  • 样式: CSS3
  • API: Chrome Extension APIs

🚀 第一章:项目初始化

1.1 创建项目结构

bash
# 创建项目目录
$ mkdir vue3-chrome-extension
$ cd vue3-chrome-extension

# 初始化 npm 项目
$ npm init -y
$ pnpm init

1.2 安装依赖

bash
# 安装 Vue3 和相关依赖
$ pnpm add vue vue-router

# 安装开发依赖
$ pnpm add -D vite @vitejs/plugin-vue

1.3 项目目录结构

bash
vue3-chrome-extension/
├── src/                    # 源代码目录
   ├── views/             # Vue 页面组件
   ├── Home.vue       # 首页
   ├── Tools.vue      # 工具页
   ├── Settings.vue   # 设置页
   └── About.vue      # 关于页
   ├── router/            # 路由配置
   └── index.js       # 路由定义
   ├── content/           # Content Script 模块
   ├── index.js       # 主入口
   ├── messageHandler.js  # 消息处理
   ├── linkManager.js     # 链接管理
   ├── styleManager.js    # 样式管理
   ├── analysisTools.js   # 分析工具
   └── storageManager.js  # 存储管理
   ├── App.vue            # 主应用组件
   ├── popup.js           # 弹窗入口
   └── background.js      # 后台脚本
├── dist/                  # 构建输出目录
├── manifest.json          # 插件配置文件
├── popup.html            # 弹窗 HTML
├── package.json          # 项目配置
└── vite.config.js        # Vite 配置

📋 第二章:核心配置文件

2.1 manifest.json - 插件配置文件

json
{
  "manifest_version": 3,
  "name": "Vue3 Chrome Extension",
  "version": "1.0.0",
  "description": "基于Vue3和Vite构建的Chrome插件",

  // 权限配置 - 定义插件需要的权限
  "permissions": [
    "activeTab",      // 访问当前活动标签页
    "storage",        // 使用 Chrome 存储 API
    "contextMenus"    // 创建右键菜单
  ],

  // 插件图标和弹窗配置
  "action": {
    "default_popup": "popup.html",
    "default_title": "Vue3 Chrome Extension"
  },

  // Content Script 配置 - 注入到网页的脚本
  "content_scripts": [
    {
      "matches": ["<all_urls>"],  // 匹配所有网址
      "js": ["content.js"]        // 注入的脚本文件
    }
  ],

  // 后台脚本配置 - Service Worker
  "background": {
    "service_worker": "background.js"
  },

  // 网页可访问的资源
  "web_accessible_resources": [
    {
      "resources": ["assets/*"],
      "matches": ["<all_urls>"]
    }
  ]
}

配置说明:

  • manifest_version: 3: 使用最新的 Manifest V3 规范
  • permissions: 定义插件所需权限,遵循最小权限原则
  • action: 配置插件图标点击行为
  • content_scripts: 配置注入到网页的脚本
  • background: 配置后台服务工作者

2.2 vite.config.js - 构建配置

js
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import fs from 'fs'
import path from 'path'

// 自定义插件:复制 manifest.json 到构建目录
function copyManifest() {
  return {
    name: 'copy-manifest',
    // 在构建完成后执行
    closeBundle() {
      const manifestSrc = path.resolve(__dirname, 'manifest.json')
      const manifestDest = path.resolve(__dirname, 'dist/manifest.json')
      
      // 复制文件
      fs.copyFileSync(manifestSrc, manifestDest)
      console.log('✓ manifest.json 已复制到 dist 目录')
    }
  }
}

export default defineConfig({
  // Vue 插件配置
  plugins: [
    vue(),           // Vue3 支持
    copyManifest()   // 自定义复制插件
  ],

  // 构建配置
  build: {
    // Rollup 构建选项
    rollupOptions: {
      // 多入口配置 - 为不同的脚本定义入口点
      input: {
        popup: 'popup.html',              // 弹窗页面
        background: 'src/background.js',   // 后台脚本
        content: 'src/content/index.js'    // 内容脚本
      },
      // 输出配置
      output: {
        // 入口文件命名格式
        entryFileNames: '[name].js',
        // 代码分割文件命名格式
        chunkFileNames: '[name].js',
        // 资源文件命名格式
        assetFileNames: '[name].[ext]'
      }
    },
    // 输出目录
    outDir: 'dist'
  }
})

配置原理解析:

多入口配置: Chrome 插件需要多个独立的 JavaScript 文件

  • popup: 弹窗界面入口
  • background: 后台服务工作者
  • content: 内容脚本入口
  • 输出文件命名: 确保生成的文件名与 manifest.json 中的配置一致
  • 自定义插件: 自动复制 manifest.json 到构建目录,简化构建流程

2.3 package.json - 项目配置

json
{
  "name": "vue3-chrome-extension",
  "version": "1.0.0",
  "type": "module",
  "scripts": {
    "dev": "vite",
    "build": "vite build",
    "preview": "vite preview"
  },
  "dependencies": {
    "vue": "^3.3.0",
    "vue-router": "^4.2.0"
  },
  "devDependencies": {
    "@vitejs/plugin-vue": "^4.2.0",
    "vite": "^4.5.0"
  }
}

🎨 第三章:Vue3 界面开发 3.1 popup.html - 弹窗页面

html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Vue3 Chrome Extension</title>
  <style>
    /* 弹窗基础样式 */
    body {
      margin: 0;
      padding: 0;
      width: 350px;      /* 固定宽度 */
      height: 500px;     /* 固定高度 */
      font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
    }

    /* 防止内容溢出 */
    #app {
      width: 100%;
      height: 100%;
      overflow: hidden;
    }
  </style>
</head>
<body>
  <!-- Vue 应用挂载点 -->
  <div id="app"></div>

  <!-- 引入 Vue 应用脚本 -->
  <script type="module" src="/src/popup.js"></script>
</body>
</html>

3.2 src/popup.js - Vue 应用入口

js
import { createApp } from 'vue'
import { createRouter, createWebHashHistory } from 'vue-router'
import App from './App.vue'
import router from './router/index.js'

// 创建 Vue 应用实例
const app = createApp(App)

// 使用路由 - 注意:Chrome 插件中必须使用 Hash 模式
app.use(router)

// 挂载应用到 DOM
app.mount('#app')

// 调试信息
console.log('Vue3 Chrome Extension Popup 已启动')

关键点说明:

使用 createWebHashHistory 而不是 createWebHistory Chrome 插件环境不支持 HTML5 History API Hash 模式确保路由在插件环境中正常工作 3.3 src/App.vue - 主应用组件

vue
<template>
  <div class="app">
    <!-- 导航栏 -->
    <nav class="nav">
      <router-link 
        v-for="route in routes" 
        :key="route.path"
        :to="route.path" 
        class="nav-item"
        :class="{ active: $route.path === route.path }"
      >
        <span class="nav-icon">{{ route.icon }}</span>
        <span class="nav-text">{{ route.name }}</span>
      </router-link>
    </nav>

    <!-- 页面内容区域 -->
    <main class="content">
      <!-- Vue Router 视图 -->
      <router-view />
    </main>
  </div>
</template>

<script>
export default {
  name: 'App',
  data() {
    return {
      // 路由配置数据
      routes: [
        { path: '/', name: '首页', icon: '🏠' },
        { path: '/tools', name: '工具', icon: '🔧' },
        { path: '/settings', name: '设置', icon: '⚙️' },
        { path: '/about', name: '关于', icon: 'ℹ️' }
      ]
    }
  }
}
</script>

<style scoped>
/* 应用容器 */
.app {
  display: flex;
  flex-direction: column;
  height: 100vh;
  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
}

/* 导航栏样式 */
.nav {
  display: flex;
  background: rgba(255, 255, 255, 0.1);
  backdrop-filter: blur(10px);
  border-bottom: 1px solid rgba(255, 255, 255, 0.2);
  padding: 8px;
}

/* 导航项样式 */
.nav-item {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  padding: 8px 4px;
  text-decoration: none;
  color: rgba(255, 255, 255, 0.8);
  border-radius: 8px;
  transition: all 0.3s ease;
  font-size: 12px;
}

/* 导航项悬停效果 */
.nav-item:hover {
  background: rgba(255, 255, 255, 0.1);
  color: white;
  transform: translateY(-2px);
}

/* 激活状态 */
.nav-item.active {
  background: rgba(255, 255, 255, 0.2);
  color: white;
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}

/* 图标样式 */
.nav-icon {
  font-size: 16px;
  margin-bottom: 4px;
}

/* 内容区域 */
.content {
  flex: 1;
  overflow-y: auto;
  padding: 16px;
}

/* 滚动条样式 */
.content::-webkit-scrollbar {
  width: 6px;
}

.content::-webkit-scrollbar-track {
  background: rgba(255, 255, 255, 0.1);
  border-radius: 3px;
}

.content::-webkit-scrollbar-thumb {
  background: rgba(255, 255, 255, 0.3);
  border-radius: 3px;
}

.content::-webkit-scrollbar-thumb:hover {
  background: rgba(255, 255, 255, 0.5);
}
</style>

3.4 路由配置 - src/router/index.js

js
import { createRouter, createWebHashHistory } from 'vue-router'

// 导入页面组件
import Home from '../views/Home.vue'
import Tools from '../views/Tools.vue'
import Settings from '../views/Settings.vue'
import About from '../views/About.vue'

// 路由配置
const routes = [
  {
    path: '/',
    name: 'Home',
    component: Home,
    meta: { title: '首页' }
  },
  {
    path: '/tools',
    name: 'Tools', 
    component: Tools,
    meta: { title: '工具' }
  },
  {
    path: '/settings',
    name: 'Settings',
    component: Settings,
    meta: { title: '设置' }
  },
  {
    path: '/about',
    name: 'About',
    component: About,
    meta: { title: '关于' }
  }
]

// 创建路由实例
const router = createRouter({
  // 使用 Hash 模式 - Chrome 插件必须使用此模式
  history: createWebHashHistory(),
  routes
})

// 路由守卫 - 设置页面标题
router.beforeEach((to, from, next) => {
  // 更新文档标题
  if (to.meta.title) {
    document.title = `${to.meta.title} - Vue3 Chrome Extension`
  }
  next()
})

export default router

路由配置要点:

Hash 模式: Chrome 插件环境限制,必须使用 Hash 路由 元信息: 使用 meta 字段存储页面标题等信息 路由守卫: 实现页面切换时的逻辑处理 🔧 第四章:页面组件开发 4.1 首页组件 - src/views/Home.vue

vue
<template>
  <div class="home">
    <!-- 页面标题 -->
    <h2 class="title">🏠 插件首页</h2>

    <!-- 当前页面信息卡片 -->
    <div class="card">
      <h3>📄 当前页面信息</h3>
      <div class="info-item">
        <strong>标题:</strong> 
        <span>{{ pageInfo.title || '获取中...' }}</span>
      </div>
      <div class="info-item">
        <strong>URL:</strong> 
        <span class="url">{{ pageInfo.url || '获取中...' }}</span>
      </div>
      <button @click="refreshPageInfo" class="btn btn-primary">
        🔄 刷新信息
      </button>
    </div>
    
    <!-- 快速操作区域 -->
    <div class="card">
      <h3>⚡ 快速操作</h3>
      <div class="quick-actions">
        <button 
          @click="highlightLinks" 
          class="btn btn-success"
          :disabled="loading.highlight"
        >
          {{ loading.highlight ? '处理中...' : '🔗 高亮链接' }}
        </button>
        
        <button 
          @click="removeHighlight" 
          class="btn btn-warning"
          :disabled="loading.remove"
        >
          {{ loading.remove ? '处理中...' : '🚫 移除高亮' }}
        </button>
        
        <button 
          @click="getPageStats" 
          class="btn btn-info"
          :disabled="loading.stats"
        >
          {{ loading.stats ? '分析中...' : '📊 页面统计' }}
        </button>
      </div>
    </div>
    
    <!-- 页面统计信息 -->
    <div v-if="pageStats" class="card">
      <h3>📊 页面统计</h3>
      <div class="stats-grid">
        <div class="stat-item">
          <span class="stat-label">链接数量</span>
          <span class="stat-value">{{ pageStats.linkCount }}</span>
        </div>
        <div class="stat-item">
          <span class="stat-label">图片数量</span>
          <span class="stat-value">{{ pageStats.imageCount }}</span>
        </div>
        <div class="stat-item">
          <span class="stat-label">文本长度</span>
          <span class="stat-value">{{ pageStats.textLength }}</span>
        </div>
        <div class="stat-item">
          <span class="stat-label">标题数量</span>
          <span class="stat-value">{{ pageStats.headingCount }}</span>
        </div>
      </div>
    </div>
  </div>
</template>

<script>
export default {
  name: 'Home',
  data() {
    return {
      // 页面信息
      pageInfo: {
        title: '',
        url: ''
      },
      // 页面统计数据
      pageStats: null,
      // 加载状态
      loading: {
        highlight: false,
        remove: false,
        stats: false
      }
    }
  },

  // 组件挂载时获取页面信息
  mounted() {
    this.getPageInfo()
  },

  methods: {
    // 获取当前页面信息
    async getPageInfo() {
      try {
        // 向 background script 发送消息
        const response = await chrome.runtime.sendMessage({
          action: 'getTabInfo'
        })
        
        if (response) {
          this.pageInfo = response
        }
      } catch (error) {
        console.error('获取页面信息失败:', error)
      }
    },
    
    // 刷新页面信息
    refreshPageInfo() {
      this.getPageInfo()
    },
    
    // 高亮页面链接
    async highlightLinks() {
      this.loading.highlight = true
      try {
        // 获取当前活动标签页
        const [tab] = await chrome.tabs.query({ 
          active: true, 
          currentWindow: true 
        })
        
        // 向 content script 发送消息
        await chrome.tabs.sendMessage(tab.id, {
          action: 'highlightLinks'
        })
        
        console.log('链接高亮完成')
      } catch (error) {
        console.error('高亮链接失败:', error)
      } finally {
        this.loading.highlight = false
      }
    },
    
    // 移除高亮
    async removeHighlight() {
      this.loading.remove = true
      try {
        const [tab] = await chrome.tabs.query({ 
          active: true, 
          currentWindow: true 
        })
        
        await chrome.tabs.sendMessage(tab.id, {
          action: 'removeHighlight'
        })
        
        console.log('高亮移除完成')
      } catch (error) {
        console.error('移除高亮失败:', error)
      } finally {
        this.loading.remove = false
      }
    },
    
    // 获取页面统计信息
    async getPageStats() {
      this.loading.stats = true
      try {
        const [tab] = await chrome.tabs.query({ 
          active: true, 
          currentWindow: true 
        })
        
        const response = await chrome.tabs.sendMessage(tab.id, {
          action: 'getPageStats'
        })
        
        if (response) {
          this.pageStats = response
        }
      } catch (error) {
        console.error('获取页面统计失败:', error)
      } finally {
        this.loading.stats = false
      }
    }
  }
}
</script>

<style scoped>
/* 首页容器 */
.home {
  color: white;
}

/* 页面标题 */
.title {
  text-align: center;
  margin-bottom: 20px;
  font-size: 24px;
  font-weight: 600;
}

/* 卡片样式 */
.card {
  background: rgba(255, 255, 255, 0.1);
  backdrop-filter: blur(10px);
  border-radius: 12px;
  padding: 16px;
  margin-bottom: 16px;
  border: 1px solid rgba(255, 255, 255, 0.2);
}

.card h3 {
  margin: 0 0 12px 0;
  font-size: 16px;
  font-weight: 600;
}

/* 信息项样式 */
.info-item {
  margin-bottom: 8px;
  font-size: 14px;
}

.info-item strong {
  color: rgba(255, 255, 255, 0.9);
}

.url {
  word-break: break-all;
  font-family: monospace;
  font-size: 12px;
}

/* 按钮样式 */
.btn {
  padding: 8px 16px;
  border: none;
  border-radius: 6px;
  cursor: pointer;
  font-size: 12px;
  font-weight: 500;
  transition: all 0.3s ease;
  margin: 4px;
}

.btn:hover {
  transform: translateY(-1px);
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
}

.btn:disabled {
  opacity: 0.6;
  cursor: not-allowed;
  transform: none;
}

/* 按钮颜色变体 */
.btn-primary {
  background: #007bff;
  color: white;
}

.btn-success {
  background: #28a745;
  color: white;
}

.btn-warning {
  background: #ffc107;
  color: #212529;
}

.btn-info {
  background: #17a2b8;
  color: white;
}

/* 快速操作区域 */
.quick-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}

/* 统计信息网格 */
.stats-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 12px;
}

.stat-item {
  display: flex;
  flex-direction: column;
  align-items: center;
  padding: 12px;
  background: rgba(255, 255, 255, 0.1);
  border-radius: 8px;
}

.stat-label {
  font-size: 12px;
  color: rgba(255, 255, 255, 0.8);
  margin-bottom: 4px;
}

.stat-value {
  font-size: 18px;
  font-weight: 600;
  color: #ffc107;
}
</style>

4.2 工具页面 - src/views/Tools.vue

vue
<template>
  <div class="tools">
    <h2 class="title">🔧 页面工具</h2>

    <!-- 样式工具 -->
    <div class="card">
      <h3>🎨 页面美化</h3>
      <div class="tool-grid">
        <button @click="toggleDarkMode" class="tool-btn">
          🌙 暗黑模式
        </button>
        <button @click="toggleReadingMode" class="tool-btn">
          📖 阅读模式
        </button>
        <button @click="increaseFontSize" class="tool-btn">
          🔍 增大字体
        </button>
        <button @click="resetStyles" class="tool-btn">
          🔄 重置样式
        </button>
      </div>
    </div>
    
    <!-- 分析工具 -->
    <div class="card">
      <h3>📊 页面分析</h3>
      <div class="tool-grid">
        <button @click="analyzeImages" class="tool-btn">
          🖼️ 图片分析
        </button>
        <button @click="analyzePerformance" class="tool-btn">
          ⚡ 性能分析
        </button>
        <button @click="extractLinks" class="tool-btn">
          🔗 提取链接
        </button>
        <button @click="checkAccessibility" class="tool-btn">
          ♿ 可访问性
        </button>
      </div>
    </div>
    
    <!-- 结果显示区域 -->
    <div v-if="analysisResult" class="card">
      <h3>📋 分析结果</h3>
      <div class="result-content">
        <pre>{{ JSON.stringify(analysisResult, null, 2) }}</pre>
      </div>
    </div>
  </div>
</template>

<script>
export default {
  name: 'Tools',
  data() {
    return {
      analysisResult: null
    }
  },

  methods: {
    // 发送消息到 content script
    async sendMessage(action, data = {}) {
      try {
        const [tab] = await chrome.tabs.query({ 
          active: true, 
          currentWindow: true 
        })
        
        const response = await chrome.tabs.sendMessage(tab.id, {
          action,
          ...data
        })
        
        return response
      } catch (error) {
        console.error(`执行 ${action} 失败:`, error)
        return null
      }
    },
    
    // 切换暗黑模式
    async toggleDarkMode() {
      await this.sendMessage('toggleDarkMode')
    },
    
    // 切换阅读模式
    async toggleReadingMode() {
      await this.sendMessage('toggleReadingMode')
    },
    
    // 增大字体
    async increaseFontSize() {
      await this.sendMessage('increaseFontSize')
    },
    
    // 重置样式
    async resetStyles() {
      await this.sendMessage('resetStyles')
    },
    
    // 分析图片
    async analyzeImages() {
      const result = await this.sendMessage('analyzeImages')
      this.analysisResult = result
    },
    
    // 性能分析
    async analyzePerformance() {
      const result = await this.sendMessage('analyzePerformance')
      this.analysisResult = result
    },
    
    // 提取链接
    async extractLinks() {
      const result = await this.sendMessage('extractLinks')
      this.analysisResult = result
    },
    
    // 检查可访问性
    async checkAccessibility() {
      const result = await this.sendMessage('analyzeAccessibility')
      this.analysisResult = result
    }
  }
}
</script>

<style scoped>
.tools {
  color: white;
}

.title {
  text-align: center;
  margin-bottom: 20px;
  font-size: 24px;
  font-weight: 600;
}

.card {
  background: rgba(255, 255, 255, 0.1);
  backdrop-filter: blur(10px);
  border-radius: 12px;
  padding: 16px;
  margin-bottom: 16px;
  border: 1px solid rgba(255, 255, 255, 0.2);
}

.card h3 {
  margin: 0 0 12px 0;
  font-size: 16px;
  font-weight: 600;
}

.tool-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 8px;
}

.tool-btn {
  padding: 12px 8px;
  border: none;
  border-radius: 8px;
  background: rgba(255, 255, 255, 0.1);
  color: white;
  cursor: pointer;
  font-size: 12px;
  font-weight: 500;
  transition: all 0.3s ease;
  border: 1px solid rgba(255, 255, 255, 0.2);
}

.tool-btn:hover {
  background: rgba(255, 255, 255, 0.2);
  transform: translateY(-2px);
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
}

.result-content {
  background: rgba(0, 0, 0, 0.3);
  border-radius: 8px;
  padding: 12px;
  max-height: 200px;
  overflow-y: auto;
}

.result-content pre {
  margin: 0;
  font-size: 11px;
  color: #e0e0e0;
  white-space: pre-wrap;
  word-break: break-word;
}
</style>

🔄 第五章:Content Script 模块化架构 5.1 架构设计理念 Content Script 是插件与网页交互的核心,我们采用模块化设计:

src/content/
├── index.js           # 主入口 - 模块协调器
├── messageHandler.js  # 消息处理 - 统一消息路由
├── linkManager.js     # 链接管理 - 链接相关功能
├── styleManager.js    # 样式管理 - 页面美化功能
├── analysisTools.js   # 分析工具 - 页面分析功能
└── storageManager.js  # 存储管理 - 数据持久化

设计原则:

单一职责: 每个模块负责特定功能领域 松耦合: 模块间通过消息机制通信 可扩展: 新功能可以独立模块开发 可维护: 清晰的代码结构和注释 5.2 主入口文件 - src/content/index.js

js
/**
 * Content Script 主入口文件
 * 负责初始化各个功能模块并协调它们之间的工作
 */

// 导入各功能模块
import MessageHandler from './messageHandler.js'
import LinkManager from './linkManager.js'
import StyleManager from './styleManager.js'
import AnalysisTools from './analysisTools.js'
import StorageManager from './storageManager.js'

/**
 * Content Script 主类
 * 作为各个模块的协调器和入口点
 */
    class ContentScript {
    constructor() {
    // 初始化各功能模块
    this.messageHandler = new MessageHandler()
    this.linkManager = new LinkManager()
    this.styleManager = new StyleManager()
    this.analysisTools = new AnalysisTools()
    this.storageManager = new StorageManager()
    
    // 初始化完成标志
    this.initialized = false
    }

  /**
   * 初始化 Content Script
   * 设置消息处理器和各模块的交互
      */

    async init() {
    
    try {
      console.log('🚀 Content Script 开始初始化...')
      
      // 1. 初始化存储管理器(优先级最高)
      await this.storageManager.init()
      
      // 2. 初始化样式管理器
      await this.styleManager.init()
      
      // 3. 初始化链接管理器
      await this.linkManager.init()
      
      // 4. 初始化分析工具
      await this.analysisTools.init()
      
      // 5. 设置消息处理器
      this.setupMessageHandlers()
      
      // 6. 检查自动高亮设置
      await this.checkAutoHighlight()
      
      this.initialized = true
      console.log('✅ Content Script 初始化完成')
      
    } catch (error) {
      console.error('❌ Content Script 初始化失败:', error)
    }
  }

  /**
   * 设置消息处理器
   * 注册各种消息的处理函数
      */

    setupMessageHandlers() {
    
    // 链接相关操作
    this.messageHandler.register('highlightLinks', () => {
      return this.linkManager.highlightAllLinks()
    })
    
    this.messageHandler.register('removeHighlight', () => {
      return this.linkManager.removeAllHighlight()
    })
    
    this.messageHandler.register('extractLinks', () => {
      return this.linkManager.extractAllLinks()
    })
    
    // 页面统计
    this.messageHandler.register('getPageStats', () => {
      return this.analysisTools.getPageStats()
    })
    
    // 样式管理操作
    this.messageHandler.register('toggleDarkMode', () => {
      return this.styleManager.toggleDarkMode()
    })
    
    this.messageHandler.register('toggleReadingMode', () => {
      return this.styleManager.toggleReadingMode()
    })
    
    this.messageHandler.register('increaseFontSize', () => {
      return this.styleManager.increaseFontSize()
    })
    
    this.messageHandler.register('resetStyles', () => {
      return this.styleManager.resetAllStyles()
    })
    
    // 分析工具
    this.messageHandler.register('analyzeImages', () => {
      return this.analysisTools.analyzeImages()
    })
    
    this.messageHandler.register('analyzePerformance', () => {
      return this.analysisTools.analyzePerformance()
    })
    
    this.messageHandler.register('analyzeAccessibility', () => {
      return this.analysisTools.analyzeAccessibility()
    })
    
    console.log('📝 消息处理器注册完成')
  }

  /**
   * 检查自动高亮设置
   * 如果启用了自动高亮,则自动执行链接高亮
      */

    async checkAutoHighlight() {
    
    try {
      const settings = await this.storageManager.getSettings()
      if (settings.autoHighlight) {
        console.log('🔗 自动高亮已启用,开始高亮链接')
        await this.linkManager.highlightAllLinks()
      }
    } catch (error) {
      console.error('检查自动高亮设置失败:', error)
    }
  }

  /**
   * 获取模块实例
   * 提供外部访问各模块的接口
      */

    getModule(moduleName) {
    
    const modules = {
      messageHandler: this.messageHandler,
      linkManager: this.linkManager,
      styleManager: this.styleManager,
      analysisTools: this.analysisTools,
      storageManager: this.storageManager
    }
    
    return modules[moduleName] || null
  }
}

// 创建并初始化 Content Script 实例
const contentScript = new ContentScript()

// 页面加载完成后初始化
if (document.readyState === 'loading') {
  document.addEventListener('DOMContentLoaded', () => {
    contentScript.init()
  })
} else {
  // 如果页面已经加载完成,直接初始化
  contentScript.init()
}

// 将实例暴露到全局,便于调试
window.contentScript = contentScript

// 导出实例(用于模块化环境)
export default contentScript

5.3 消息处理器 - src/content/messageHandler.js

js
/**
 * 消息处理器模块
 * 统一管理来自 popup 和 background 的消息
 * 提供消息路由和处理机制
 */

class MessageHandler {
  constructor() {
    // 消息处理器映射表
    this.handlers = new Map()
    
    // 消息处理统计
    this.stats = {
      received: 0,
      processed: 0,
      errors: 0
    }
    
    // 初始化消息监听
    this.setupMessageListener()
  }

  /**
   * 设置消息监听器
   * 监听来自 popup 和 background 的消息
      */

    setupMessageListener() {
    
    chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
      // 更新接收统计
      this.stats.received++
      
      console.log('📨 收到消息:', request)
      
      // 异步处理消息
      this.handleMessage(request, sender)
        .then(result => {
          // 发送响应
          sendResponse({
            success: true,
            data: result,
            timestamp: Date.now()
          })
          
          // 更新处理统计
          this.stats.processed++
        })
        .catch(error => {
          console.error('❌ 消息处理失败:', error)
          
          // 发送错误响应
          sendResponse({
            success: false,
            error: error.message,
            timestamp: Date.now()
          })
          
          // 更新错误统计
          this.stats.errors++
        })
      
      // 返回 true 表示异步响应
      return true
    })
    
    console.log('👂 消息监听器已设置')
  }

  /**
   * 处理消息
   * @param {Object} request - 消息请求对象
   * @param {Object} sender - 消息发送者信息
   * @returns {Promise} 处理结果
      */

    async handleMessage(request, sender) {
    
    const { action, ...params } = request
    
    // 检查是否有对应的处理器
    if (!this.handlers.has(action)) {
      throw new Error(`未知的操作: ${action}`)
    }
    
    // 获取处理器函数
    const handler = this.handlers.get(action)
    
    try {
      // 执行处理器
      const result = await handler(params, sender)
      
      console.log(`✅ 操作 ${action} 执行成功:`, result)
      return result
      
    } catch (error) {
      console.error(`❌ 操作 ${action} 执行失败:`, error)
      throw error
    }
  }

  /**
   * 注册消息处理器
   * @param {string} action - 操作名称
   * @param {Function} handler - 处理器函数
      */

    register(action, handler) {
    
    if (typeof handler !== 'function') {
      throw new Error('处理器必须是一个函数')
    }
    
    this.handlers.set(action, handler)
    console.log(`📝 注册消息处理器: ${action}`)
  }

  /**
   * 移除消息处理器
   * @param {string} action - 操作名称
      */

    unregister(action) {
    
    if (this.handlers.has(action)) {
      this.handlers.delete(action)
      console.log(`🗑️ 移除消息处理器: ${action}`)
    }
  }

  /**
   * 获取已注册的处理器列表
   * @returns {Array} 处理器名称列表
      */

    getRegisteredHandlers() {
    
    return Array.from(this.handlers.keys())
  }

  /**
   * 获取消息处理统计信息
   * @returns {Object} 统计信息
      */

    getStats() {
    
    return {
      ...this.stats,
      successRate: this.stats.received > 0 
        ? ((this.stats.processed / this.stats.received) * 100).toFixed(2) + '%'
        : '0%',
      errorRate: this.stats.received > 0
        ? ((this.stats.errors / this.stats.received) * 100).toFixed(2) + '%'
        : '0%'
    }
  }

  /**
   * 重置统计信息
      */

    resetStats() {
    
    this.stats = {
      received: 0,
      processed: 0,
      errors: 0
    }
    console.log('📊 消息处理统计已重置')
  }
}

export default MessageHandler

5.4 链接管理器 - src/content/linkManager.js

js
/**
 * 链接管理器模块
 * 负责页面链接的高亮、提取、分析等功能
 */

class LinkManager {
  constructor() {
    // 高亮样式 ID
    this.styleId = 'vue3-extension-link-highlight'
    
    // 高亮状态
    this.isHighlighted = false
    
    // 链接缓存
    this.linkCache = new Map()
    
    // 配置选项
    this.options = {
      highlightColor: '#ffeb3b',
      highlightOpacity: 0.3,
      animationDuration: '0.3s'
    }
  }

  /**
   * 初始化链接管理器
      */

    async init() {
    
    console.log('🔗 链接管理器初始化中...')
    
    // 注入高亮样式
    this.injectHighlightStyles()
    
    // 缓存页面链接
    this.cachePageLinks()
    
    console.log('✅ 链接管理器初始化完成')
  }

  /**
   * 注入链接高亮样式
      */

    injectHighlightStyles() {
    
    // 检查是否已经注入
    if (document.getElementById(this.styleId)) {
      return
    }
    
    const style = document.createElement('style')
    style.id = this.styleId
    style.textContent = `
      /* Vue3 Extension 链接高亮样式 */
      .vue3-ext-highlighted-link {
        background-color: ${this.options.highlightColor} !important;
        opacity: ${this.options.highlightOpacity} !important;
        transition: all ${this.options.animationDuration} ease !important;
        border-radius: 3px !important;
        padding: 2px 4px !important;
        margin: 0 1px !important;
        box-shadow: 0 2px 4px rgba(0,0,0,0.1) !important;
      }
      
      .vue3-ext-highlighted-link:hover {
        opacity: 0.6 !important;
        transform: scale(1.02) !important;
      }
      
      /* 不同类型链接的颜色区分 */
      .vue3-ext-highlighted-link.external {
        background-color: #f44336 !important;
      }
      
      .vue3-ext-highlighted-link.internal {
        background-color: #4caf50 !important;
      }
      
      .vue3-ext-highlighted-link.anchor {
        background-color: #2196f3 !important;
      }
    `
    
    document.head.appendChild(style)
    console.log('🎨 链接高亮样式已注入')
  }

  /**
   * 缓存页面链接
      */

    cachePageLinks() {
    
    const links = document.querySelectorAll('a[href]')
    this.linkCache.clear()
    
    links.forEach((link, index) => {
      const href = link.getAttribute('href')
      const text = link.textContent.trim()
      const type = this.getLinkType(href)
      
      this.linkCache.set(index, {
        element: link,
        href,
        text,
        type,
        isValid: this.isValidLink(href)
      })
    })
    
    console.log(`📋 已缓存 ${this.linkCache.size} 个链接`)
  }

  /**
   * 判断链接类型
   * @param {string} href - 链接地址
   * @returns {string} 链接类型
      */

    getLinkType(href) {
    
    if (!href) return 'invalid'
    
    if (href.startsWith('#')) {
      return 'anchor'  // 锚点链接
    }
    
    if (href.startsWith('http') || href.startsWith('//')) {
      const currentDomain = window.location.hostname
      const linkDomain = new URL(href, window.location.origin).hostname
      return linkDomain === currentDomain ? 'internal' : 'external'
    }
    
    if (href.startsWith('/') || href.startsWith('./') || href.startsWith('../')) {
      return 'internal'  // 相对链接
    }
    
    return 'other'
  }

  /**
   * 检查链接是否有效
   * @param {string} href - 链接地址
   * @returns {boolean} 是否有效
      */

    isValidLink(href) {
    
    if (!href || href === '#') return false
    
    // 排除 JavaScript 链接
    if (href.startsWith('javascript:')) return false
    
    // 排除邮件链接(可选)
    if (href.startsWith('mailto:')) return false
    
    return true
  }

  /**
   * 高亮所有链接
   * @returns {Object} 高亮结果
      */

    highlightAllLinks() {
    
    try {
      // 如果已经高亮,先移除
      if (this.isHighlighted) {
        this.removeAllHighlight()
      }
      
      let highlightedCount = 0
      
      this.linkCache.forEach((linkInfo) => {
        const { element, type, isValid } = linkInfo
        
        if (isValid && element) {
          // 添加高亮类
          element.classList.add('vue3-ext-highlighted-link', type)
          highlightedCount++
        }
      })
      
      this.isHighlighted = true
      
      const result = {
        success: true,
        highlightedCount,
        totalLinks: this.linkCache.size,
        timestamp: Date.now()
      }
      
      console.log('🔆 链接高亮完成:', result)
      return result
      
    } catch (error) {
      console.error('❌ 链接高亮失败:', error)
      throw error
    }
  }

  /**
   * 移除所有高亮
   * @returns {Object} 移除结果
      */

    removeAllHighlight() {
    
    try {
      let removedCount = 0
      
      // 移除所有高亮类
      const highlightedLinks = document.querySelectorAll('.vue3-ext-highlighted-link')
      highlightedLinks.forEach(link => {
        link.classList.remove(
          'vue3-ext-highlighted-link', 
          'external', 
          'internal', 
          'anchor'
        )
        removedCount++
      })
      
      this.isHighlighted = false
      
      const result = {
        success: true,
        removedCount,
        timestamp: Date.now()
      }
      
      console.log('🚫 链接高亮已移除:', result)
      return result
      
    } catch (error) {
      console.error('❌ 移除链接高亮失败:', error)
      throw error
    }
  }

  /**
   * 提取所有链接
   * @returns {Object} 链接提取结果
      */

    extractAllLinks() {
    
    try {
      const links = []
      const stats = {
        total: 0,
        internal: 0,
        external: 0,
        anchor: 0,
        invalid: 0
      }
      
      this.linkCache.forEach((linkInfo) => {
        const { href, text, type, isValid } = linkInfo
        
        if (isValid) {
          links.push({
            href,
            text: text.substring(0, 100), // 限制文本长度
            type
          })
        }
        
        stats.total++
        stats[type]++
      })
      
      const result = {
        success: true,
        links,
        stats,
        timestamp: Date.now()
      }
      
      console.log('🔗 链接提取完成:', result)
      return result
      
    } catch (error) {
      console.error('❌ 链接提取失败:', error)
      throw error
    }
  }

  /**
   * 获取链接统计信息
   * @returns {Object} 统计信息
      */

    getLinkStats() {
    
    const stats = {
      total: this.linkCache.size,
      internal: 0,
      external: 0,
      anchor: 0,
      invalid: 0,
      valid: 0
    }
    
    this.linkCache.forEach((linkInfo) => {
      const { type, isValid } = linkInfo
      stats[type]++
      if (isValid) stats.valid++
    })
    
    return stats
  }

  /**
   * 检查链接有效性(异步)
   * @param {string} url - 要检查的链接
   * @returns {Promise<boolean>} 是否有效
      */

    async checkLinkValidity(url) {
    
    try {
      const response = await fetch(url, { 
        method: 'HEAD',
        mode: 'no-cors'
      })
      return response.ok
    } catch (error) {
      console.warn('链接检查失败:', url, error)
      return false
    }
  }

  /**
   * 更新高亮颜色
   * @param {string} color - 新的高亮颜色
      */

    updateHighlightColor(color) {
    
    this.options.highlightColor = color
    
    // 重新注入样式
    const existingStyle = document.getElementById(this.styleId)
    if (existingStyle) {
      existingStyle.remove()
    }
    
    this.injectHighlightStyles()
    
    // 如果当前有高亮,重新应用
    if (this.isHighlighted) {
      this.removeAllHighlight()
      this.highlightAllLinks()
    }
  }
}

export default LinkManager

⚙️ 第六章:后台脚本开发 6.1 background.js - Service Worker

js
/**
 * Background Service Worker
 * Chrome Extension 的后台脚本,运行在独立的上下文中
 * 负责处理插件的生命周期事件、权限管理、消息路由等
 */

// 插件安装时的初始化处理
chrome.runtime.onInstalled.addListener((details) => {
  console.log('🚀 Vue3 Chrome Extension 已安装', details)

  // 根据安装原因执行不同的初始化逻辑
  switch (details.reason) {
    case 'install':
      console.log('📦 首次安装插件')
      handleFirstInstall()
      break
      
    case 'update':
      console.log('🔄 插件已更新')
      handleUpdate(details.previousVersion)
      break
      
    case 'chrome_update':
      console.log('🌐 Chrome 浏览器已更新')
      break
  }
})

/**
 * 首次安装处理函数
 * 设置默认配置和创建右键菜单
 */
    function handleFirstInstall() {
    // 设置默认配置到 Chrome 存储
    chrome.storage.sync.set({
    autoHighlight: false,        // 自动高亮链接
    highlightColor: '#ffeb3b',   // 高亮颜色
    darkMode: false,             // 暗黑模式
    readingMode: false,          // 阅读模式
    fontSize: 100,               // 字体大小百分比
    version: '1.0.0'             // 插件版本
    })

  // 创建右键菜单项
  createContextMenus()

  console.log('✅ 默认配置已设置')
}

/**
 * 插件更新处理函数
 * @param {string} previousVersion - 之前的版本号
 */
    function handleUpdate(previousVersion) {
    console.log(`📈 从版本 ${previousVersion} 更新到当前版本`)

  // 这里可以添加版本迁移逻辑
  // 例如:数据格式变更、新功能默认设置等
}

/**
 * 创建右键菜单
 */
    function createContextMenus() {
    // 主菜单项
    chrome.contextMenus.create({
    id: 'vue3-extension-main',
    title: 'Vue3 插件工具',
    contexts: ['page', 'selection', 'link']
    })

  // 子菜单项 - 链接高亮
  chrome.contextMenus.create({
    id: 'highlightLinks',
    parentId: 'vue3-extension-main',
    title: '🔗 高亮页面所有链接',
    contexts: ['page']
  })

  // 子菜单项 - 暗黑模式
  chrome.contextMenus.create({
    id: 'toggleDarkMode',
    parentId: 'vue3-extension-main',
    title: '🌙 切换暗黑模式',
    contexts: ['page']
  })

  // 子菜单项 - 阅读模式
  chrome.contextMenus.create({
    id: 'toggleReadingMode',
    parentId: 'vue3-extension-main',
    title: '📖 切换阅读模式',
    contexts: ['page']
  })

  console.log('📋 右键菜单已创建')
}

// 处理右键菜单点击事件
chrome.contextMenus.onClicked.addListener((info, tab) => {
  console.log('🖱️ 右键菜单被点击:', info.menuItemId)

  // 根据菜单项 ID 执行相应操作
  switch (info.menuItemId) {
    case 'highlightLinks':
      sendMessageToContentScript(tab.id, { action: 'highlightLinks' })
      break
      
    case 'toggleDarkMode':
      sendMessageToContentScript(tab.id, { action: 'toggleDarkMode' })
      break
      
    case 'toggleReadingMode':
      sendMessageToContentScript(tab.id, { action: 'toggleReadingMode' })
      break
      
    default:
      console.log('未知的菜单项:', info.menuItemId)
  }
})

/**
 * 向 Content Script 发送消息的辅助函数
 * @param {number} tabId - 标签页 ID
 * @param {Object} message - 要发送的消息
 */
    async function sendMessageToContentScript(tabId, message) {
    try {
    const response = await chrome.tabs.sendMessage(tabId, message)
    console.log('✅ 消息发送成功:', response)
    } catch (error) {
    console.error('❌ 消息发送失败:', error)
    
    // 如果 Content Script 未加载,可以尝试注入
    if (error.message.includes('Could not establish connection')) {
      console.log('🔄 尝试重新注入 Content Script')
      await injectContentScript(tabId)
    }
    }
    }

/**
 * 注入 Content Script(紧急情况下使用)
 * @param {number} tabId - 标签页 ID
 */
    async function injectContentScript(tabId) {
    try {
    await chrome.scripting.executeScript({
      target: { tabId },
      files: ['content.js']
    })
    console.log('✅ Content Script 注入成功')
    } catch (error) {
    console.error('❌ Content Script 注入失败:', error)
    }
    }

// 处理来自 Content Script 或 Popup 的消息
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
  console.log('📨 Background 收到消息:', request)

  // 异步处理消息
  handleBackgroundMessage(request, sender)
    .then(result => {
      sendResponse({
        success: true,
        data: result,
        timestamp: Date.now()
      })
    })
    .catch(error => {
      console.error('❌ Background 消息处理失败:', error)
      sendResponse({
        success: false,
        error: error.message,
        timestamp: Date.now()
      })
    })

  // 返回 true 表示异步响应
  return true
})

/**
 * 处理后台消息的主函数
 * @param {Object} request - 请求对象
 * @param {Object} sender - 发送者信息
 * @returns {Promise} 处理结果
 */
    async function handleBackgroundMessage(request, sender) {
    const { action, ...params } = request

  switch (action) {
    case 'getTabInfo':
      return await getTabInfo()
      
    case 'openOptionsPage':
      return await openOptionsPage()
      
    case 'updateBadge':
      return await updateBadge(params)
      
    case 'getStorageData':
      return await getStorageData(params.keys)
      
    case 'setStorageData':
      return await setStorageData(params.data)
      
    default:
      throw new Error(`未知的后台操作: ${action}`)
  }
}

/**
 * 获取当前标签页信息
 * @returns {Promise<Object>} 标签页信息
 */
    async function getTabInfo() {
    const [tab] = await chrome.tabs.query({ 
    active: true, 
    currentWindow: true 
    })

  if (!tab) {
    throw new Error('无法获取当前标签页')
  }

  return {
    id: tab.id,
    title: tab.title,
    url: tab.url,
    favIconUrl: tab.favIconUrl,
    status: tab.status
  }
}

/**
 * 打开选项页面
 * @returns {Promise<boolean>} 是否成功打开
 */
    async function openOptionsPage() {
    try {
    await chrome.runtime.openOptionsPage()
    return true
    } catch (error) {
    console.error('打开选项页面失败:', error)
    return false
    }
    }

/**
 * 更新插件图标徽章
 * @param {Object} params - 徽章参数
 * @returns {Promise<boolean>} 是否成功更新
 */
    async function updateBadge({ text, color, tabId }) {
    try {
    if (text !== undefined) {
      await chrome.action.setBadgeText({ 
        text: String(text),
        tabId 
      })
    }
    
    if (color) {
      await chrome.action.setBadgeBackgroundColor({ 
        color,
        tabId 
      })
    }
    
    return true
    } catch (error) {
    console.error('更新徽章失败:', error)
    return false
    }
    }

/**
 * 获取存储数据
 * @param {Array|string} keys - 要获取的键
 * @returns {Promise<Object>} 存储数据
 */
    async function getStorageData(keys) {
    return new Promise((resolve, reject) => {
    chrome.storage.sync.get(keys, (result) => {
      if (chrome.runtime.lastError) {
        reject(new Error(chrome.runtime.lastError.message))
      } else {
        resolve(result)
      }
    })
    })
    }

/**
 * 设置存储数据
 * @param {Object} data - 要存储的数据
 * @returns {Promise<boolean>} 是否成功设置
 */
    async function setStorageData(data) {
    return new Promise((resolve, reject) => {
    chrome.storage.sync.set(data, () => {
      if (chrome.runtime.lastError) {
        reject(new Error(chrome.runtime.lastError.message))
      } else {
        resolve(true)
      }
    })
    })
    }

// 监听标签页更新事件
chrome.tabs.onUpdated.addListener(async (tabId, changeInfo, tab) => {
  // 只在页面完全加载后处理
  if (changeInfo.status === 'complete' && tab.url) {
    console.log('📄 页面加载完成:', tab.url)
    
    try {
      // 检查自动高亮设置
      const settings = await getStorageData(['autoHighlight'])
      
      if (settings.autoHighlight) {
        console.log('🔗 自动高亮已启用,准备高亮链接')
        
        // 延迟执行,确保 Content Script 已加载
        setTimeout(async () => {
          await sendMessageToContentScript(tabId, { 
            action: 'highlightLinks' 
          })
        }, 1000)
      }
      
      // 重置徽章
      await updateBadge({ text: '', tabId })
      
    } catch (error) {
      console.error('处理页面更新事件失败:', error)
    }
  }
})

// 监听存储变化事件
chrome.storage.onChanged.addListener((changes, namespace) => {
  console.log('💾 存储发生变化:', changes, namespace)

  // 处理特定设置的变化
  Object.keys(changes).forEach(key => {
    const { oldValue, newValue } = changes[key]
    
    switch (key) {
      case 'autoHighlight':
        console.log(`🔗 自动高亮设置变更: ${oldValue} -> ${newValue}`)
        break
        
      case 'highlightColor':
        console.log(`🎨 高亮颜色变更: ${oldValue} -> ${newValue}`)
        break
        
      default:
        console.log(`⚙️ 设置 ${key} 变更: ${oldValue} -> ${newValue}`)
    }
  })
})

// 监听插件启动事件
chrome.runtime.onStartup.addListener(() => {
  console.log('🚀 Chrome 启动,插件后台脚本已加载')
})

// 监听插件挂起事件
chrome.runtime.onSuspend.addListener(() => {
  console.log('😴 插件后台脚本即将挂起')

  // 在挂起前清理资源
  // 例如:关闭数据库连接、保存状态等
})

console.log('✅ Vue3 Chrome Extension Background Script 已启动')

Background Script 关键特性:

生命周期管理: 处理安装、更新、启动等事件 权限管理: 管理插件权限和 API 访问 消息路由: 在 popup 和 content script 之间传递消息 存储管理: 统一管理插件配置和数据 右键菜单: 提供便捷的页面操作入口 自动化功能: 根据设置自动执行某些操作 🔧 第七章:构建和部署 7.1 构建流程详解

bash
# 开发模式 - 实时预览
npm run dev

# 生产构建 - 生成插件文件
npm run build

# 预览构建结果
npm run preview

构建过程分析:

Vite 解析配置: 读取 vite.config.js 配置 多入口处理: 分别构建 popup、background、content 脚本 Vue 组件编译: 将 .vue 文件编译为 JavaScript 代码优化: 压缩、tree-shaking、代码分割 资源处理: 处理 CSS、图片等静态资源 文件输出: 生成到 dist 目录 自动复制: 复制 manifest.json 到输出目录 7.2 部署到 Chrome 商店 步骤 1: 准备发布文件

bash
# 构建生产版本
npm run build

# 检查 dist 目录内容
ls -la dist/
# 应该包含:
# - manifest.json
# - popup.html
# - popup.js
# - popup.css
# - background.js
# - content.js

步骤 2: 创建发布包

bash
# 进入构建目录
cd dist

# 创建 ZIP 包
zip -r ../vue3-chrome-extension.zip .

# 返回项目根目录
cd ..

步骤 3: 上传到 Chrome 开发者控制台

访问 Chrome 开发者控制台 点击「新增项目」 上传 vue3-chrome-extension.zip 填写插件信息: 名称和描述 图标和截图 分类和标签 隐私政策 提交审核 7.3 本地测试和调试 加载未打包的扩展程序:

打开 Chrome 浏览器 访问 chrome://extensions/ 开启「开发者模式」 点击「加载已解压的扩展程序」 选择项目的 dist 目录 调试技巧:

js
// 在 Content Script 中调试
console.log('Content Script 调试信息')
debugger // 设置断点

// 在 Background Script 中调试
// 右键插件图标 -> 检查弹出内容 -> 背景页
console.log('Background Script 调试信息')

// 在 Popup 中调试
// 右键插件图标 -> 检查弹出内容
console.log('Popup 调试信息')

📊 第八章:性能优化和最佳实践 8.1 性能优化策略

  1. 代码分割和懒加载
js
// 在 Vue Router 中使用懒加载
const routes = [
  {
    path: '/tools',
    name: 'Tools',
    // 懒加载组件
    component: () => import('../views/Tools.vue')
  }
]
  1. Content Script 优化
js
// 使用防抖函数避免频繁操作
function debounce(func, wait) {
  let timeout
  return function executedFunction(...args) {
    const later = () => {
      clearTimeout(timeout)
      func(...args)
    }
    clearTimeout(timeout)
    timeout = setTimeout(later, wait)
  }
}

// 优化 DOM 查询
class OptimizedLinkManager {
  constructor() {
    this.linkCache = new WeakMap() // 使用 WeakMap 避免内存泄漏
    this.observer = null
  }

  // 使用 MutationObserver 监听 DOM 变化
  setupDOMObserver() {
    this.observer = new MutationObserver(
      debounce(this.handleDOMChanges.bind(this), 300)
    )
    
    this.observer.observe(document.body, {
      childList: true,
      subtree: true
    })
  }

  handleDOMChanges(mutations) {
    // 只处理新增的链接
    mutations.forEach(mutation => {
      mutation.addedNodes.forEach(node => {
        if (node.nodeType === Node.ELEMENT_NODE) {
          const links = node.querySelectorAll('a[href]')
          this.cacheNewLinks(links)
        }
      })
    })
  }
}
  1. 内存管理
js
// 清理事件监听器
class ComponentWithCleanup {
  constructor() {
    this.eventListeners = []
  }

  addEventListener(element, event, handler) {
    element.addEventListener(event, handler)
    this.eventListeners.push({ element, event, handler })
  }

  cleanup() {
    // 移除所有事件监听器
    this.eventListeners.forEach(({ element, event, handler }) => {
      element.removeEventListener(event, handler)
    })
    this.eventListeners = []
  }
}

8.2 安全最佳实践

  1. 内容安全策略 (CSP)
json
// manifest.json 中添加 CSP
{
  "content_security_policy": {
    "extension_pages": "script-src 'self'; object-src 'self'"
  }
}
  1. 权限最小化原则
json
// 只请求必要的权限
{
  "permissions": [
    "activeTab",      // 而不是 "tabs"
    "storage"         // 而不是 "unlimitedStorage"
  ]
}
  1. 输入验证和清理
js
// 清理用户输入
function sanitizeInput(input) {
  return input
    .replace(/[<>"'&]/g, '') // 移除危险字符
    .trim()
    .substring(0, 1000) // 限制长度
}

// 验证 URL
function isValidURL(url) {
  try {
    new URL(url)
    return true
  } catch {
    return false
  }
}

8.3 用户体验优化

  1. 加载状态管理
vue
<template>
  <div class="tool-button">
    <button 
      @click="executeAction" 
      :disabled="loading"
      class="btn"
    >
      <span v-if="loading" class="spinner"></span>
      {{ loading ? '处理中...' : '执行操作' }}
    </button>
  </div>
</template>

<style>
.spinner {
  display: inline-block;
  width: 12px;
  height: 12px;
  border: 2px solid #f3f3f3;
  border-top: 2px solid #3498db;
  border-radius: 50%;
  animation: spin 1s linear infinite;
}

@keyframes spin {
  0% { transform: rotate(0deg); }
  100% { transform: rotate(360deg); }
}
</style>
  1. 错误处理和用户反馈
js
// 全局错误处理
class ErrorHandler {
  static showNotification(message, type = 'info') {
    // 创建通知元素
    const notification = document.createElement('div')
    notification.className = `notification notification-${type}`
    notification.textContent = message
    
    // 添加到页面
    document.body.appendChild(notification)
    
    // 自动移除
    setTimeout(() => {
      notification.remove()
    }, 3000)
  }

  static handleError(error, context = '') {
    console.error(`错误 [${context}]:`, error)
    
    // 显示用户友好的错误信息
    const userMessage = this.getUserFriendlyMessage(error)
    this.showNotification(userMessage, 'error')
  }

  static getUserFriendlyMessage(error) {
    const errorMessages = {
      'NetworkError': '网络连接失败,请检查网络设置',
      'PermissionError': '权限不足,请检查插件权限设置',
      'TimeoutError': '操作超时,请稍后重试'
    }
    
    return errorMessages[error.name] || '操作失败,请稍后重试'
  }
}

🎯 第九章:实战案例和扩展 9.1 案例 1:智能链接分析器 这个案例展示如何扩展链接管理器,添加智能分析功能:

js
// 扩展的链接分析器
class SmartLinkAnalyzer extends LinkManager {
  constructor() {
    super()
    this.analysisCache = new Map()
    this.patterns = {
      social: /(?:facebook|twitter|instagram|linkedin|youtube)\.com/i,
      shopping: /(?:amazon|ebay|taobao|tmall)\.com/i,
      news: /(?:cnn|bbc|reuters|xinhua)\.com/i,
      tech: /(?:github|stackoverflow|medium)\.com/i
    }
  }

  /**
   * 智能分析链接类型和安全性
      */

    async analyzeLinksIntelligently() {
    
    const analysis = {
      categories: {},
      security: {
        safe: 0,
        suspicious: 0,
        unknown: 0
      },
      recommendations: []
    }
    
    for (const [index, linkInfo] of this.linkCache.entries()) {
      const { href, text } = linkInfo
      
      // 分类分析
      const category = this.categorizeLink(href)
      analysis.categories[category] = (analysis.categories[category] || 0) + 1
      
      // 安全性分析
      const securityLevel = await this.analyzeLinkSecurity(href)
      analysis.security[securityLevel]++
      
      // 缓存分析结果
      this.analysisCache.set(index, {
        category,
        securityLevel,
        timestamp: Date.now()
      })
    }
    
    // 生成建议
    analysis.recommendations = this.generateRecommendations(analysis)
    
    return analysis
  }

  /**
   * 链接分类
      */

    categorizeLink(href) {
    
    for (const [category, pattern] of Object.entries(this.patterns)) {
      if (pattern.test(href)) {
        return category
      }
    }
    return 'other'
  }

  /**
   * 安全性分析
      */

    async analyzeLinkSecurity(href) {
    
    try {
      // 检查是否为 HTTPS
      if (!href.startsWith('https://')) {
        return 'suspicious'
      }
      
      // 检查域名长度(过长可能是钓鱼网站)
      const domain = new URL(href).hostname
      if (domain.length > 50) {
        return 'suspicious'
      }
      
      // 检查是否包含可疑字符
      if (/[\u4e00-\u9fa5]/.test(domain)) { // 包含中文字符的域名
        return 'suspicious'
      }
      
      return 'safe'
    } catch {
      return 'unknown'
    }
  }

  /**
   * 生成安全建议
      */

    generateRecommendations(analysis) {
    
    const recommendations = []
    
    if (analysis.security.suspicious > 0) {
      recommendations.push({
        type: 'warning',
        message: `发现 ${analysis.security.suspicious} 个可疑链接,建议谨慎点击`
      })
    }
    
    if (analysis.categories.shopping > 5) {
      recommendations.push({
        type: 'info',
        message: '页面包含大量购物链接,建议开启广告拦截器'
      })
    }
    
    return recommendations
  }
}

9.2 案例 2:页面性能监控器

js
// 页面性能监控器
class PerformanceMonitor {
  constructor() {
    this.metrics = {}
    this.observers = []
  }

  /**
   * 开始性能监控
      */

    startMonitoring() {
    
    this.collectNavigationMetrics()
    this.setupPerformanceObserver()
    this.monitorResourceLoading()
    this.trackUserInteractions()
  }

  /**
   * 收集导航性能指标
      */

    collectNavigationMetrics() {
    
    const navigation = performance.getEntriesByType('navigation')[0]
    
    if (navigation) {
      this.metrics.navigation = {
        // DNS 查询时间
        dnsLookup: navigation.domainLookupEnd - navigation.domainLookupStart,
        // TCP 连接时间
        tcpConnect: navigation.connectEnd - navigation.connectStart,
        // 请求响应时间
        requestResponse: navigation.responseEnd - navigation.requestStart,
        // DOM 解析时间
        domParsing: navigation.domContentLoadedEventEnd - navigation.responseEnd,
        // 页面完全加载时间
        pageLoad: navigation.loadEventEnd - navigation.navigationStart
      }
    }
  }

  /**
   * 设置性能观察器
      */

    setupPerformanceObserver() {
    
    // 监控 LCP (Largest Contentful Paint)
    const lcpObserver = new PerformanceObserver((list) => {
      const entries = list.getEntries()
      const lastEntry = entries[entries.length - 1]
      this.metrics.lcp = lastEntry.startTime
    })
    lcpObserver.observe({ entryTypes: ['largest-contentful-paint'] })
    this.observers.push(lcpObserver)
    
    // 监控 FID (First Input Delay)
    const fidObserver = new PerformanceObserver((list) => {
      const entries = list.getEntries()
      entries.forEach(entry => {
        this.metrics.fid = entry.processingStart - entry.startTime
      })
    })
    fidObserver.observe({ entryTypes: ['first-input'] })
    this.observers.push(fidObserver)
    
    // 监控 CLS (Cumulative Layout Shift)
    let clsValue = 0
    const clsObserver = new PerformanceObserver((list) => {
      const entries = list.getEntries()
      entries.forEach(entry => {
        if (!entry.hadRecentInput) {
          clsValue += entry.value
        }
      })
      this.metrics.cls = clsValue
    })
    clsObserver.observe({ entryTypes: ['layout-shift'] })
    this.observers.push(clsObserver)
  }

  /**
   * 监控资源加载
      */

    monitorResourceLoading() {
    
    const resources = performance.getEntriesByType('resource')
    
    this.metrics.resources = {
      total: resources.length,
      images: resources.filter(r => r.initiatorType === 'img').length,
      scripts: resources.filter(r => r.initiatorType === 'script').length,
      stylesheets: resources.filter(r => r.initiatorType === 'link').length,
      slowResources: resources.filter(r => r.duration > 1000).length
    }
  }

  /**
   * 跟踪用户交互
      */

    trackUserInteractions() {
    
    let interactionCount = 0
    
    const trackInteraction = () => {
      interactionCount++
    }
    
    ['click', 'scroll', 'keydown'].forEach(event => {
      document.addEventListener(event, trackInteraction, { passive: true })
    })
    
    // 每 5 秒更新一次交互统计
    setInterval(() => {
      this.metrics.interactions = {
        total: interactionCount,
        rate: interactionCount / (Date.now() - performance.timing.navigationStart) * 1000
      }
    }, 5000)
  }

  /**
   * 生成性能报告
      */

    generateReport() {
    
    const report = {
      timestamp: Date.now(),
      url: window.location.href,
      metrics: this.metrics,
      recommendations: this.generateRecommendations()
    }
    
    return report
  }

  /**
   * 生成性能优化建议
      */

    generateRecommendations() {
    
    const recommendations = []
    
    // LCP 建议
    if (this.metrics.lcp > 2500) {
      recommendations.push({
        type: 'warning',
        metric: 'LCP',
        message: 'Largest Contentful Paint 过慢,建议优化图片和关键资源加载'
      })
    }
    
    // FID 建议
    if (this.metrics.fid > 100) {
      recommendations.push({
        type: 'warning',
        metric: 'FID',
        message: 'First Input Delay 过长,建议减少 JavaScript 执行时间'
      })
    }
    
    // CLS 建议
    if (this.metrics.cls > 0.1) {
      recommendations.push({
        type: 'warning',
        metric: 'CLS',
        message: 'Cumulative Layout Shift 过大,建议为图片和广告预留空间'
      })
    }
    
    return recommendations
  }

  /**
   * 清理监控器
      */

    cleanup() {
    
    this.observers.forEach(observer => observer.disconnect())
    this.observers = []
  }
}

🔍 第十章:调试和故障排除 10.1 常见问题和解决方案 问题 1: Content Script 无法注入

json
// 解决方案:检查 manifest.json 配置
{
  "content_scripts": [
    {
      "matches": ["<all_urls>"],  // 确保匹配模式正确
      "js": ["content.js"],
      "run_at": "document_end"     // 确保在 DOM 加载后运行
    }
  ]
}

// 在 background.js 中添加错误处理
chrome.tabs.onUpdated.addListener((tabId, changeInfo, tab) => {
  if (changeInfo.status === 'complete') {
    chrome.tabs.sendMessage(tabId, { action: 'ping' })
      .catch(error => {
        console.log('Content script 未加载,尝试注入:', error)
        // 手动注入 content script
        chrome.scripting.executeScript({
          target: { tabId },
          files: ['content.js']
        })
      })
  }
})

问题 2: 消息传递失败

js
// 解决方案:添加重试机制
class ReliableMessaging {
  static async sendMessage(tabId, message, maxRetries = 3) {
    for (let i = 0; i < maxRetries; i++) {
      try {
        const response = await chrome.tabs.sendMessage(tabId, message)
        return response
      } catch (error) {
        console.warn(`消息发送失败,重试 ${i + 1}/${maxRetries}:`, error)
        
        if (i === maxRetries - 1) {
          throw error
        }
        
        // 等待一段时间后重试
        await new Promise(resolve => setTimeout(resolve, 1000))
      }
    }
  }
}

问题 3: 样式冲突

css
/* 解决方案:使用更具体的选择器和 !important */
.vue3-extension-container {
  all: initial !important; /* 重置所有样式 */
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif !important;
}

.vue3-extension-container * {
  box-sizing: border-box !important;
}

/* 使用 CSS 自定义属性避免冲突 */
:root {
  --vue3-ext-primary-color: #007bff;
  --vue3-ext-secondary-color: #6c757d;
}

10.2 调试工具和技巧

  1. Chrome DevTools 扩展调试

// 在 content script 中添加调试信息 class DebugHelper { static log(message, data = null) { if (process.env.NODE_ENV === 'development') { console.group(🔧 [Vue3 Extension Debug] ${message}) if (data) { console.log('数据:', data) } console.trace('调用栈') console.groupEnd() } }

static performance(label, fn) { const start = performance.now() const result = fn() const end = performance.now()

this.log(`性能测试: ${label}`, {
  duration: `${(end - start).toFixed(2)}ms`,
  result
})

return result

} }

// 使用示例 DebugHelper.log('开始高亮链接') const result = DebugHelper.performance('链接高亮', () => { return linkManager.highlightAllLinks() }) DebugHelper.log('高亮完成', result) 一键获取完整项目代码 javascript

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 2. 错误监控和上报

// 全局错误监控 class ErrorMonitor { constructor() { this.errors = [] this.setupErrorHandlers() }

setupErrorHandlers() { // 监听未捕获的错误 window.addEventListener('error', (event) => { this.recordError({ type: 'javascript', message: event.message, filename: event.filename, lineno: event.lineno, colno: event.colno, stack: event.error?.stack }) })

// 监听 Promise 拒绝
window.addEventListener('unhandledrejection', (event) => {
  this.recordError({
    type: 'promise',
    message: event.reason?.message || 'Promise rejected',
    stack: event.reason?.stack
  })
})

}

recordError(error) { const errorRecord = { ...error, timestamp: Date.now(), url: window.location.href, userAgent: navigator.userAgent }

this.errors.push(errorRecord)

// 限制错误记录数量
if (this.errors.length > 100) {
  this.errors = this.errors.slice(-50)
}

// 发送错误报告(可选)
this.reportError(errorRecord)

}

async reportError(error) { try { // 发送到错误收集服务 await fetch('https://your-error-service.com/api/errors', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(error) }) } catch (reportError) { console.warn('错误上报失败:', reportError) } }

getErrorReport() { return { totalErrors: this.errors.length, recentErrors: this.errors.slice(-10), errorTypes: this.groupErrorsByType() } }

groupErrorsByType() { const groups = {} this.errors.forEach(error => { const key = error.type || 'unknown' groups[key] = (groups[key] || 0) + 1 }) return groups } }

// 初始化错误监控 const errorMonitor = new ErrorMonitor() 一键获取完整项目代码 javascript

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84

———————————————— 版权声明:本文为CSDN博主「2401_86326952」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/2401_86326952/article/details/149606329

最近更新