从零到上架:Chrome 新标签页生产力扩展 FocusTab

从零到上架:Chrome 新标签页生产力扩展 FocusTab

本文记录了 FocusTab 这款 Chrome 扩展从需求构思、技术实现到打包上架 Chrome Web Store 的完整过程。全程零框架、零依赖,纯 HTML/CSS/JavaScript 实现,希望能给想开发 Chrome 扩展的同学提供一份实战参考。

一、为什么要做 FocusTab

每次打开新标签页,面对的要么是空白页,要么是被各种资讯流分散注意力。作为一个经常需要管理每日任务、追踪工作进度的开发者,我想要的新标签页应该是一个生产力仪表盘 ------ 打开浏览器的那一刻,就能看到今天要做什么、做了什么、专注了多久。

于是 FocusTab 诞生了。它将新标签页替换为一个集成了待办清单、番茄钟计时器、日报记录和周报汇总的一站式生产力工具。

二、功能全览

FocusTab 的功能分布在三个界面:新标签页全屏仪表盘工具栏弹出窗口(Popup)后台 Service Worker

2.1 新标签页仪表盘

这是用户打开新标签页时看到的主界面,采用毛玻璃(Glassmorphism)设计风格:

  • 动态问候语:根据时间段显示 Good Morning / Good Afternoon / Good Evening
  • 任务进度环:实时显示今日任务完成率,100% 时触发彩纸庆祝动画
  • 番茄钟计时器:可自定义专注时长和休息时长,圆环进度动画
  • 日报面板:自动同步任务系统中完成的事项,支持手动记录、明日计划、备注和心情
  • 周视图:按周导航,纵览每天的工作汇总
  • 任务管理:支持优先级(P0-P3)、截止日期、分类标签、拖拽排序、搜索过滤

点击工具栏图标弹出的 420×540 像素紧凑视图,通过 5 个 Tab 页组织功能:

Tab 功能
Tasks 任务增删改查、导入导出、拖拽排序
Daily Log 日报记录、历史回顾、数据导出
Pomodoro 番茄钟计时器(专注/休息模式)
Reports 今日报告 + 本周报告
Settings 通知推送时间配置、测试通知

2.3 后台 Service Worker

通过 chrome.alarms 每分钟检查一次,在用户设定的时间自动推送日报和周报系统通知。通知内容包含任务概览、优先级分布、标签分布等统计信息,支持中英文双语。

2.4 其他亮点

  • 中英文双语:运行时一键切换,所有界面文案即时更新
  • 亮/暗主题:CSS 变量驱动,切换无闪烁
  • 完全离线 :零网络请求,所有数据存储在本地 chrome.storage.local
  • 零依赖:不使用任何前端框架和第三方库,整个扩展打包体积仅 56 KB

三、技术架构

3.1 项目结构

python 复制代码
FocusTab/
├── chrome-extension/           # Chrome 扩展核心
│   ├── manifest.json           # Manifest V3 配置
│   ├── newtab.html             # 新标签页(含内联 CSS,672 行)
│   ├── newtab.js               # 新标签页逻辑(1249 行)
│   ├── popup.html              # 弹出窗口(含内联 CSS,812 行)
│   ├── popup.js                # 弹出窗口逻辑(1612 行)
│   ├── background.js           # Service Worker(421 行)
│   └── icons/                  # 扩展图标(16/48/128px)
├── privacy-policy.html         # 隐私政策页面
├── chrome-store-assets/        # 商店素材图片
└── FocusTab-v1.0.0.zip         # 上架用打包文件

3.2 Manifest V3 配置

json 复制代码
{
  "manifest_version": 3,
  "name": "FocusTab - New Tab Todo & Pomodoro",
  "description": "New tab productivity dashboard with todo list, Pomodoro timer, daily/weekly reports, dark mode & bilingual support.",
  "version": "1.0.0",
  "action": {
    "default_popup": "popup.html",
    "default_title": "FocusTab"
  },
  "chrome_url_overrides": {
    "newtab": "newtab.html"
  },
  "background": {
    "service_worker": "background.js"
  },
  "permissions": ["storage", "alarms", "notifications"],
  "commands": {
    "_execute_action": {
      "suggested_key": { "default": "Alt+T" },
      "description": "Open FocusTab popup"
    }
  }
}

几个关键设计决策:

使用 Manifest V3 而非 V2。 Chrome 已明确 Manifest V2 将在 2024 年底全面淘汰,新扩展必须使用 V3。V3 的主要变化是将 Background Page 替换为 Service Worker,后者不常驻内存、按需唤醒,对性能更友好。

权限最小化原则。 FocusTab 仅申请了三个权限:storage(存储数据)、alarms(定时器)、notifications(系统通知)。没有使用 tabshistory<all_urls> 等敏感权限,这不仅是出于隐私考量,也有利于通过 Chrome Web Store 审核。

快捷键注册。 通过 commands 字段注册 Alt+T 快捷键,用户无需移动鼠标即可快速打开 Popup。

3.3 数据存储方案

扩展使用 chrome.storage.local 作为唯一存储方案,所有数据键值如下:

存储键 内容 使用场景
todo-app-data 任务数组 newtab / popup / background 三端共享
focustab-dailylog 日报数据(按日期索引) newtab / popup 读写
focustab-pomo-settings 番茄钟配置 newtab / popup 读写
focustab-settings 通知推送时间 popup 写入 / background 读取
focustab-last-notify 上次通知时间戳 background 防重复推送
todo-lang 语言偏好 (en/zh) 三端共享
todo-theme 主题偏好 (dark/light) newtab / popup

选择 chrome.storage.local 而非 localStorage 的原因:

  1. Service Worker 中无法访问 localStorage(无 DOM 环境)
  2. chrome.storage.local 支持异步读写,不会阻塞主线程
  3. 多个页面(newtab、popup、background)之间数据自动同步
  4. 提供 onChanged 事件监听,可以实时响应数据变化

3.4 国际化(i18n)实现

没有使用 Chrome 扩展的 _locales 目录方案,而是采用运行时 JavaScript 字典替换。每个 JS 文件头部定义完整的双语字典:

javascript 复制代码
const i18n = {
  zh: {
    greeting_morning: '早安',
    greeting_afternoon: '午安',
    greeting_evening: '晚安',
    add_placeholder: '添加新任务...',
    // ... 100+ 条文案
  },
  en: {
    greeting_morning: 'Good Morning',
    greeting_afternoon: 'Good Afternoon',
    greeting_evening: 'Good Evening',
    add_placeholder: 'Add a new task...',
    // ...
  }
};

切换语言时,遍历所有带 data-i18n 属性的 DOM 元素,替换 textContentplaceholder。这种方案的好处是切换即时生效,无需刷新页面。

3.5 番茄钟状态机

番茄钟实现了一个简单的状态机,包含专注(Focus)、短休息(Short Break)和长休息(Long Break)三个状态:

arduino 复制代码
┌─────────┐    完成     ┌──────────────┐    完成     ┌─────────┐
│  Focus  │ ────────>  │  Short Break │ ────────>  │  Focus  │
│ (25min) │            │   (5min)     │            │ (25min) │
└─────────┘            └──────────────┘            └─────────┘
     │                                                  │
     │ 每 4 轮                                          │
     v                                                  │
┌──────────────┐                                        │
│  Long Break  │ ───────────────────────────────────────┘
│  (15min)     │                      完成
└──────────────┘

时长可在设置中自定义,进度通过 SVG 圆环动画实时展示。

3.6 拖拽排序

任务列表支持拖拽排序,使用原生 HTML5 Drag & Drop API 实现:

javascript 复制代码
item.setAttribute('draggable', 'true');

item.addEventListener('dragstart', e => {
  e.dataTransfer.setData('text/plain', index);
  item.classList.add('dragging');
});

item.addEventListener('dragover', e => {
  e.preventDefault();
  const dragging = document.querySelector('.dragging');
  const siblings = [...list.querySelectorAll('.todo-item:not(.dragging)')];
  const nextSibling = siblings.find(s => {
    return e.clientY <= s.getBoundingClientRect().top + s.offsetHeight / 2;
  });
  list.insertBefore(dragging, nextSibling);
});

item.addEventListener('drop', e => {
  // 更新数据顺序并持久化
});

没有使用 Sortable.js 等库,因为在扩展这种轻量场景下,原生 API 完全够用,且省去了引入外部依赖的打包体积。

3.7 后台通知系统

Service Worker 是整个通知系统的核心。由于 Manifest V3 的 Service Worker 不常驻内存,需要通过 chrome.alarms 实现定时唤醒:

javascript 复制代码
// 创建每分钟触发一次的定时器
chrome.alarms.create('focustab-notify-check', { periodInMinutes: 1 });

// 监听定时器触发
chrome.alarms.onAlarm.addListener(async (alarm) => {
  if (alarm.name !== 'focustab-notify-check') return;
  
  const now = new Date();
  const settings = await chrome.storage.local.get('focustab-settings');
  
  // 检查是否到达日报推送时间
  if (isTimeToNotify(now, settings.dailyTime)) {
    await sendDailyNotification();
  }
  
  // 检查是否到达周报推送时间(仅周日)
  if (now.getDay() === 0 && isTimeToNotify(now, settings.weeklyTime)) {
    await sendWeeklyNotification();
  }
});

通知内容会自动汇总当天的任务完成情况,包括已完成数、待完成数、逾期任务等,并根据用户的语言设置生成中文或英文的通知文案。

四、上架 Chrome Web Store 全流程

开发完成后,将扩展发布到 Chrome Web Store 需要经过以下步骤。这个过程中有不少坑,下面逐一说明。

4.1 注册开发者账号

前往 Chrome Web Store Developer Dashboard,用 Google 账号登录后需要支付一次性 $5 注册费 。注册完成后,务必在「帐户」分页中填写并验证联络电子邮件地址,否则后续无法发布。

4.2 打包扩展

Chrome Web Store 要求上传 .zip 文件,且 manifest.json 必须在 zip 的根目录:

bash 复制代码
# 正确的打包方式:进入扩展目录内部打包
cd chrome-extension
zip -r ../FocusTab-v1.0.0.zip . -x "*.DS_Store"

# 错误的打包方式:从外部打包会多一层目录
zip -r FocusTab-v1.0.0.zip chrome-extension/
# 这样 manifest.json 的路径会变成 chrome-extension/manifest.json,审核会被拒

这是第一个容易踩的坑:zip 内的目录结构必须让 manifest.json 处于根层级。

4.3 description 字符限制

manifest.json 中的 description 字段有 132 字符的严格上限。如果超出,上传时会直接报错:

資訊清單中的 description 欄位過長 (168 字元)。這個欄位已超過 132 字元的上限。

这个限制在 Chrome 官方文档中不太显眼,建议提前控制描述长度。最终精简后的描述:

sql 复制代码
New tab productivity dashboard with todo list, Pomodoro timer, daily/weekly reports, dark mode & bilingual support.

共 115 字符,保留了所有核心卖点。

4.4 准备商店素材图片

Chrome Web Store 要求提供以下图片素材,尺寸要求非常严格(像素级精确):

素材类型 尺寸要求 是否必填
螢幕截圖 1280×800 或 640×400 必填(至少 1 张)
小型宣傳圖塊 440×280 选填
跑馬燈宣傳圖塊 1400×560 选填

我使用了 Playwright 截图的方案:先用 HTML/CSS 还原真实 UI,然后通过 Playwright 以精确的视窗尺寸截图。这样既能保证像素级精确,又能避免手动截图时的尺寸偏差。

javascript 复制代码
const { chromium } = require('playwright');

(async () => {
  const browser = await chromium.launch();
  const context = await browser.newContext({
    viewport: { width: 1280, height: 800 },
    deviceScaleFactor: 1  // 注意:必须设为 1,否则会生成 2x 分辨率的图片
  });
  const page = await context.newPage();
  await page.goto(`file://${__dirname}/screenshot.html`);
  await page.screenshot({ path: 'screenshot-1280x800.png' });
  await browser.close();
})();

这里有个小坑:deviceScaleFactor 默认可能是 2(Retina 屏幕),生成的图片会是 2560×1600,上传会因尺寸不符被拒。

4.5 填写权限理由

Chrome Web Store 要求对每个权限写明使用理由。审核团队会根据理由判断权限是否必要,不必要的权限会导致审核被拒:

权限 理由
storage Used to save user-created tasks, daily logs, Pomodoro timer settings, language and theme preferences locally in the browser. All data is stored via chrome.storage.local and never transmitted externally.
alarms Used to schedule a periodic check (once per minute) that determines whether it is time to send the user a daily or weekly report notification based on their configured notification time.
notifications Used to send local system notifications reminding the user to review their daily log or weekly summary report at the time they have configured in settings.

4.6 远端程式码声明

Chrome 会要求声明是否使用了「远端程式码」(Remote Code),即任何不打包在扩展内的 JS 或 Wasm 代码。这包括 <script> 标记中的外部引用、指向外部的模块导入,以及通过 eval() 执行的字符串。

FocusTab 完全不使用远端程式码,所有 JavaScript 都打包在扩展内,因此选择「不,我没有使用远端程式码」,并在理由中说明:

FocusTab runs entirely offline. All JavaScript code is bundled within the extension package. There are no external script references, no remote module imports, and no use of eval() or similar dynamic code evaluation.

4.7 隐私权政策

如果扩展使用了 storage 等权限,Chrome Web Store 可能要求提供隐私权政策网址。即使扩展不收集任何用户数据,也建议准备一份隐私政策页面。

我创建了一个简洁的 privacy-policy.html,核心声明三点:

  1. 不收集任何个人信息
  2. 所有数据存储在本地 chrome.storage.local
  3. 零网络请求,完全离线运行

然后通过 GitHub Pages 托管这个页面,将网址填入 Chrome Web Store 的隐私权政策栏位。部署步骤:

bash 复制代码
# 初始化仓库并推送到 GitHub
git init
git add .
git commit -m "Initial commit: FocusTab Chrome extension with privacy policy"
git remote add origin git@github.com:username/focustab.git
git branch -M main
git push -u origin main

在 GitHub 仓库的 Settings → Pages 中,选择 Deploy from a branch,分支选 main,目录选 / (root),等待 1-2 分钟即可通过 https://username.github.io/focustab/privacy-policy.html 访问。

4.8 提交审核

以上所有信息填写完毕后,点击「储存草稿」确认无误,再点击「提交审核」。Chrome Web Store 的审核周期通常为 1-3 个工作日。审核通过后,扩展就会出现在 Chrome 应用商店中。

五、踩坑总结

回顾整个开发到上架的过程,这里汇总遇到的所有坑点:

坑点 现象 解决方案
zip 打包目录结构 上传后提示找不到 manifest.json 在扩展目录内部执行 zip -r,确保 manifest.json 在 zip 根目录
description 超长 上传报错:超过 132 字元上限 精简到 132 字符以内,保留核心卖点
截图 deviceScaleFactor 生成的图片尺寸是要求的 2 倍 将 Playwright 的 deviceScaleFactor 设为 1
未验证邮箱 点击发布时提示「无法发布」 在帐户分页中填写并验证联络电子邮件
GitHub Pages 404 隐私政策网址返回 404 需要在仓库 Settings → Pages 中手动启用
Service Worker 不常驻 定时任务不执行 使用 chrome.alarms 替代 setInterval

六、写在最后

FocusTab 的整个技术栈可以用一句话概括:纯原生 HTML + CSS + JavaScript,零框架零依赖,Manifest V3。全部代码约 4000 行,打包体积 56 KB,却实现了待办清单、番茄钟、日报周报、双语、双主题、拖拽排序、系统通知等完整功能。

对于 Chrome 扩展这种轻量级应用场景,我的建议是:不要过度工程化。React/Vue 当然好用,但引入框架意味着需要构建工具、打包配置、更大的体积,以及可能出现的 CSP(内容安全策略)兼容问题。原生 JavaScript 足以应对绝大多数扩展的需求,而且代码更直观、调试更简单。


项目地址github.com/Mythetic/fo...

如果这篇文章对你有帮助,欢迎点赞收藏。有问题欢迎评论区交流 👋

相关推荐
敲代码的约德尔人2 小时前
我在 3 个项目中踩坑后,才真正理解了 JavaScript 设计模式
前端·javascript
子淼8122 小时前
Kali Linux 入门指南:基础操作与常用指令解析
前端
QYR市场调研2 小时前
低密度聚乙烯市场竞争格局变化趋势
前端
学以智用2 小时前
Vue 3 组件完全指南
前端·vue.js
重庆穿山甲2 小时前
Java开发者的大模型入门:AgentScope Java组件全攻略(一)
前端·后端
LawrenceLan3 小时前
36.Flutter 零基础入门(三十六):StatefulWidget 与 setState 进阶 —— 动态页面必学
开发语言·前端·flutter·dart
ego.iblacat3 小时前
Web 技术与 Nginx 网站环境部署
运维·前端·nginx
ricky_fan3 小时前
(已解决)安装openclaw龙虾[特殊字符]npm权限问题EACCES
前端·npm·node.js
专业流量卡3 小时前
让小龙虾给我写文章
前端