强曰为道
与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

Chrome 扩展开发完全指南 / 第 2 章:项目结构与基础

第 2 章:项目结构与基础

一个清晰的项目结构和正确的 manifest.json 配置是 Chrome 扩展开发的基石。本章将详细讲解扩展的目录组织、manifest.json 的每一个字段以及开发调试流程。


2.1 标准项目结构

my-extension/
├── manifest.json              # 必需 — 扩展清单文件
├── icons/                     # 扩展图标(多种尺寸)
│   ├── icon-16.png
│   ├── icon-32.png
│   ├── icon-48.png
│   └── icon-128.png
├── background/
│   └── service-worker.js      # Service Worker 后台脚本
├── content/
│   ├── content.js             # 内容脚本
│   └── content.css            # 注入页面的样式
├── popup/
│   ├── popup.html             # 弹出页面
│   ├── popup.css              # 弹出页面样式
│   └── popup.js               # 弹出页面逻辑
├── options/
│   ├── options.html           # 选项页面
│   ├── options.css
│   └── options.js
├── sidepanel/
│   ├── sidepanel.html         # 侧边栏页面
│   ├── sidepanel.css
│   └── sidepanel.js
├── lib/                       # 共享库代码
│   └── utils.js
├── _locales/                  # 国际化文件
│   ├── en/
│   │   └── messages.json
│   └── zh_CN/
│       └── messages.json
├── rules/                     # 网络请求规则
│   └── rules.json
├── styles/                    # 公共样式
│   └── common.css
├── assets/                    # 其他静态资源
│   └── fonts/
└── README.md

目录组织建议

目录说明推荐做法
icons/扩展图标保持 16/32/48/128 四种尺寸
background/Service Worker单文件或拆分模块
content/内容脚本和样式按功能分文件
popup/弹出页面独立的 HTML/CSS/JS
options/选项页面与 Popup 结构一致
lib/共享代码工具函数、常量定义
_locales/多语言每种语言一个子目录

💡 提示:对于小型扩展(< 10 个文件),可以将所有文件放在根目录下。中大型项目建议按功能模块组织。


2.2 manifest.json 完整字段解析

manifest.json 是扩展的核心配置文件,Chrome 通过它了解扩展的名称、版本、权限和组件。

2.2.1 基础字段

{
  "manifest_version": 3,
  "name": "我的扩展",
  "description": "一个示例 Chrome 扩展",
  "version": "1.0.0",
  "version_name": "1.0.0 Beta"
}
字段类型必需说明
manifest_versionnumber必须为 3
namestring扩展名称,最多 45 字符
descriptionstring扩展描述,最多 132 字符
versionstring版本号,格式 1-4 个以 . 分隔的整数
version_namestring显示版本名,如 “Beta”

⚠️ 注意namedescription 可以使用 __MSG_keyName__ 格式引用国际化消息(见第 14 章)。

2.2.2 图标

{
  "icons": {
    "16": "icons/icon-16.png",
    "32": "icons/icon-32.png",
    "48": "icons/icon-48.png",
    "128": "icons/icon-128.png"
  }
}
尺寸用途
16×16网页收藏夹图标、扩展管理页面列表
32×32Windows 计算机通常需要此尺寸
48×48扩展管理页面、安装对话框
128×128Chrome Web Store 展示、安装时展示

💡 提示:图标应为 PNG 格式,透明背景。128×128 是最重要的尺寸,用于商店展示。

2.2.3 入口文件

{
  "background": {
    "service_worker": "background/service-worker.js",
    "type": "module"
  },
  "action": {
    "default_popup": "popup/popup.html",
    "default_icon": {
      "16": "icons/icon-16.png",
      "32": "icons/icon-32.png",
      "48": "icons/icon-48.png",
      "128": "icons/icon-128.png"
    },
    "default_title": "点击打开面板"
  },
  "options_page": "options/options.html",
  "side_panel": {
    "default_path": "sidepanel/sidepanel.html"
  }
}

2.2.4 内容脚本

{
  "content_scripts": [
    {
      "matches": ["*://*.example.com/*", "*://example.com/*"],
      "js": ["content/content.js"],
      "css": ["content/content.css"],
      "run_at": "document_idle",
      "all_frames": false
    }
  ]
}
字段类型说明
matchesstring[]URL 匹配模式,使用 Match Patterns
jsstring[]注入的 JavaScript 文件列表
cssstring[]注入的 CSS 文件列表
run_atstring注入时机:document_start / document_end / document_idle
all_framesboolean是否注入所有框架(含 iframe),默认 false

Match Pattern 格式

<scheme>://<host>/<path>
模式匹配
*://www.example.com/*http 和 https 的 www.example.com 所有路径
https://*.example.com/*https 的 example.com 及其所有子域名
<all_urls>所有 URL(需要对应权限)

2.2.5 权限

{
  "permissions": [
    "storage",
    "activeTab",
    "contextMenus",
    "notifications",
    "alarms"
  ],
  "optional_permissions": [
    "tabs",
    "bookmarks",
    "history"
  ],
  "host_permissions": [
    "*://*.example.com/*",
    "*://api.example.com/*"
  ]
}
字段说明
permissions安装时必需的权限,用户安装时会看到提示
optional_permissions运行时按需申请的权限
host_permissions访问特定网站数据的权限

2.2.6 完整示例

下面是一个功能完善的 manifest.json 示例:

{
  "manifest_version": 3,
  "name": "__MSG_appName__",
  "description": "__MSG_appDescription__",
  "version": "2.1.0",
  "version_name": "2.1.0 Stable",
  "minimum_chrome_version": "116",

  "icons": {
    "16": "icons/icon-16.png",
    "32": "icons/icon-32.png",
    "48": "icons/icon-48.png",
    "128": "icons/icon-128.png"
  },

  "background": {
    "service_worker": "background/service-worker.js",
    "type": "module"
  },

  "action": {
    "default_popup": "popup/popup.html",
    "default_icon": {
      "16": "icons/icon-16.png",
      "32": "icons/icon-32.png"
    },
    "default_title": "__MSG_extTooltip__"
  },

  "options_page": "options/options.html",

  "side_panel": {
    "default_path": "sidepanel/sidepanel.html"
  },

  "content_scripts": [
    {
      "matches": ["<all_urls>"],
      "js": ["content/content.js"],
      "css": ["content/content.css"],
      "run_at": "document_idle"
    }
  ],

  "permissions": [
    "storage",
    "activeTab",
    "contextMenus",
    "notifications",
    "alarms",
    "sidePanel"
  ],

  "optional_permissions": [
    "tabs",
    "bookmarks"
  ],

  "host_permissions": [
    "*://*.example.com/*"
  ],

  "content_security_policy": {
    "extension_pages": "script-src 'self'; object-src 'self'"
  },

  "default_locale": "en",

  "web_accessible_resources": [
    {
      "resources": ["assets/fonts/*.woff2", "images/*.png"],
      "matches": ["*://*.example.com/*"]
    }
  ],

  "key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA..."
}

2.3 加载未打包扩展

步骤

  1. 打开 Chrome,导航到 chrome://extensions/
  2. 确认右上角 “开发者模式” 已开启
  3. 点击 “加载已解压的扩展程序” 按钮
  4. 选择扩展项目根目录(包含 manifest.json 的文件夹)
  5. 扩展出现在列表中,说明加载成功
chrome://extensions/
┌─────────────────────────────────────────────────────────┐
│ [开启] 开发者模式                                         │
│                                                          │
│ [加载已解压的扩展程序] [打包扩展程序] [更新]                │
│                                                          │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ 🔵 我的扩展 v1.0.0                    [启用] [删除]  │ │
│ │ ID: abcdefghijklmnopqrstuvwxyz                      │ │
│ │ 类型: 扩展程序                                       │ │
│ │ 检查视图: Service Worker [检查]                       │ │
│ └──────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────┘

常见加载错误

错误信息原因解决方案
Manifest is missing选择的目录不包含 manifest.json确认选择的是项目根目录
'manifest_version' is requiredmanifest.json 格式错误检查 JSON 语法
Unrecognized permission使用了不存在的权限名称查阅权限文档
Service worker registration failed路径或语法错误检查 service_worker 路径和文件语法

2.4 重新加载扩展

开发过程中需要频繁重新加载扩展:

  1. 手动重载:在 chrome://extensions/ 页面点击扩展卡片上的 🔄 按钮
  2. 全局更新:点击页面顶部的 “更新” 按钮,更新所有扩展

💡 提示:使用扩展工具 Extensions Reloader 可以一键重载所有开发中的扩展。

文件变更与自动重载

文件类型是否自动重载操作
manifest.json需要手动重载
Service Worker需要手动重载
Content Scripts需要刷新目标页面
Popup / Options关闭后重新打开即可
CSS(注入页面的)需要刷新目标页面

2.5 调试扩展

2.5.1 调试 Service Worker

  1. 打开 chrome://extensions/
  2. 找到你的扩展,点击 “Service Worker” 链接
  3. 打开 DevTools → Console 面板查看日志
  4. 在 Sources 面板中可以设置断点

2.5.2 调试 Popup

  1. 右键点击扩展图标 → “检查弹出内容”
  2. 或在 chrome://extensions/ 中点击 “检查视图” → “popup.html”

2.5.3 调试 Content Scripts

  1. 打开目标网页
  2. F12 打开 DevTools
  3. 在 Console 面板顶部的下拉框选择你的扩展上下文
  4. Sources 面板中 Content Scripts 在 “Content scripts” 分组下

2.5.4 调试 Options / Side Panel

  1. 打开 Options 页面或 Side Panel
  2. 右键 → “检查” 即可

2.6 使用构建工具

对于真实项目,建议使用构建工具来打包扩展。

使用 Vite 构建

# 创建项目
npm create vite@latest my-extension -- --template vanilla-ts
cd my-extension

# 安装 vite-plugin-web-extension
npm install -D @crxjs/vite-plugin

vite.config.ts 配置:

import { defineConfig } from 'vite';
import { crx } from '@crxjs/vite-plugin';
import manifest from './manifest.json';

export default defineConfig({
  plugins: [crx({ manifest })],
  build: {
    outDir: 'dist',
    sourcemap: true
  }
});

使用 webpack 构建

npm init -y
npm install -D webpack webpack-cli copy-webpack-plugin

webpack.config.js 配置:

const path = require('path');
const CopyPlugin = require('copy-webpack-plugin');

module.exports = {
  mode: 'development',
  devtool: 'cheap-module-source-map',
  entry: {
    'background/service-worker': './src/background/service-worker.js',
    'content/content': './src/content/content.js',
    'popup/popup': './src/popup/popup.js',
    'options/options': './src/options/options.js'
  },
  output: {
    path: path.resolve(__dirname, 'dist'),
    clean: true
  },
  plugins: [
    new CopyPlugin({
      patterns: [
        { from: 'manifest.json', to: 'manifest.json' },
        { from: 'icons', to: 'icons' },
        { from: 'popup/popup.html', to: 'popup/popup.html' },
        { from: 'options/options.html', to: 'options/options.html' },
        { from: '_locales', to: '_locales' }
      ]
    })
  ]
};

2.7 业务场景

场景一:团队项目结构

company-extension/
├── src/
│   ├── background/
│   │   ├── service-worker.ts      # 入口
│   │   ├── message-handler.ts     # 消息路由
│   │   └── api-client.ts          # API 调用
│   ├── content/
│   │   ├── injector.ts            # 注入逻辑
│   │   ├── ui-component.ts        # DOM 组件
│   │   └── styles/
│   ├── popup/
│   │   ├── Popup.tsx              # React 组件
│   │   └── index.html
│   ├── shared/
│   │   ├── constants.ts
│   │   ├── types.ts
│   │   └── storage.ts
│   └── manifest.json
├── tests/
├── scripts/
│   └── build.ts
├── package.json
├── tsconfig.json
└── vite.config.ts

场景二:多入口扩展

一个扩展包含多个独立功能模块,每个模块有自己的 Content Script:

{
  "content_scripts": [
    {
      "matches": ["*://github.com/*"],
      "js": ["content/github-enhancer.js"],
      "run_at": "document_idle"
    },
    {
      "matches": ["*://stackoverflow.com/*"],
      "js": ["content/so-helper.js"],
      "run_at": "document_idle"
    },
    {
      "matches": ["<all_urls>"],
      "js": ["content/universal.js"],
      "run_at": "document_start",
      "all_frames": true
    }
  ]
}

2.8 扩展阅读