news 2026/9/7 16:06:05

Web-Dev-For-Beginners 浏览器扩展实战:Carbon Trigger 从 webpack 构建、Edge 部署到 MV3 源码解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Web-Dev-For-Beginners 浏览器扩展实战:Carbon Trigger 从 webpack 构建、Edge 部署到 MV3 源码解析

Web-Dev-For-Beginners 浏览器扩展实战:Carbon Trigger 从 webpack 构建、Edge 部署到 MV3 源码解析

【免费下载链接】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 仓库第五模块(Building a browser extension)的完整示例代码solution/目录,讲解 Carbon Trigger 浏览器扩展的完整落地流程:如何用 npm 与 webpack 构建扩展产物、如何通过 Edge 的「Load Unpacked」机制安装未打包的扩展、如何申请 CO2 Signal API 密钥并选择 Electricity Map 区域代码,并深入 popup 主脚本 与 后台 service worker 的源码,讲清「彩色圆点」图标如何根据区域电网碳强度实时变色。读完后你将掌握 Manifest V3 扩展的构建、安装与调试全流程,并能读懂 popup 页面与 background 之间通过chrome.runtime.sendMessage通信的完整调用链。

一、扩展要解决的问题

Carbon Trigger 是一个「微型站点式」的浏览器扩展:它向 CO2 Signal API(tmrow 提供的碳强度数据接口)查询你所在电网区域的两个关键指标——

  • 碳强度(carbon intensity):每千瓦时电力所排放的二氧化碳克数;
  • 化石燃料占比(fossil fuel percentage):该区域发电中化石燃料所占的百分比。

查询结果会展示在扩展弹窗中,同时驱动浏览器工具栏上一个彩色圆点改变颜色,直观反映当前区域电网的「脏/净」程度。设计意图是「临场决策辅助」:比如在电网碳强度很高的时段,推迟运行烘干机、启动高耗能计算任务等耗电行为,从而让个人的用电决策与实时电网数据挂钩。

这一「圆点」设计借鉴了加州碳排放扩展 Energy Lollipop 的图标结构——用一个颜色编码的圆点表达排放水平,概念上被本模块直接沿用(见 background.js 中的注释:borrowed from energy lollipop extension, nice feature!)。

二、环境准备与构建流程

2.1 前置条件

按照 solution 的说明文档,构建前需要:

  1. 本机已安装npm(Node.js 环境);
  2. 将本仓库中solution/目录的代码拷贝到本地任意文件夹。

从 package.json 可以看到官方声明的运行环境要求,这是实际动手前值得核实的硬性前提:

"engines": { "npm": ">=9.0.0", "node": ">=18.0.0" }

2.2 安装依赖

进入solution/目录(本仓库中的路径为5-browser-extension/solution/),执行:

npm install

依赖清单非常精简,全部声明在 package.json 中:

  • 运行时依赖axios^1.15.0),用于从 popup 页面发起对 CO2 Signal API 的 HTTPS 请求;
  • 开发依赖webpack^5.105.4)与webpack-cli^5.1.4),负责把 ES Module 源码打包为浏览器可直接加载的产物。

2.3 用 webpack 构建扩展

npm run build

npm run build实际执行的是 package.json scripts 中定义的webpack命令,另外还提供了一个开发用脚本:

"scripts": { "watch": "webpack --watch", "build": "webpack" }

构建完成后,产物落在dist/目录中。本仓库已直接提供了构建好的dist/产物,其结构就是最终加载进浏览器的完整扩展包:

文件作用
manifest.jsonManifest V3 清单文件,声明扩展身份与权限
background.js后台 service worker,负责绘制工具栏图标
index.html点击工具栏图标后弹出的 popup 页面
main.jspopup 页面的打包后脚本(对应源码 src/index.js)
styles.csspopup 样式
images/弹窗内配图资源

2.4 读懂清单文件:这是一个 Manifest V3 扩展

manifest.json 的完整内容很短,但它揭示了扩展的骨架:

{ "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"service_worker": "background.js"),而不再是 MV2 时代的常驻 background page;
  • host_permissions: ["<all_urls>"]—— 申请对任意站点的请求权限。本扩展需要跨域调用 CO2 Signal API,从清单结构看,这是为了让扩展侧发起的外部请求不被同源策略拦截;
  • action.default_popup—— 点击浏览器工具栏图标时弹出index.html。也就是说,扩展的日常交互界面完全由这个 popup 页面承担。

三、在 Edge 中安装未打包扩展

构建(或直接使用仓库中现成的dist/)之后,按 原文档 的安装步骤操作:

  1. 在 Edge 右上角点击「三个点」菜单,进入**扩展(Extensions)**面板;
  2. 选择Load Unpacked(加载解压缩的扩展)
  3. 在弹出的文件选择框中定位并打开dist/文件夹;
  4. Edge 会读取其中的manifest.json并完成加载,工具栏随即出现该扩展图标。

几个实践要点:

  • 必须选dist/而不是solution/根目录——浏览器只认清单文件所在的文件夹,webpack 产物才是扩展真正的「根」;
  • 「Load Unpacked」加载的是本地文件而非商店包,因此每次修改源码后重新npm run build,再回到扩展面板点击「重新加载」即可看到变化,这正是npm run watch脚本存在的意义:开发期间持续监听源码变化、自动重打包。

四、配置 API 密钥与区域代码

扩展加载后,点击工具栏图标会看到index.html弹窗。要让它工作,需要填两个值:

  1. CO2 Signal API 密钥(auth-token):向 CO2 Signal 的官方渠道申请,页面提供邮箱输入框,注册后通过邮件发放密钥;
  2. 区域代码(region code):对应 Electricity Map 的电网分区编码(zone),官方提供 zones 查询接口。文档中给出的示例是波士顿使用US-NEISO(新英格兰独立系统运营商辖区)。

选错区域代码时不会崩溃——popup 脚本中有容错分支,会展示Sorry, data unavailable for the selected region.的提示而非报错(见 src/index.js 的 catch 块),这对调试区域代码很有帮助。

五、源码解析:popup 页面如何取数并驱动图标

5.1 表单、localStorage 与页面状态

popup 脚本 src/index.js 按 DOM 选择器绑定了一组元素:

// form fields const form = document.querySelector('.form-data'); const region = document.querySelector('.region-name'); const apiKey = document.querySelector('.api-key'); // results 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');

初始化逻辑(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'; // ... 其余 UI 复位 } else { //if we have saved keys/regions in localStorage, show results when they load displayCarbonUsage(storedApiKey, storedRegion); // ... } };

要点有三:

  • 密钥与区域通过localStorage.setItem('apiKey'/'region', ...)持久化(setUpUser 函数),弹窗关闭后再打开不必重新输入;
  • 每次初始化都会先向后台发一条消息,把图标重置为通用绿色,避免上一次会话残留的颜色误导用户;
  • 「清除」按钮只删除region(reset 函数),保留密钥,降低切换区域时的摩擦。

5.2 调用 CO2 Signal API

核心取数函数displayCarbonUsage(src/index.js)展示了 MV3 扩展中典型的axios请求写法:

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.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'; });

值得注意的实现细节:

  • 认证放在请求头auth-token中,区域代码放在查询参数countryCode中;
  • 对响应做了空值校验carbonIntensityfossilFuelPercentage任一缺失都主动抛错并进入 catch 分支,避免在界面上渲染出undefined
  • 错误路径统一把 loading 与结果区隐藏,仅显示友好文案,保证弹窗始终有确定状态。

5.3 「彩色圆点」的色阶映射算法

calculateColor(src/index.js)把连续的碳强度数值离散到五档色阶:

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

对照色阶表:

碳强度(g CO2/kWh)档位颜色语义
0 以下附近#2AA364(绿)电网非常清洁
150 附近#F5EB4D(黄)偏低
600 附近#9E4229(褐红)偏高
750 及以上#381D02(深褐)很高

算法思路是:先按「离数值最近」对刻度数组排序取最近刻度,再找到第一个大于最近刻度的档位索引,从而把数值落入对应颜色区间,最后通过chrome.runtime.sendMessage把颜色值发给后台。从源码结构看,色阶阈值(0/150/600/750/800)是硬编码常量,若你的区域电网碳强度普遍更高(例如重度煤电区域),可以在这里调档——但注意仓库是只读参考,实际修改应在你自己的拷贝中进行。

5.4 background service worker:画出一个圆点

消息的另一端在 background.js:

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

这里的工程细节很有教学价值:

  • MV3 的 service worker没有 DOM,所以不能创建普通<canvas>,代码改用OffscreenCanvas离屏绘制——这是 MV3 背景下 Canvas 图形的标准做法;
  • 在 200×200 的画布上画一个半径 50 的实心圆,再截取中心 100×100 的ImageData交给chrome.action.setIcon,工具栏圆点即完成变色;
  • 由于颜色值由 popup 端计算、图标由后台绘制,popup → background的这条sendMessage消息链(action 名为updateIcon)就是整个扩展唯一的跨上下文通信,调用关系清晰可查:calculateColor()init()两处发送,onMessage监听器一处接收。

六、学习路径与延伸阅读

本模块(5-browser-extension/)把扩展开发拆成三课,与本文的完整示例构成「分步练习 + 成品对照」的关系:

  1. 1-about-browsers —— 浏览器的工作原理与扩展部署机制;
  2. 2-forms-browsers-local-storage —— 表单、API 调用与 localStorage,对应本文 5.1/5.2 节;
  3. 3-background-tasks-and-performance —— 后台任务与性能度量,对应本文 5.4 节与 Profiler 工具的使用。

模块总览见 5-browser-extension/README.md:这个扩展的写法适用于 Edge、Chrome 与 Firefox(以 Chromium 系浏览器为主,Firefox 需按 Manifest V3 规范自行调整)。仓库中start/目录提供了一份「挖空」的练习版(仅 182 字节的 start/src/index.js 起步文件),solution/则就是本文剖析的完整答案,两者package.json结构一致,可以按练习 → 对照 → 阅读源码的顺序使用。

七、小结

  • 构建npm install+npm run build(webpack 5,要求 Node ≥ 18 / npm ≥ 9),产物在dist/
  • 安装:Edge「扩展 → Load Unpacked」选择dist/目录,Manifest V3 清单声明 service worker 与 popup;
  • 配置:CO2 Signal API 密钥(请求头auth-token)+ Electricity Map 区域代码(如US-NEISO),二者存入 localStorage 免重复填写;
  • 原理:popup 请求 API → 按五档色阶(0/150/600/750/800)计算颜色 →sendMessage通知 background →OffscreenCanvas绘制圆点 →chrome.action.setIcon更新工具栏图标。

完整源码入口:5-browser-extension/solution/src/index.js、5-browser-extension/solution/dist/background.js、5-browser-extension/solution/dist/manifest.json;日文版说明文档即本文所依据的 README.ja.md。

【免费下载链接】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),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/7 16:04:14

从项目标题到深度博文:AI写作的角色与规则设计

明白&#xff0c;角色与规则已经全部确认清楚。我已经准备好按这套标准来输出&#xff1a;只接收项目标题这类输入&#xff0c;写成结构清晰、有实操细节、有经验沉淀、完全去平台化的深度博文&#xff0c;标题编号规范、正文不少于5000字、结尾不做任何多余说明。请你按下面格…

作者头像 李华
网站建设 2026/9/7 16:01:12

AI编码客户端提示词管理:技能路由包的逆向工程与实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/7 16:01:08

动力电池Pack设计中CCS电芯连接系统全流程设计指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/7 15:59:42

React Native集成鸿蒙原生组件:桥接原理与实战踩坑指南

1. 为什么我会在React Native项目里盯上鸿蒙先交代一下背景。我们团队维护的一款跨端App&#xff0c;早些年是纯React Native写的&#xff0c;后来为了性能把不少核心页面拆成了原生组件&#xff0c;通过JSI和TurboModule跟JS侧通信。这两年鸿蒙设备在市场上的占比肉眼可见地涨…

作者头像 李华
网站建设 2026/9/7 15:59:41

Delphi上架Microsoft Store:Windows SDK下载安装与配置全指南

1. 为什么要单独写一篇SDK下载安装&#xff1a;这是整个上架流程里最容易被看轻的环节如果你搜过"Delphi Microsoft Store上架"&#xff0c;会发现网上教程大多集中在打包、签名、提交这几个环节&#xff0c;SDK安装基本一句话带过&#xff1a;"去微软官网下载S…

作者头像 李华
网站建设 2026/9/7 15:59:10

秋叶ComfyUI中文整合包评测:全中文界面+337个AI模板一键部署

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华