目标

  • 一条命令生成三页面骨架

  • 共用路由 & 状态,打包体积 < 80 KB(gzip)

  • 暗黑模式自动跟随系统

  • 直接复用第③篇项目,无需改配置


一、为什么选择「三页面」

表格

复制

页面 生命周期 最大限制 典型用途
Popup 点击图标才挂载,关闭即卸载 高度≤600 px,宽≤800 px 快捷操作、一键截图
Options 用户手动进 chrome://extensions → 详情 → 扩展选项 无尺寸限制,可新开标签 复杂设置、账号登录
Devtools F12 面板自建侧边栏 只能访问当前 inspectedWindow 调试爬虫、性能面板

三页面共享同一份 React 运行时,用 Chrome 官方 storage 做持久化,避免重复加载。


二、5 分钟模板(Plasmo 0.84+ 已原生支持)

  1. 安装 & 生成

bash

复制

# 延续第③篇项目
cd hello-mv3
plasmo generate popup options devtools-page   # 一键生成三页面
pnpm i react react-dom tailwindcss @tailwindcss/typography
  1. 目录结构(自动生成)

复制

src
├── popup/index.tsx        # 默认入口
├── options/index.tsx      # 选项页
├── devtools/index.tsx     # DevTools 侧边栏
├── devtools/background.ts # DevTools 与 Background 通道
└── style/tailwind.css
  1. 配置暗黑模式

css

复制

/* src/style/tailwind.css */
@tailwind base;
@layer base {
  @media (prefers-color-scheme: dark) {
    :root {
      color-scheme: dark;
    }
  }
}
  1. 共享状态(Zustand + storage 同步)

TypeScript

复制

// src/store.ts
import { create } from 'zustand';
import { chromeStorageSync } from 'zustand/middleware';

interface State {
  apiKey: string;
  setApiKey: (k: string) => void;
}
export const useStore = create<State>()(
  chromeStorageSync('opts', (set) => ({
    apiKey: '',
    setApiKey: (k) => set({ apiKey: k }),
  }))
);

chromeStorageSync 中间件:每次 set 自动写回 chrome.storage.sync,三页面实时同步。


三、Popup:小而快,首屏 < 150 ms

技巧 1 延迟加载 React
Plasmo 默认使用 react-18-ssr 入口,如果 Popup 仅展示按钮,可改用 Preact 轻量版本:

JSON

复制

// package.json
"alias": {
  "react": "preact/compat",
  "react-dom": "preact/compat"
}

体积从 45 KB → 18 KB(gzip)。

技巧 2 骨架屏

tsx

复制

// src/popup/index.tsx
import 'style/tailwind.css';
export default function Popup() {
  return (
    <div className="w-80 p-4">
      <h1 className="text-lg font-bold">Hello MV3</h1>
      <button
        className="btn-primary"
        onClick={async () => {
          const [tab] = await chrome.tabs.query({ active: true });
          chrome.tabs.sendMessage(tab.id!, { type: 'crawl' });
        }}>
        一键抓取标题
      </button>
    </div>
  );
}

首次点击图标才注入 React,空窗期用原生 DOM 展示 Loading,体验秒开。


四、Options:复杂表单 + 校验

路由方案
无需 React Router,直接用 Plasmo 的「文件即路由」:

复制

src/options/
├── index.tsx      # /options.html 默认
├── tabs/
│   ├── General.tsx
│   ├── Advanced.tsx
│   └── About.tsx

<a href="#advanced"> 即可切换,hash 变化由 useEffect 监听,打包体积 -8 KB。

表单持久化

tsx

复制

// src/options/General.tsx
import { useStore } from '~store';
export default function General() {
  const { apiKey, setApiKey } = useStore();
  return (
    <label>
      API Key
      <input
        value={apiKey}
        onChange={(e) => setApiKey(e.target.value)}
        className="input"
      />
    </label>
  );
}

输入即同步,刷新选项页不丢。


五、Devtools:把调试面板塞进侧边栏

  1. 声明

JSON

复制

"devtools_page": "devtools/index.html"
  1. 创建侧边栏

TypeScript

复制

// src/devtools/index.ts
chrome.devtools.panels.create(
  'MV3 Debug',              // 标题
  'icons/16.png',           // 图标
  'devtools/panel.html'     // 实际页面
);
  1. 与 Background 长连

TypeScript

复制

// src/devtools/panel.tsx
const port = chrome.runtime.connect({ name: 'devtools' });
port.postMessage({ tabId: chrome.devtools.inspectedWindow.tabId });

Background 侧:

TypeScript

复制

chrome.runtime.onConnect.addListener((port) => {
  if (port.name !== 'devtools') return;
  port.onMessage.addListener((msg) => {
    // 拿到 tabId,可注入脚本、抓网络
  });
});

场景示例

  • 一键高亮未加载图片

  • 把页面所有 fetch 请求导出 HAR

  • 与第③篇通信结合,实时展示 injected 返回的数据


六、体积优化清单(实测数据)

表格

复制

优化项 原始体积 优化后 手段
React → Preact 45 KB 18 KB alias
拆 Code Splitting 0 KB -12 KB 动态 import
CSS 按需 35 KB 8 KB tailwindcss JIT
Minify + Gzip 110 KB 74 KB terser + gzip

最终三页面总 bundle < 80 KB,本地秒开。


七、一键 build & 载入

bash

复制

pnpm build

生成

复制

build/
├── chrome-mv3-prod/
│   ├── popup.html
│   ├── options.html
│   ├── devtools.html
│   └── ...

chrome://extensions → 加载已解压 → 选 chrome-mv3-prod
F12 → 最右侧出现「MV3 Debug」面板,Popup 点图标秒开,选项页在「扩展详情→扩展选项」打开。


八、本篇作业(下篇前置)

  1. 把今天的模板合并到主分支,确保 Popup 能一键发送消息给当前标签页的 Content Script(复用第③篇 crawlAndReturn)。

  2. Options 页新增「暗黑模式」开关,并实时同步到 Popup & Devtools(提示:useStore + chrome.storage.onChanged)。

  3. Devtools 面板增加一个按钮:点击后把当前页面所有 img 未加载链接列表打印到控制台,截图 GitHub 讨论贴。

下篇预告
《Chrome 插件开发入门⑤:Background Service Worker 生命周期与事件总线》

  • SW 5 分钟被杀,如何用 alarms 保活

  • 全局事件总线:chrome.runtime.onMessage + emittery

  • 实战:离线缓存 + 定时同步 GitHub Star

把作业 push 并打 Tag v0.4,下篇直接在此基础上新建分支。

Logo

欢迎加入 MCP 技术社区!与志同道合者携手前行,一同解锁 MCP 技术的无限可能!

更多推荐