Carbon Trigger 浏览器扩展完整实现解析:基于 CO2 Signal API 的电力碳排放强度提醒工具
本篇技术指南以 Web-Dev-For-Beginners 仓库第 5 课(Browser Extension)的完整解答代码为对象,围绕 5-browser-extension/solution/README.md 所描述的 Carbon Trigger 扩展,从环境准备、依赖安装、Webpack 构建、Edge 加载,到源码逐段解读的完整链路展开讲解。读者读完可以独立把该扩展从零构建并安装到自己的浏览器中,同时理解 Manifest V3 扩展中“前端弹窗 + localStorage 状态 + Service Worker 后台绘制图标 + 第三方 API 请求”这一套典型协作模式。
一、扩展要做什么:让浏览器的“小圆点”提示你当前区域用电强度
Carbon Trigger 是一个浏览器扩展示例,它的核心创意是:利用 tmrow 提供的 CO2 Signal API 追踪电力使用情况,在浏览器扩展栏常驻一个彩色圆点,让你在浏览网页时随时感知所在区域发电产生的碳强度(carbon intensity),从而帮助用户对“要不要现在跑视频转码、要不要开启大型下载”这类高耗能活动做出更环保的判断。
该“彩色圆点”交互理念源自面向加州排放场景的 Energy Lollipop 扩展。示例中默认的经典场景是位于波士顿(区域码 US-NEISO)的用户,但任何在 Electricity Map 覆盖区域 内的地区理论上都可以使用对应区域码。整个示例遵循课程“三节课完成一个扩展”的脉络:浏览器工作原理、表单与本地存储、后台任务与性能,最后在本目录汇聚为完整可运行实现。
二、前置环境要求:Node 与 npm
在开始之前,需要确认本机已经安装了 npm。仓库在 5-browser-extension/solution/package.json 的 engines 字段明确声明了版本下限:
"engines": {
"npm": ">=9.0.0",
"node": ">=18.0.0"
}
也就是说,建议使用 Node.js 18.0.0 及以上、npm 9.0.0 及以上的运行环境,否则可能出现 Webpack 构建或依赖安装的兼容性问题。可用以下命令核对当前环境:
node -v
npm -v
除了运行环境外,由于本示例是练习性质、涉及调用外部 API 服务,还需要两个账号层面的准备:一个 CO2 Signal API Key(用于鉴权请求碳强度数据),以及对应你所在地区的电力区域码(用于定位数据),这两项的具体获取方式见下文“六、获取 API Key 与配置区域码”。
三、获取代码、安装依赖并执行构建
在本地任意文件夹放置一份本课程代码后,按以下顺序执行:
1. 安装全部依赖
npm install
依据 package.json 的声明,该命令会安装两类包:
- 生产依赖:
axios(^1.15.0),用于向 CO2 Signal API 发起 HTTP 请求; - 开发依赖:
webpack(^5.105.4)与webpack-cli(^5.1.4),用于把src/下的源码打包成浏览器可直接加载的dist/产物。
2. 使用 Webpack 构建扩展
npm run build
该命令等价于直接运行 webpack,会读取 src/index.js 作为入口进行打包。开发调试阶段也可以使用增量监听模式:
npm run watch
对应 package.json 中 scripts 字段定义如下:
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"watch": "webpack --watch",
"build": "webpack"
}
3. 认识 dist 构建产物
构建完成后,会在 5-browser-extension/solution/dist 下生成浏览器可直接加载的一整套扩展文件:
| 文件 | 作用 |
|---|---|
manifest.json |
Manifest V3 清单,声明扩展名称、版本、权限、后台与弹窗 |
background.js |
Service Worker,负责监听消息并把碳强度映射为工具栏彩色圆点图标 |
index.html |
点击工具栏图标弹出的设置/结果页面 |
main.js |
Webpack 打包后的入口逻辑(对应 src/index.js) |
styles.css |
弹窗样式 |
images/ |
弹窗头部展示的插画资源 |
其中 dist/manifest.json 是典型的 MV3 结构:
{
"manifest_version": 3,
"name": "My Carbon Trigger",
"version": "0.1.0",
"host_permissions": ["<all_urls>"],
"background": {
"service_worker": "background.js"
},
"action": {
"default_popup": "index.html"
}
}
值得注意的三个关键点:
manifest_version: 3:采用 Manifest V3 规范,后台逻辑由 Service Worker 承载,与课程 3-background-tasks-and-performance 的内容一一对应;action.default_popup:指向index.html,即点击工具栏图标时弹出设置界面;host_permissions: ["<all_urls>"]:允许扩展向任意域名发起跨域请求,这是能调用 CO2 Signal API 的前提。
四、弹窗界面与表单结构
构建前的原始 HTML 位于课程各阶段练习中,而本目录的 dist/index.html 即打包后实际加载的弹窗页面。其结构包含两块核心区域:
第一块:首次使用时的信息录入表单
<form class="form-data" autocomplete="on">
<label for="region">Region Name</label>
<input type="text" id="region" required class="region-name" />
<label for="api">Your API Key from tmrow</label>
<input type="text" id="api" required class="api-key" />
<button class="search-btn">Submit</button>
</form>
两个输入框分别收集区域码与 CO2 Signal 的 API Key,均为必填项。该表单用于首次设置,用户只需要输入一次,后续会自动保存。
第二块:数据展示区
<div class="loading">loading...</div>
<div class="errors"></div>
<div class="result-container">
<p><strong>Region: </strong><span class="my-region"></span></p>
<p><strong>Carbon Usage: </strong><span class="carbon-usage"></span></p>
<p><strong>Fossil Fuel Percentage: </strong><span class="fossil-fuel"></span></p>
</div>
<button class="clear-btn">Change region</button>
其中 .loading 用于请求期间的加载提示,.errors 用于展示失败信息,.result-container 负责呈现区域、碳强度、化石燃料占比三项数据;“Change region”按钮允许用户清除保存的地区并重新设置。课程 2-forms-browsers-local-storage 正是围绕这套表单与存储逻辑展开讲解的。
五、核心逻辑逐段解析:状态、请求与图标
全部交互逻辑都集中在 solution/src/index.js,下面按执行时序拆解。
1. 选择 DOM 节点与声明辅助函数
文件开头用 document.querySelector 一次性抓取表单、结果区、错误区等所需节点,随后定义了 calculateColor——把“碳强度数值”映射成工具栏图标颜色的核心函数:
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 } });
};
其工作原理是把当前碳强度值与刻度数组 [0, 150, 600, 750, 800] 做最近邻匹配,得到索引后再查颜色表:
- 碳强度越接近 0(如
#2AA364绿色):电力相对清洁,适合进行高耗能活动; - 介于 150~600(如
#F5EB4D黄色):碳排放中等; - 达到 750~800(如
#9E4229、#381D02深棕褐色):化石燃料占比很高,应避免高耗能操作。
最终通过 chrome.runtime.sendMessage({ action: 'updateIcon', value: { color } }) 把颜色值投递给后台 Service Worker。
2. 请求碳强度数据
displayCarbonUsage(apiKey, region) 负责实际的数据获取与界面渲染,源码中真实请求的接口为 https://api.co2signal.com/v1/latest:
await axios
.get('https://api.co2signal.com/v1/latest', {
params: { countryCode: region },
headers: { 'auth-token': apiKey },
})
.then((response) => {
const data = response?.data?.data;
if (data?.carbonIntensity == null || data?.fossilFuelPercentage == null) {
throw new Error('Missing carbon intensity or fossil fuel data');
}
let CO2 = Math.floor(data.carbonIntensity);
calculateColor(CO2);
loading.style.display = 'none';
form.style.display = 'none';
myregion.textContent = region;
usage.textContent =
Math.round(data.carbonIntensity) + ' grams (grams C02 emitted per kilowatt hour)';
fossilfuel.textContent =
data.fossilFuelPercentage.toFixed(2) +
'% (percentage of fossil fuels used to generate electricity)';
results.style.display = 'block';
})
.catch(...);
实现要点:
- 区域码通过
params: { countryCode: region }传递,API Key 放在自定义请求头auth-token中——这与 CO2 Signal 官方鉴权方式一致; - 渲染前对返回数据做了存在性校验(
carbonIntensity与fossilFuelPercentage二者任一缺失即抛错),保证后续toFixed(2)、Math.round不会对空值操作; - 成功后隐藏 loading 与表单、显示结果容器,并以两行文本分别输出“每千瓦时排放的克数”与“发电所用化石燃料占比”;
- 失败时(如区域码无效、配额耗尽、网络异常)进入 catch 分支,隐藏 loading 与结果、在
.errors中展示提示语Sorry, data unavailable for the selected region.
3. 保存用户设置并触发首次请求
const setUpUser = async (apiKey, region) => {
localStorage.setItem('apiKey', apiKey);
localStorage.setItem('region', region);
loading.style.display = 'block';
errors.textContent = '';
clearBtn.style.display = 'block';
displayCarbonUsage(apiKey, region);
};
setUpUser 会把 API Key 与区域码写入 localStorage,这样用户关闭浏览器再打开后无需重复输入。它由表单的 submit 事件触发:
const handleSubmit = async (e) => {
e.preventDefault();
setUpUser(apiKey.value, region.value);
};
form.addEventListener('submit', (e) => handleSubmit(e));
4. 启动时的状态恢复:init
扩展每次弹出时都会执行 init(),它的职责是“根据 localStorage 是否存在历史配置,决定显示表单还是直接展示结果”:
const init = async () => {
const storedApiKey = localStorage.getItem('apiKey');
const storedRegion = localStorage.getItem('region');
chrome.runtime.sendMessage({ action: 'updateIcon', value: { color: 'green' } });
if (storedApiKey === null || storedRegion === null) {
form.style.display = 'block';
results.style.display = 'none';
...
} else {
results.style.display = 'none';
form.style.display = 'none';
displayCarbonUsage(storedApiKey, storedRegion);
clearBtn.style.display = 'block';
}
};
值得一提的细节:无论是否已有配置,init 都会先把图标重置为绿色(color: 'green'),避免用户看到上一次残留的颜色状态,随后若有缓存配置则直接调用 displayCarbonUsage 拉取最新数据。
5. 更换区域的清除逻辑
const reset = async (e) => {
e.preventDefault();
localStorage.removeItem('region');
init();
};
clearBtn.addEventListener('click', (e) => reset(e));
“Change region”按钮只移除 region 这一项而保留 apiKey,随后重新执行 init() 走“未配置”分支显示表单,实现只改区域、不重复填 Key 的体验。
6. 后台 Service Worker:把颜色画成小圆点
前端发来的 updateIcon 消息由 solution/dist/background.js 接收处理:
chrome.runtime.onMessage.addListener(function (msg, sender, sendResponse) {
if (msg.action === 'updateIcon') {
chrome.action.setIcon({ imageData: drawIcon(msg.value) });
}
});
function drawIcon(value) {
let canvas = new OffscreenCanvas(200, 200);
let 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);
}
它使用 OffscreenCanvas 在内存中绘制一个指定颜色的实心圆,再通过 chrome.action.setIcon 实时替换浏览器工具栏的扩展图标——这就是“圆点随碳强度变色”的实现源头。源码注释也标明该画法思路借鉴自 Energy Lollipop 扩展。这一节与课程 3-background-tasks-and-performance 中关于 Service Worker 与后台消息通信的知识点形成闭环。
六、获取 API Key 与配置区域码
扩展要想真正工作,必须完成两项外部配置:
- CO2 Signal API Key:前往 CO2 Signal 官网,在首页输入邮箱即可通过邮件获取一个免费 API Key。它对应源码中请求头里的
auth-token字段; - 区域码(zone code):区域码用于告诉 CO2 Signal 你要查询哪个电力市场/电网的数据。可以在 Electricity Map 官网地图上查看你所在区域对应的代码,其 API 层也提供可用区域清单接口
http://api.electricitymap.org/v3/zones。
以文档给出的波士顿为例,其区域码为 US-NEISO(新英格兰独立系统运营商)。如果你在北京、伦敦或东京等地区,需要先查证该区域是否被 CO2 Signal 数据源覆盖,并填写对应代码,否则会得到 data unavailable 的错误提示。需要注意的是:示例演示的 CO2 Signal 接口与鉴权格式以本仓库源码所写为准,若官方后续调整接口版本,需要相应更新 solution/src/index.js 中的 URL 与参数。
七、在 Edge(或其他 Chromium 内核浏览器)中加载安装
构建产物就绪后,按以下步骤把扩展加载进 Edge:
- 打开浏览器,点击右上角“三点”菜单,进入“扩展”面板;
- 打开页面左下角的**“开发人员模式”**开关;
- 点击**“加载解压缩的扩展”**(Load Unpacked);
- 在弹出的目录选择框中选中本项目的
dist文件夹,确认后扩展即加载成功; - 建议在扩展管理页点击“重新加载”图标进行刷新(本地修改源码重新 build 后同样需要刷新才能生效)。
由于该扩展基于 Manifest V3 开发,而 Chrome、Edge 等主流浏览器均遵循这一规范,理论上同一份 dist 产物也可通过 Chrome 的 chrome://extensions 开发者模式加载,只是本仓库文档以 Edge 作为演示环境。
八、使用效果与结果解读
安装并点击扩展图标后,在弹出的界面中输入 CO2 Signal API Key 与区域码并提交:
- 首次提交后,数据会写入
localStorage,之后每次打开浏览器弹出扩展都会自动刷新最新数据; - 工具栏上的扩展图标会立即变为当前区域的碳强度对应颜色:偏绿表示电力较清洁,偏黄/棕表示化石燃料占比高;
- 弹窗内会显示三项具体数据——区域码、碳强度(每千瓦时排放的克数)与化石燃料发电占比(%)。
借助这个“一瞥即知”的彩色圆点,用户可以在电力较清洁的时间段安排洗衣、充电、视频渲染等高耗能活动,从而把碳中和理念落到日常使用习惯中。文档特别声明,此“圆点系统”的设计灵感来自面向加州碳排放场景的 Energy Lollipop 扩展。
九、从练习版到完整版:继续深入学习的路径
本目录(solution/)代表“完整可运行代码”,而在课程体系中还有一个起步版 start/ 目录(见 5-browser-extension/start),二者都包含 src/、package.json 与 dist/ 骨架,适合学习者先自行编码再对照本解答。与之配套的三节理论课程分别是:
- 1-about-browsers:浏览器历史与扩展工作机制;
- 2-forms-browsers-local-storage:表单、浏览器存储与本扩展的表单-存储模块;
- 3-background-tasks-and-performance:Service Worker、后台任务与性能考量。
将本节源码与课程原文对照阅读,即可完整掌握“浏览器扩展 + 外部数据 API + 本地存储 + 后台图标刷新”这一条在真实 Web 开发中反复出现的工具链实现范式。需要说明的是,仓库中部分语言目录下的课程文档与图片说明由 AI 翻译生成,若个别表述存在歧义,应以英文原版文档为准。
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 StartedRust0627
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

