首页
/ Web-Dev-For-Beginners 的 Carbon Trigger 浏览器扩展:完整代码构建、Edge 加载与源码运行原理剖析

Web-Dev-For-Beginners 的 Carbon Trigger 浏览器扩展:完整代码构建、Edge 加载与源码运行原理剖析

2026-09-07 13:57:12作者:董斯意

本文基于本课程仓库 5-browser-extension 模块中已完成的 Carbon Trigger 浏览器扩展工程(对应文档为 5-browser-extension/solution/README.md 及其多语言翻译版本),完整讲解如何从源码构建一个可用的碳强度提示扩展:你既能按「npm 构建 → Edge 加载 → 填写 API Key」的路径立即跑通它,也能通过阅读 src/index.jsmanifest.jsonbackground.js 理解它背后的 Manifest V3 架构、CO2 Signal API 调用链与动态图标机制。读完你将掌握浏览器扩展「界面表单 + 本地存储 + 后台 Service Worker」的完整实现范式。

Carbon Trigger 浏览器扩展界面截图,展示区域碳强度与化石燃料占比的读取结果

一、扩展解决的问题:把区域电网的碳强度带进浏览器

Carbon Trigger 使用 tmrow 提供的 CO2 Signal API 追踪电力消耗数据,制作成一个运行在浏览器中的「碳提醒器」:它会实时告诉你当前所在区域电网的电力使用有多「重」,从而帮助你基于数据对高耗电活动做出判断。例如,当区域用电处于高峰、碳强度较高时,可以推迟运行烘干机这类高碳排活动。

其核心交互是一套「彩色圆点」图标系统——圆点颜色随区域碳强度变化,提示当前时段是否适合执行高能耗任务。这一设计概念源自面向加州排放量的 Energy Lollipop 浏览器扩展(这一点在 background.js 源码注释与模块 5-browser-extension/README.md 的致谢部分均有明确说明)。

在更宏观的课程层面,该扩展属于本仓库「Web-Dev-For-Beginners」课程的第 5 个模块「Building a browser extension」,整套课程的定位是 24 Lessons / 12 Weeks 的 Web 开发者入门路径。模块配套的三节理论课分别是:1-about-browsers2-forms-browsers-local-storage3-background-tasks-and-performance,依次覆盖浏览器原理、表单与本地存储、后台任务与性能评估——这些正是 Carbon Trigger 实现中用到的全部关键技术。

二、工程结构与运行环境

2.1 需要的前置条件

仓库的 package.json 明确声明了运行环境要求:

环境 最低版本
Node.js >= 18.0.0
npm >= 9.0.0

工程名为 carbon-trigger-extension,许可协议为 MIT。代码采用 ES Module 编写,由 webpack 负责打包。

2.2 依赖与脚本

依赖分为两类:运行时依赖 axios(用于发起 CO2 Signal API 请求),开发依赖 webpackwebpack-cli(负责打包)。脚本定义如下:

脚本 底层命令 用途
build webpack 一次性的正式构建
watch webpack --watch 监听源码变更并持续重建(开发调试用)

2.3 工程目录清单

solution 目录下源码与构建产物的组织方式为:

  • src/index.js —— 弹窗页面(popup)的全部业务逻辑源码,是 webpack 的打包入口;
  • package.json / package-lock.json —— npm 依赖与脚本声明;
  • translation/ —— 各语言的 README 翻译(如 README.ms.mdREADME.ja.md 等);
  • dist/ —— 构建产物目录,这是最终加载进浏览器扩展管理页的目录,内含 manifest.jsonbackground.js(Service Worker)、main.js(打包后的业务脚本)、index.html(弹窗页面)、styles.cssimages/ 资源;
  • 同模块还提供了未完成的练习起点工程 5-browser-extension/start,供读者对照本完整实现动手补全。

三、三步构建:install → build → Load Unpacked

3.1 安装依赖

先把本仓库的 solution 目录代码复制到本地计算机的一个文件夹中,然后在其中执行:

npm install

该命令根据 package.json 安装 axios、webpack 等全部依赖包。

3.2 用 webpack 构建扩展

npm run build

脚本等价于直接调用 webpack,将 src/index.js 等源码打包输出到 dist 目录。开发过程中若希望源码改动即时反映到产物,可改用 npm run watch 让 webpack 进入监听模式持续重建。

3.3 加载到 Edge

安装扩展时,点击浏览器右上角的「三点」菜单,找到扩展面板;在其中选择「Load Unpacked」(加载解压缩的扩展),然后在弹出的文件选择框中打开刚才构建生成的 dist 文件夹,扩展即被加载。从本仓库的 package.json 的 keywords(chrome extensionedge extension)和模块 README 看,该扩展基于 Chromium 体系开发,上述方式同样适用于 Chrome 的开发者模式。

在 Microsoft Edge 扩展管理页使用 Load Unpacked 加载 dist 构建产物的过程

3.4 加载后扩展在浏览器中的「身份」

构建产物中的 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"
	}
}

关键字段的工程含义:

  • manifest_version: 3:使用新一代 Manifest V3 规范,后台逻辑以 Service Worker 形式运行;
  • action.default_popup:点击工具栏图标时弹出的页面为 index.html
  • background.service_worker:后台脚本 background.js,负责接收弹窗发来的消息并动态绘制工具栏图标;
  • host_permissions: ["<all_urls>"]:声明对任意站点的访问权限,使扩展能够跨域请求 CO2 Signal API。

四、使用前必备:API Key 与区域代码

要让扩展真正返回数据,需要准备两项凭证:

  1. CO2 Signal API 的 API Key:前往 CO2 Signal 官网页面,在输入框中填写你的邮箱,官方会通过邮件把 Key 发给你;
  2. 区域(Zone)代码:需要与你所在地对应的电力区域编码。区域列表可参考 electricityMap 开放平台提供的 zones 接口数据,并与地图页面上标注的电网区域相互对应。以美国波士顿为例,文档中使用的是 US-NEISO

这两项凭证并非硬编码在源码中,而是在扩展弹窗表单里由用户首次填写。打开扩展后,弹窗页面(对应 dist/index.html)会显示一个表单,包含 Region NameYour API Key from tmrow 两个输入框和一个 Submit 按钮:

<form class="form-data" autocomplete="on">
	<div>
		<h2>New? Add your Information</h2>
	</div>
	<div>
		<label for="region">Region Name</label>
		<input type="text" id="region" required class="region-name" />
	</div>
	<div>
		<label for="api">Your API Key from tmrow</label>
		<input type="text" id="api" required class="api-key" />
	</div>
	<button class="search-btn">Submit</button>
</form>

两个输入框均带 required 约束;若填写合法,扩展会通过 Web Storage 把它们持久化下来(详见下文源码解析),这样下次打开浏览器时无需重复输入即可直接读取上次的区域数据。

五、源码级解析:从表单提交到结果展示的完整链路

src/index.js 是弹窗的全部逻辑。它的运行可拆成四条相互咬合的链路,与表单、结果容器等 DOM 元素(对应 dist/index.html 中的 form-dataloadingerrorsresult-container 区块)一一对应。

5.1 启动自检 init():决定「展示表单」还是「直接出数据」

const init = async () => {
	//if anything is in localStorage, pick it up
	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) {
		//if we don't have the keys, show the form
		form.style.display = 'block';
		results.style.display = 'none';
		loading.style.display = 'none';
		clearBtn.style.display = 'none';
		errors.textContent = '';
	} else {
		//if we have saved keys/regions in localStorage, show results when they load
		results.style.display = 'none';
		form.style.display = 'none';
		displayCarbonUsage(storedApiKey, storedRegion);
		clearBtn.style.display = 'block';
	}
};

启动时首先把图标恢复为默认的绿色,然后检查 localStorage 中是否已有 apiKeyregion:两者缺任一就显示配置表单;两者齐备则跳过表单,直接用历史值发起一次数据请求(这是「记忆上次配置」的用户体验来源)。

5.2 提交表单 setUpUser():写入本地存储并立即请求

const setUpUser = async (apiKey, region) => {
	localStorage.setItem('apiKey', apiKey);
	localStorage.setItem('region', region);
	loading.style.display = 'block';
	errors.textContent = '';
	clearBtn.style.display = 'block';
	//make initial call
	displayCarbonUsage(apiKey, region);
};

表单 submit 事件(由 handleSubmit 阻止默认刷新行为后触发)调用 setUpUser:把 Key 与区域写入 localStorage,显示 loading 状态,紧接着发起首次数据请求。

5.3 请求数据 displayCarbonUsage():CO2 Signal API 的真实调用

这是整个扩展的数据核心,通过 axios 携带认证头与区域参数请求 CO2 Signal 接口:

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);

				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 (error) {
		console.warn('Data fetch failed:', error.message);
		loading.style.display = 'none';
		results.style.display = 'none';
		errors.textContent = 'Sorry, data unavailable for the selected region.';
	}
};

值得注意的实现细节:

  • 请求参数:区域通过 params.countryCode 传递,API Key 通过请求头 auth-token 传递,即上文提到的 US-NEISO 一类区域代码直接作为 countryCode 使用;
  • 响应校验:取 response.data.data 后,先校验 carbonIntensity(碳强度)与 fossilFuelPercentage(化石燃料占比)是否缺失,任一缺失立即抛出异常,避免后续对空值做运算;
  • 单位说明:碳强度展示为「每千瓦时发电产生的 CO2 克数」,如 173 grams (grams C02 emitted per kilowatt hour);化石燃料占比保留两位小数展示为百分比;
  • 失败兜底:网络失败或区域无数据时,隐藏 loading 与结果区,并在 errors 区块显示「Sorry, data unavailable for the selected region.」。

5.4 换区域 reset():只清区域、保留 Key

结果区块下方有一个 Change region 按钮,点击后 reset 只删除 region 这一个键并重新执行 init(),从而让表单重新出现但不必再次填写 API Key——对需要跨区域对比数据的用户来说相当贴心。

5.5 彩色圆点如何映射碳强度

成功取数后 displayCarbonUsage 会调用 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];
	//console.log(value + ' is closest to ' + closestNum);
	let num = (element) => element > closestNum;
	let scaleIndex = co2Scale.findIndex(num);

	let closestColor = colors[scaleIndex];
	//console.log(scaleIndex, closestColor);

	chrome.runtime.sendMessage({ action: 'updateIcon', value: { color: closestColor } });
};

可以理解为一套分档调色板,用五档阈值锚点(0、150、600、750、800 gCO₂/kWh 量级)与五档颜色构成近似程度匹配,最终把图标调成对应的绿(#2AA364)、黄(#F5EB4D)或深棕(#9E4229/#381D02)等颜色,并以 chrome.runtime.sendMessage({ action: 'updateIcon', ... }) 发送消息交给后台 Service Worker 处理。绿→黄→棕的递进直观对应「当前电网清洁 → 中度 → 重度高碳」,也就是用户判断是否适合执行高耗电任务的依据。

六、后台 Service Worker:动态绘制工具栏圆点图标

弹窗向后台发送 updateIcon 消息后,由 background.js 完成图标的实际绘制。它用 OffscreenCanvas 画一个纯色圆形,再通过 chrome.action.setIcon 设置为工具栏图标,其代码开头的注释「borrowed from energy lollipop extension, nice feature!」也印证了文档中 Energy Lollipop 的设计借鉴关系:

chrome.runtime.onMessage.addListener(function (msg, sender, sendResponse) {
	if (msg.action === 'updateIcon') {
		chrome.action.setIcon({ imageData: drawIcon(msg.value) });
	}
});
//borrowed from energy lollipop extension, nice feature!
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);
}

该机制的价值在于:图标不用预生成一组静态 PNG,而是按需在后台用 Canvas 动态绘制任意颜色,代码量与资源占用都极小——这正呼应了模块第三课 3-background-tasks-and-performance 关于后台任务与性能的主题。整个链路可概括为:

  1. 弹窗 src/index.js 完成 API 请求与碳强度计算;
  2. 通过 chrome.runtime.sendMessage 把目标颜色发给 Service Worker;
  3. Service Worker 用 OffscreenCanvas 画出圆点并调用 chrome.action.setIcon 更新工具栏图标;
  4. 用户无需打开弹窗,仅看工具栏圆点颜色即可感知当前区域碳强度。

七、交互状态机与用户体验设计小结

综合 dist/index.htmlsrc/index.js 的可见逻辑,弹窗存在三种明确的状态:未配置(显示表单)加载中(loading 提示)已出结果(显示区域、碳强度、化石燃料占比 + Change region 按钮),以及一种异常状态(错误提示文案)。各状态通过 style.displayformloadingerrorsresult-containerclear-btn 五个元素间切换,代码在 src/index.js 末尾以两个事件监听收束:

form.addEventListener('submit', (e) => handleSubmit(e));
clearBtn.addEventListener('click', (e) => reset(e));

//start app
init();

八、实践建议与进一步学习

若想在真实环境中运行本扩展验证上述全部机制,只需按文档路径操作:将本仓库的 solution 目录下载到本地后依次执行 npm installnpm run build,在 Edge(或 Chrome)扩展管理页启用开发者模式并「Load Unpacked」选择 dist 文件夹,最后用邮箱领取 CO2 Signal API Key 并填入与你所在电网区域匹配的 Zone 编码即可。仓库只读,以上操作均在你本地副本中完成,不会改动仓库内容。

若想亲手从零实现而非直接使用完整代码,可对照同模块的练习起点工程 5-browser-extension/start,并依次学习模块配套课程:

  1. 浏览器基础:理解扩展运行的宿主环境与页面生命周期;
  2. 表单与本地存储:对应本扩展的 setUpUser/reset 的 localStorage 读写实践;
  3. 后台任务与性能:对应 Service Worker 绘制图标与接口调用的效率考量。

这套「小型专用网站式扩展」的架构——表单采集配置、本地持久化、HTTP 拉取第三方数据、消息驱动后台换肤——是浏览器扩展开发中可复用的标准范式,Carbon Trigger 的完整代码即为该范式最直观的落地样板。

登录后查看全文
热门项目推荐
相关项目推荐