Web-Dev-For-Beginners 实战解析:Carbon Trigger 碳足迹浏览器扩展(完整代码)的构建、安装与运行原理
【免费下载链接】Web-Dev-For-Beginners24 Lessons, 12 Weeks, Get Started as a Web Developer项目地址: https://gitcode.com/GitHub_Trending/we/Web-Dev-For-Beginners
本文以微软 Web-Dev-For-Beginners 开源课程第 5 模块《浏览器扩展》的解决方案代码为对象,深入讲解如何在 5-browser-extension/solution 目录下用 npm + webpack 构建并安装一个基于 CO2 Signal API 的「碳触发器」扩展。读完本文你将掌握:扩展的构建产物与加载方式、API Key 与区域代码的申请与填法、浏览器图标圆点颜色如何随区域碳强度变化,以及 popup 页面、localStorage 状态管理、service worker 后台脚本之间的完整调用链。
项目背景:一个「像迷你网站」的浏览器扩展
在 5-browser-extension/README.md 的模块说明中,该扩展被定位为「tailored to a very specific task 的迷你网站」:它调用 CO2 Signal API 查询指定区域的用电量与碳强度(carbon intensity),返回该区域的碳足迹读数,帮助用户在浏览器里就能判断此刻是否适合进行高耗能活动——例如在区域用电高峰时推迟开启烘干机这类「碳密集」活动。模块说明同时指出,该扩展面向 Edge、Chrome 与 Firefox 设计。
仓库中同一模块提供了两套代码:
- 5-browser-extension/start/:起步代码,start/src/index.js 中只保留 6 段带编号的 TODO 注释(表单字段、调用 API、设置用户 Key 与区域、处理表单提交、初始检查、绑定监听器并启动),供学习者按课程逐步填空;
- 5-browser-extension/solution/:本文主角——已完成的实现,核心逻辑位于 solution/src/index.js。
前置条件与构建命令
按 solution/README.md 的要求,本地需要先安装 npm,然后下载代码副本并执行构建:
npm install npm run buildnpm run build由 webpack 驱动。从 solution/package.json 可以看到工程环境约束与依赖:
- 引擎要求:
node >= 18.0.0、npm >= 9.0.0; - devDependencies:
webpack ^5.105.4、webpack-cli ^5.1.4; - dependencies:
axios ^1.15.0(用于请求 CO2 Signal API); - scripts:
build为webpack,另有watch(webpack --watch)便于开发期自动重建。
构建后 webpack 会把源码打包为dist/目录——仓库中已保留了构建产物 solution/dist/,内含浏览器扩展必需的manifest.json、background.js(service worker)、index.html(popup 弹窗)、styles.css与main.js。
在 Edge 中加载未打包的扩展
构建完成后,把dist文件夹加载进浏览器即可:
- 点击浏览器右上角的「三个点」菜单,找到并打开「扩展(Extensions)」面板;
- 打开「开发人员模式」后选择「加载解压缩的扩展(Load Unpacked)」;
- 在弹出的目录选择框中打开刚才构建生成的
dist文件夹,扩展即被加载。
扩展的入口身份由 solution/dist/manifest.json 声明,这是一个标准的 Manifest V3 配置:
{ "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" } }几个关键字段说明了扩展的底层架构:background.service_worker注册了常驻后台的background.js,用于接收消息并重绘工具栏图标;action.default_popup指向index.html,点击工具栏图标时弹出的设置/结果面板即来自该文件;host_permissions允许扩展向 API 主机发起请求。
使用前必需的两种配置:API Key 与区域代码
首次使用时必须在 popup 表单中填写两项数据(来源见 solution/README.md):
- CO2 Signal 的 API Key:在 CO2 Signal 官网页面输入邮箱即可申请,密钥会以邮件形式发放(原文 README 中保留了申请入口链接,可按原文档指引获取);
- 区域代码(Region Code):需要与 Electricity Map 对应的电力区域代码,可在其 zones 接口查询,例如美国波士顿地区使用
US-NEISO。
这两项分别对应 popup 表单中的Region Name与Your API Key from tmrow输入框。提交后,扩展会通过 CO2 Signal API 拉取该区域的实时数据,并把结果展示在界面上。
运行效果:看懂「圆点颜色」与结果数据
配置成功后,浏览器扩展栏上的彩色圆点会随之改变,用来直观反映当前区域的用电「重量级」;如果区域恰好处于高碳强度时段,你可以据此推迟高耗能活动。源码注释表明,这个「圆点系统」的创意借鉴了 Energy Lollipop 扩展对加州排放的可视化方式。
圆点的色彩是如何决定的?答案在 solution/src/index.js 的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 } }); };声明的刻度与色带对应关系如下(表中为代码中直接声明的数据):
| 碳强度刻度(gCO₂/kWh 附近) | 图标圆点颜色 | 色值 |
|---|---|---|
| 0 | 绿 | #2AA364 |
| 150 | 黄 | #F5EB4D |
| 600 | 红棕 | #9E4229 |
| 750 | 深棕 | #381D02 |
| 800 | 深棕 | #381D02 |
整体设计意图是:数值越靠近绿色刻度代表当前用电越「清洁」,越靠近深色刻度代表碳排放越重,从而给用户一个快速判断依据。值得留意的是,代码用sort原地排序后取最近刻度,再以findIndex找到第一个大于该刻度的位置来取色,属于一种精简的近似映射。
calculateColor并非直接画图,而是通过chrome.runtime.sendMessage把颜色消息发给后台。真正负责绘制圆点的是 solution/dist/background.js:它监听updateIcon消息,用OffscreenCanvas(200, 200)画一个实心圆并调用chrome.action.setIcon更新工具栏图标——即消息驱动 + 离屏 canvas 绘制的实现路径。
数据拉取与展示:一次完整的 CO2 Signal 调用
结果面板中的具体数据来自 solution/src/index.js 的displayCarbonUsage。它使用 axios 请求 CO2 Signal 的/v1/latest接口,把区域代码放进请求参数、API Key 放进认证头:
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); ... 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)'; });对应的结果展示区定义在 solution/dist/index.html 中,与上述字段一一对应:
Region(.my-region):当前查询的区域代码;Carbon Usage(.carbon-usage):碳强度,单位为「每千瓦时排放的 CO₂ 克数」;Fossil Fuel Percentage(.fossil-fuel):化石燃料发电占比百分比;- 另有
loading(加载中提示)、errors(错误信息)与clear-btn(更换区域按钮)等状态元素。
值得注意的健壮性设计:代码先校验carbonIntensity与fossilFuelPercentage是否存在,任一缺失即抛错并落入catch,在errors区显示「Sorry, data unavailable for the selected region.」并隐藏结果面板,避免把损坏数据渲染到界面。
状态持久化与完整启动流程
为了让用户不必每次打开都重复填写 Key 与区域,实现采用了 localStorage。整段生命周期逻辑也在 solution/src/index.js 中:
setUpUser(apiKey, region):把两项配置写入localStorage,显示 loading,立即发起首次数据请求;init():扩展每次打开时先读localStorage——若apiKey或region缺失则展示表单,反之直接调用displayCarbonUsage展示上次保存区域的结果,并默认先把工具栏图标置为绿色;reset():只移除localStorage中的region后重新init(),对应界面上「Change region」按钮的语义;- 事件绑定位于文件末尾:表单
submit走handleSubmit,「Change region」按钮的click走reset,随后执行init()完成冷启动。
从 solution/dist/styles.css 还能看到 popup 的最小宽度、深色模式(prefers-color-scheme)适配等细节,弹窗以紧凑卡片呈现,适配扩展按钮的小尺寸界面。
学习路径:如何从起步代码走到这份完整实现
若想亲手复现,可对照课程的渐进式任务展开:
- 先了解浏览器原理与扩展加载方式,对应 5-browser-extension/1-about-browsers/README.md;
- 掌握表单与 localStorage,对应 5-browser-extension/2-forms-browsers-local-storage/README.md;
- 理解后台任务与性能,对应 5-browser-extension/3-background-tasks-and-performance/README.md。
之后在 5-browser-extension/start/src/index.js 的编号 TODO 指引下依次补齐代码,最终即可与 solution/src/index.js 的实现逐段对照:从「选择表单/结果 DOM 元素」到「调用 API」「保存用户配置」「提交处理」「初始检查」再到「事件绑定与启动」。
小结
Carbon Trigger 是一个麻雀虽小、五脏俱全的浏览器扩展范例:它以 Manifest V3 为骨架,串起了 popup 表单、localStorage 持久化、axios 远程 API 调用、消息驱动的离屏 canvas 图标绘制以及异常兜底处理。对照 solution/ 下的完整代码与dist构建产物,即可把课程中关于「浏览器扩展」「表单」「本地存储」「后台任务」的知识点串联成一个真实可运行、可安装、可日常使用的碳足迹提醒工具。
【免费下载链接】Web-Dev-For-Beginners24 Lessons, 12 Weeks, Get Started as a Web Developer项目地址: https://gitcode.com/GitHub_Trending/we/Web-Dev-For-Beginners
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考