Web-Dev-For-Beginners 浏览器扩展第 5 课:用 webpack + CO2 Signal API 构建 Carbon Trigger 浏览器扩展(starter 代码实战指南)
本文基于仓库中 Carbon Trigger 浏览器扩展的 starter 代码说明展开,讲解如何从零构建一个调用 CO2 Signal API、实时反映本地区电网碳排放强度的 Edge/Chrome 扩展:包括 npm 依赖安装、webpack 构建打包、以“Load Unpacked”方式加载未打包扩展,以及如何配置 API 密钥与区域代码。读完后你能够独立完成扩展的构建、加载与配置全流程,并从仓库源码理解其表单提交、本地存储与动态图标变色(“碳点”)的实现原理。
一、项目定位:为什么要做这个浏览器扩展
Carbon Trigger 是 Web-Dev-For-Beginners 课程第 5 课(Building a browser extension)的核心实战项目。根据 starter 代码文档 的说明:
- 扩展使用 tmrow 的 CO2 Signal API 追踪电力使用情况,把“你所在地区的电网碳排放有多重”作为一个常驻提醒直接显示在浏览器里;
- 用户以“随时查看”(ad hoc)的方式使用它,据此判断当前时段是否适合执行高耗能活动(例如课程 README 中举例:在地区用电碳排放高峰时段推迟使用烘干机这类高碳排电器);
- 扩展在 Edge、Chrome、Firefox 上均可运行,本质是一个“为特定任务定制的迷你网站”。
模块整体由三节课程组成(见 5-browser-extension/README.md):
- About the browser——浏览器如何工作、如何部署扩展;
- Forms, browsers and local storage——构建表单、调用 API、使用 localStorage;
- Background tasks and performance——后台任务与性能测量。
starter 目录(5-browser-extension/start/)就是第 2 节的起步代码:一个带编号注释的空壳 src/index.js,供学习者按编号分段填写代码:
//1
// form fields
// results divs
//6
//call the API
//5
//set up user's api key and region
//4
// handle form submission
//3 initial checks
//2
// set listeners and start app
这些编号注释对应课程文档中的代码段://1 处放 DOM 引用(.form-data、.api-key、.result-container 等 querySelector),//2 处放事件监听与 init() 启动,//3 放初始化检查(读取 localStorage 判断是否首次用户),//4 放表单提交处理,//5 放 API 密钥与区域配置,//6 放真正的 API 调用。这个“按编号填空”的结构是仓库对初学者友好的关键设计——完整填好的参考实现位于 solution/src/index.js。
二、环境准备与构建:npm install / npm run build
starter 文档的“Getting Started”给出了完整的最小操作链,这里结合 start/package.json 补充各步骤的含义与版本前提。
2.1 前置条件
- 计算机上已安装 npm;
- 将
start/目录的代码拷贝到本地一个文件夹中。
从 package.json 的 engines 字段可确认运行环境要求:npm >= 9.0.0、Node.js >= 18.0.0,低于此版本的工具链不应作为前提。
2.2 安装依赖
npm install
该命令依据 package.json 安装两类依赖:
| 依赖 | 版本 | 用途 |
|---|---|---|
| webpack | ^5.105.0 | 将 ES module 源码(含 axios 的 import)打包为浏览器可直接加载的脚本 |
| webpack-cli | ^5.1.4 | 提供 webpack 命令行入口,供 npm run build / npm run watch 调用 |
| axios | ^1.15.0 | 运行时依赖,solution 中用它发起对 CO2 Signal API 的 HTTP GET 请求 |
2.3 构建扩展
npm run build
package.json 中的 scripts 定义如下:
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"watch": "webpack --watch",
"build": "webpack"
}
npm run build执行一次webpack,产物输出到dist目录(文档中“Open the 'dist' folder at the prompt”即指该目录);npm run watch开启监听模式,适合边写代码边热更新;test脚本当前是占位实现(无测试用例,exit 1),说明仓库并未为扩展提供自动化测试。
starter 与 solution/package.json 的依赖结构完全一致(同样的 engines、scripts、axios 依赖),差异只在 webpack 的补丁版本号,两者是同一套脚手架的“待填写版”与“完成版”。
三、在 Edge 中加载未打包扩展(Load Unpacked)
starter 文档给出的安装步骤是理解浏览器扩展部署流程的核心,完整继承如下:
- 在 Edge 浏览器右上角打开“三个点”菜单,找到 Extensions(扩展)面板;
- 在面板中选择 Load unpacked(加载未打包的扩展);
- 在弹出的目录选择框中打开构建产物
dist文件夹,扩展即被加载。
这一步之所以要求先 npm run build,是因为扩展入口必须引用 webpack 打包后的脚本——源码里 import axios from 'axios' 这类 ES module 语法与多文件依赖不能直接交给扩展宿主运行,必须由 webpack 合并成自包含的 bundle。从 solution/src/index.js 的第一行 import axios from 'axios' 即可印证:不经打包,浏览器扩展上下文无法解析这个模块。
四、配置运行:API 密钥与区域代码
扩展加载后并不能直接用。starter 文档明确列出两项必需配置:
- CO2 Signal API 密钥:在 CO2 Signal 官网通过邮箱注册获取(文档说明是在该页面输入邮箱领取);
- 区域代码:对应 Electricity Map 的地区编码,可查询 Electricity Map 的区域列表接口
api.electricitymap.org/v3/zones获得。文档以作者所在的波士顿为例,使用的区域代码是US-NEISO(新英格兰独立系统运营商区域)。
把这两项填入扩展界面的表单后,扩展栏中的彩色圆点会随你所在地区的能源使用强度变化而变色,提示用户当前时段哪些高能耗活动“合适”、哪些应当推迟。文档同时交代了这一“dot”(圆点)系统的灵感来源:加州排放量的 Energy Lollipop 扩展的图标配色机制。
从源码结构看,区域代码的取值直接决定 API 查询参数:solution/src/index.js 中请求以 params: { countryCode: region } 传给 https://api.co2signal.com/v1/latest,因此区域代码填错(比如误填城市名)会直接导致查询失败,进入 catch 分支显示“Sorry, data unavailable for the selected region.”的报错文案。
五、源码级纵深:starter 编号注释背后跑通了什么
结合 solution/src/index.js 的完整实现,可以看清 starter 中每段编号注释对应的真实逻辑:
5.1 DOM 引用(对应 //1)
const form = document.querySelector('.form-data');
const region = document.querySelector('.region-name');
const apiKey = document.querySelector('.api-key');
const errors = document.querySelector('.errors');
const loading = document.querySelector('.loading');
const results = document.querySelector('.result-container');
const usage = document.querySelector('.carbon-usage');
const fossilfuel = document.querySelector('.fossil-fuel');
const myregion = document.querySelector('.my-region');
const clearBtn = document.querySelector('.clear-btn');
十个 querySelector 把表单输入区(区域、API 密钥)与结果展示区(碳强度、化石燃料占比、加载/错误状态)全部固化为常量引用,后续所有 UI 状态切换都围绕它们的 style.display 与 textContent 展开。
5.2 本地存储驱动的初始化(对应 //3、//5)
init() 是应用启动入口:
const init = async () => {
const storedApiKey = localStorage.getItem('apiKey');
const storedRegion = localStorage.getItem('region');
//set icon to be generic green
chrome.runtime.sendMessage({
action: 'updateIcon',
value: { color: 'green' },
});
if (storedApiKey === null || storedRegion === null) {
// 首次用户:显示表单
form.style.display = 'block';
// ...
} else {
// 回头用户:直接拉取数据
displayCarbonUsage(storedApiKey, storedRegion);
clearBtn.style.display = 'block';
}
};
关键行为:用 localStorage 中是否同时存在 apiKey 与 region 两个键来区分首次用户与回头用户;首次访问展示表单,回头访问则跳过表单直接发起 API 调用。reset() 只清除 region 键并重新执行 init(),让用户可以重新选择地区。课程文档(2-forms-browsers-local-storage/README.md)特别强调:扩展拥有与网页隔离的独立 localStorage,且在 DevTools 的 Application 面板下可以查看这些数据。
5.3 API 调用与数据校验(对应 //6)
const displayCarbonUsage = async (apiKey, region) => {
try {
await axios
.get('https://api.co2signal.com/v1/latest', {
params: { countryCode: region },
headers: { 'auth-token': apiKey },
})
.then((response) => {
const data = response?.data?.data;
// ✅ Validate required data before using
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 / fossilfuel / myregion 等 UI
});
} catch (error) {
console.warn('Data fetch failed:', error.message);
errors.textContent = 'Sorry, data unavailable for the selected region.';
}
};
这段代码集中体现了本课程的几个知识点:auth-token 请求头完成 API 鉴权、async/await 保持扩展界面在等待网络期间不冻结、可选链 response?.data?.data 与空值校验防御异常响应、catch 分支把网络错误转换为用户可读的提示文案。
5.4 “碳点”变色机制(starter 文档的最后一块拼图)
starter 文档提到“彩色圆点会随地区能源使用变化”,其实现就在 calculateColor:
calculateColor = async (value) => {
let co2Scale = [0, 150, 600, 750, 800];
let colors = ['#2AA364', '#F5EB4D', '#9E4229', '#381D02', '#381D02'];
// 找到 co2Scale 中距离当前值最近的刻度,取对应颜色
chrome.runtime.sendMessage({ action: 'updateIcon', value: { color: closestColor } });
};
实现方式是“最近刻度映射”:把碳强度值对齐到 [0, 150, 600, 750, 800] 这一组刻度(单位是每千瓦时碳排放的克数),分别映射到绿、黄、棕、深棕等颜色,再通过 chrome.runtime.sendMessage 把颜色传给后台脚本去改写扩展图标。这正是 starter 文档中“extension bar 里的 dot 反映你地区的能源使用”的底层机制,也是课程第 3 节(background tasks)要深入讲的后台消息通道。
六、可验证的自检清单
按本文流程操作后,可逐项验证:
npm run build成功后出现dist目录——构建链正常;- Edge 中 Load unpacked 选择
dist后,扩展列表出现该扩展——加载成功; - 打开扩展弹出界面,未填过配置时显示 API 密钥与区域两个输入项——
init()走了首次用户分支; - 填入 API 密钥与区域代码(如
US-NEISO)提交后,界面显示碳强度(克/千瓦时)与化石燃料占比,扩展栏圆点变色——API 调用与calculateColor生效; - 关闭重开扩展,表单不再出现而直接进入结果页——
localStorage持久化生效。
七、小结
本文以 starter 文档 为主线,完整覆盖了这个浏览器扩展项目的实操路径:环境要求(npm >= 9 / Node >= 18)、npm install 与 npm run build(webpack 打包到 dist)、Edge 的 Load unpacked 加载方式、CO2 Signal API 密钥与区域代码(如波士顿的 US-NEISO)的获取与配置,以及彩色“碳点”的用户价值。配合 start 的空壳源码、solution 的完整实现 与 第 2 节课程文档,你可以从“会装、会用”进一步走到“看得懂每一行为什么这么写”的程度。
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

