第 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_version | number | ✅ | 必须为 3 |
name | string | ✅ | 扩展名称,最多 45 字符 |
description | string | ✅ | 扩展描述,最多 132 字符 |
version | string | ✅ | 版本号,格式 1-4 个以 . 分隔的整数 |
version_name | string | ❌ | 显示版本名,如 “Beta” |
⚠️ 注意:
name和description可以使用__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×32 | Windows 计算机通常需要此尺寸 |
| 48×48 | 扩展管理页面、安装对话框 |
| 128×128 | Chrome 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
}
]
}
| 字段 | 类型 | 说明 |
|---|---|---|
matches | string[] | URL 匹配模式,使用 Match Patterns |
js | string[] | 注入的 JavaScript 文件列表 |
css | string[] | 注入的 CSS 文件列表 |
run_at | string | 注入时机:document_start / document_end / document_idle |
all_frames | boolean | 是否注入所有框架(含 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 加载未打包扩展
步骤
- 打开 Chrome,导航到
chrome://extensions/ - 确认右上角 “开发者模式” 已开启
- 点击 “加载已解压的扩展程序” 按钮
- 选择扩展项目根目录(包含
manifest.json的文件夹) - 扩展出现在列表中,说明加载成功
chrome://extensions/
┌─────────────────────────────────────────────────────────┐
│ [开启] 开发者模式 │
│ │
│ [加载已解压的扩展程序] [打包扩展程序] [更新] │
│ │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ 🔵 我的扩展 v1.0.0 [启用] [删除] │ │
│ │ ID: abcdefghijklmnopqrstuvwxyz │ │
│ │ 类型: 扩展程序 │ │
│ │ 检查视图: Service Worker [检查] │ │
│ └──────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────┘
常见加载错误
| 错误信息 | 原因 | 解决方案 |
|---|---|---|
Manifest is missing | 选择的目录不包含 manifest.json | 确认选择的是项目根目录 |
'manifest_version' is required | manifest.json 格式错误 | 检查 JSON 语法 |
Unrecognized permission | 使用了不存在的权限名称 | 查阅权限文档 |
Service worker registration failed | 路径或语法错误 | 检查 service_worker 路径和文件语法 |
2.4 重新加载扩展
开发过程中需要频繁重新加载扩展:
- 手动重载:在
chrome://extensions/页面点击扩展卡片上的 🔄 按钮 - 全局更新:点击页面顶部的 “更新” 按钮,更新所有扩展
💡 提示:使用扩展工具 Extensions Reloader 可以一键重载所有开发中的扩展。
文件变更与自动重载
| 文件类型 | 是否自动重载 | 操作 |
|---|---|---|
| manifest.json | ❌ | 需要手动重载 |
| Service Worker | ❌ | 需要手动重载 |
| Content Scripts | ❌ | 需要刷新目标页面 |
| Popup / Options | ✅ | 关闭后重新打开即可 |
| CSS(注入页面的) | ❌ | 需要刷新目标页面 |
2.5 调试扩展
2.5.1 调试 Service Worker
- 打开
chrome://extensions/ - 找到你的扩展,点击 “Service Worker” 链接
- 打开 DevTools → Console 面板查看日志
- 在 Sources 面板中可以设置断点
2.5.2 调试 Popup
- 右键点击扩展图标 → “检查弹出内容”
- 或在
chrome://extensions/中点击 “检查视图” → “popup.html”
2.5.3 调试 Content Scripts
- 打开目标网页
- F12 打开 DevTools
- 在 Console 面板顶部的下拉框选择你的扩展上下文
- Sources 面板中 Content Scripts 在 “Content scripts” 分组下
2.5.4 调试 Options / Side Panel
- 打开 Options 页面或 Side Panel
- 右键 → “检查” 即可
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
}
]
}