Web-Dev-For-Beginners Carbon Trigger 浏览器扩展:基于 CO2 Signal API 的完整实现与部署
本文围绕 Web-Dev-For-Beginners 课程第 5 模块(浏览器扩展项目)的"完成版代码"文档展开,介绍 Carbon Trigger 浏览器扩展的用途、构建与安装流程,并结合仓库中 solution/src/index.js 的真实源码,深入讲解数据获取、碳强度到颜色的映射、本地存储持久化以及跨脚本消息传递的完整实现链路。读完后你将能够独立构建、加载并理解一个可运行的 CO2 强度提醒扩展。
项目背景:把区域碳强度提醒装进浏览器
Carbon Trigger 是一个浏览器扩展:它调用 CO2 Signal API 跟踪电力消耗,在浏览器里直接提示"你所在区域的用电碳强度有多高"。文档给出的典型使用方式是"随手查一下"(ad hoc)——在需要时打开扩展,根据当前区域电网的碳强度情况,判断此时是否适合执行高耗电活动(例如文档提到,可以据此决定是否推迟开烘干机)。
扩展最直观的反馈是工具栏里的一个"彩色圆点":在扩展界面输入 API 密钥和区域代码后,圆点颜色会随区域能源使用情况变化,提示哪些高能耗活动当前"合适"、哪些应该延后。这个"圆点"指示器的概念,来源于面向加州排放数据的 Energy Lollipop 扩展(该出处记录在 5-browser-extension/README.md 的 Credits 部分)。
该扩展基于 Chromium 扩展 API 编写,因此可同时运行于 Edge、Chrome 等 Chromium 系浏览器(课程模块 5-browser-extension/README.md 说明它可在 Edge、Chrome 与 Firefox 上构建)。
环境要求与构建步骤
原始文档只要求"安装 npm 后执行两条命令",仓库中 solution/package.json 则给出了更精确的适用前提,可据此确认运行环境:
| 配置项 | 取值 | 说明 |
|---|---|---|
| node | >=18.0.0 |
engines 字段声明的最低 Node 版本 |
| npm | >=9.0.0 |
engines 字段声明的最低 npm 版本 |
| webpack | ^5.105.4(devDependency) |
用于打包扩展产物 |
| webpack-cli | ^5.1.4(devDependency) |
提供 webpack 命令行入口 |
| axios | ^1.15.0(dependency) |
扩展运行时用于请求 CO2 Signal API |
构建流程按 solution/README.md(以及其西语版本 solution/translation/README.es.md)描述如下:
- 将该目录下的代码下载一份到本地文件夹;
- 安装全部依赖:
npm install
- 使用 webpack 构建扩展:
npm run build
从 package.json 的 scripts 字段可以看到,build 对应的就是 webpack,另外还有一个 watch 脚本(webpack --watch),可在开发期间监听源码变更并重新打包。构建完成后,产物输出到 dist 目录,浏览器加载的就是这个目录。
在 Edge 中加载未打包扩展
扩展产物并不经过应用商店分发,而是以"未打包扩展"(Load Unpacked)方式加载。按文档步骤:
- 点击 Edge 右上角的"三个点"菜单,进入扩展功能(Extensions)面板;
- 选择"Load Unpacked"(加载已解压缩的扩展);
- 在弹出的目录选择框中打开构建生成的
dist文件夹,扩展即被加载。
一个实用提示(出自模块第 3 课 3-background-tasks-and-performance/README.md):如果修改了代码,需要重新执行 npm run build,并回到扩展面板手动重新加载扩展,改动才会生效。
使用配置:API 密钥与区域代码
扩展首次打开会显示一个表单,需要填入两项配置:
- CO2 Signal API 密钥:文档说明需通过 CO2 Signal 官方页面申请(在页面表单中填写邮箱获取),它会被写入请求头
auth-token发送给 API; - 区域代码:需对应 Electricity Map 的区域划分,可从其 zones 接口查询全部可用代码。文档给出的示例是:作者位于波士顿,使用
'US-NEISO'。
配置提交后,扩展会立刻发起一次数据请求并展示结果;两项配置会写入 localStorage,之后打开扩展无需重复填写。若所选区域没有可用数据,界面会显示"Sorry, data unavailable for the selected region."的提示(见下文错误处理小节)。
源码解析:主脚本的核心逻辑
仓库中 solution/src/index.js 是完成版扩展的主脚本(约 129 行),它承担表单处理、API 调用、颜色映射与状态恢复四件事。下面按数据流顺序拆解。
数据获取与校验:displayCarbonUsage
核心请求逻辑在 src/index.js#L34-L68。它使用 axios 发起 GET 请求:
await axios
.get('https://api.co2signal.com/v1/latest', {
params: { countryCode: region },
headers: { 'auth-token': apiKey },
})
即调用 CO2 Signal 的 v1/latest 接口,以 countryCode 查询参数传入区域代码,以 auth-token 请求头携带 API 密钥。请求成功后,代码先做防御性校验(L44-L47):carbonIntensity(碳强度,克 CO2/千瓦时)与 fossilFuelPercentage(化石燃料发电占比)任一为 null 就抛出异常,避免把残缺数据渲染到界面上。
校验通过后,界面显示两项关键指标(L55-L59):
- 碳强度:取整后展示,例如 "412 grams (grams C02 emitted per kilowatt hour)";
- 化石燃料占比:保留两位小数,例如 "63.45% (percentage of fossil fuels used to generate electricity)"。
请求失败时走 catch 分支(L62-L67):隐藏加载态与结果区,在 .errors 元素中写入"所选区域数据不可用"的提示,并输出 console.warn 便于调试。整个流程通过显式切换 loading / form / results 三个容器的 display 来管理 UI 状态。
碳强度到颜色的映射:calculateColor
calculateColor(src/index.js#L17-L32)把数值翻译成直观颜色,相当于一个"红绿灯"系统:
calculateColor = async (value) => {
let co2Scale = [0, 150, 600, 750, 800];
let colors = ['#2AA364', '#F5EB4D', '#9E4229', '#381D02', '#381D02'];
let closestNum = co2Scale.sort((a, b) => {
return Math.abs(a - value) - Math.abs(b - value);
})[0];
let num = (element) => element > closestNum;
let scaleIndex = co2Scale.findIndex(num);
let closestColor = colors[scaleIndex];
chrome.runtime.sendMessage({ action: 'updateIcon', value: { color: closestColor } });
};
分档规则(克 CO2/千瓦时)与颜色对应关系如下:
| 碳强度区间 | 对应档位值 | 颜色 | 含义 |
| --- | --- | --- |
| 0–150 | 0 / 150 | #2AA364(绿) | 清洁电力占比高 |
| 150–600 | 600 | #F5EB4D(黄) | 中等 |
| 600–750 | 750 | #9E4229(橙棕) | 偏高 |
| 750–800 | 800 | #381D02(深棕) | 非常高 |
| ≥800 | 800 | #381D02(深棕) | 非常高 |
实现上有两个值得注意的细节。其一是求"最近档位"的技巧:对 co2Scale 按"与目标值距离差"排序后取首元素(L21-L23),再配合 findIndex 找到该档位之后第一个更大的刻度作为 scaleIndex,从而从 colors 数组中取色。其二是取色后不做任何本地渲染,而是直接通过 chrome.runtime.sendMessage 把 { action: 'updateIcon', value: { color } } 消息发给后台脚本(L31)——因为只有后台脚本有能力修改浏览器工具栏图标。
用户配置持久化与初始化
setUpUser(L72-L80)在表单提交时把 apiKey 与 region 写入 localStorage,重置错误提示、显示加载态与"清除"按钮,并立即触发一次 displayCarbonUsage。
init(L89-L116)在脚本启动时执行两次检查:先从 localStorage 读取已保存的 apiKey 与 region,然后按两种分支处理——
init 还会发送一条默认消息,把图标先置为绿色(L95-L100),让用户在数据返回前就能看到扩展已在工作。
最后,脚本末尾(L125-L129)绑定 form 的 submit 与 clearBtn 的 click 监听器并调用 init() 启动应用。reset 函数(L118-L123)只清除 region 一项再重新 init(),把扩展还原到待配置状态。
彩色圆点:消息传递与后台图标渲染
主脚本只负责"算出颜色",真正把工具栏图标染色的工作在后台脚本中完成。按第 3 课 3-background-tasks-and-performance/README.md 的讲解,dist/background.js 中的监听器形如:
chrome.runtime.onMessage.addListener(function (msg, sender, sendResponse) {
if (msg.action === 'updateIcon') {
chrome.action.setIcon({ imageData: drawIcon(msg.value) });
}
});
// 用 OffscreenCanvas 绘制动态图标
function drawIcon(value) {
const canvas = new OffscreenCanvas(200, 200);
const context = canvas.getContext('2d');
context.beginPath();
context.fillStyle = value.color;
context.arc(100, 100, 50, 0, 2 * Math.PI);
context.fill();
return context.getImageData(50, 50, 100, 100);
}
完整链路为:主脚本取到 API 数据后调用 calculateColor(CO2) → chrome.runtime.sendMessage 发送颜色 → 后台脚本的 onMessage 监听器收到消息 → 用 OffscreenCanvas 在内存中画一个填充该颜色的圆形并取回 ImageData → chrome.action.setIcon 更新工具栏图标。选择 OffscreenCanvas 而非普通 Canvas,是为了不在 UI 线程上做绘制、避免阻塞交互(这一点在课程文档的"Performance Considerations"中被明确点出)。
chrome.runtime API 在其中扮演扩展的"神经系统"角色:它负责不同脚本上下文之间的消息传递、后台任务与生命周期事件,这正是"内容脚本算数据、后台脚本改图标"这一职责分离得以成立的基础。需要说明的是,仓库 solution 目录内提供的是主脚本 src/index.js 与构建配置,manifest、弹窗页面等扩展骨架部分未在仓库中完整给出,后台脚本的完整写法以第 3 课文档中的示例为准(从源码结构看,index.js 中所有 chrome.runtime 调用都假设后台侧存在对应的 onMessage 处理者)。
延伸学习与相关路径
- 本模块总览与项目出处(Green Principles 团队、Energy Lollipop 等):5-browser-extension/README.md
- 英文原版完成版说明:5-browser-extension/solution/README.md
- 完成版主脚本与构建配置:5-browser-extension/solution/src/index.js、5-browser-extension/solution/package.json
- 扩展骨架与表单、localStorage 的逐课讲解:5-browser-extension/2-forms-browsers-local-storage/README.md
- 性能剖析、动态图标与后台任务的完整教学:5-browser-extension/3-background-tasks-and-performance/README.md
综合来看,Carbon Trigger 虽然代码量不大,却完整覆盖了 Chromium 扩展开发的关键面:外部 API 鉴权与异步请求、UI 状态管理、localStorage 持久化、跨脚本消息传递以及 OffscreenCanvas 动态图标绘制。按文档给出的 npm install → npm run build → Load Unpacked 三步走,再结合对 solution/src/index.js 的上述解读,就可以完整复现并改造这个碳强度提醒扩展。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0624
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00

