
Web-Dev-For-Beginners 浏览器扩展模块:从 CO2 Signal API 调用、LocalStorage 持久化到动态工具栏图标的三步实战【免费下载链接】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-browser-extension模块,完整讲解如何从零构建一个可在 Edge、Chrome、Firefox 中运行的 Carbon Trigger 浏览器扩展:先用表单收集用户输入的地区代码与 CO2 Signal API Key,再通过 HTTP API 拉取该地区的实时碳强度与化石燃料占比,最后用chrome.runtime消息机制和OffscreenCanvas把结果绘制成工具栏上的彩色圆点。读完本篇,你将掌握 Manifest V3 扩展的项目结构、Webpack 构建流程、Load Unpacked 安装方式、LocalStorage 用户偏好持久化、API 鉴权调用与错误处理,以及基于浏览器 DevTools 的性能剖析方法。模块总览:一个迷你网站式的扩展5-browser-extension/README.md对这个模块的定位是:构建浏览器扩展是一种以特定任务为导向的迷你网站开发方式,在动手的同时训练你对 Web 应用性能的理解。该扩展的具体功能是:调用 CO2 Signal API 查询指定地区(以 Electricity Map 的地区代码标识,如波士顿地区使用US-NEISO)电网的电力消耗与碳强度;把结果以碳足迹读数的形式呈现给用户,并同步改变工具栏图标的颜色(即所谓的dot圆点系统,灵感来自面向加州排放数据的 Energy Lollipop 扩展);用户可按需(ad hoc)打开扩展做决策参考——模块给出的典型场景是:在所在地区电力碳强度较高的时段,推迟烘干衣物这类高碳排活动。模块由三节课程串联,对应5-browser-extension/README.md中的 Topics 列表:浏览器原理与扩展工程搭建 —— 浏览器架构、扩展安装与开发流程、双屏 UI 的 HTML 结构;表单、API 调用与 Local Storage —— DOM 引用、事件监听、表单提交拦截、本地持久化、async/await异步数据获取;后台任务与性能优化 —— 动态图标、后台 Service Worker 消息处理、DevTools Performance 剖析。每节课程都配有独立作业,如 为扩展重新设计样式、采用一个 API、分析一个站点的性能,可结合 start 目录 的练习代码与 solution 目录 的参考答案对照完成。工程结构与构建工具链仓库为该模块提供了两套代码:start目录是留给学生填空的练习脚手架,solution目录是完整可运行的参考实现。两者结构一致:5-browser-extension/ ├── start/ # 练习用脚手架(含占位注释) │ ├── dist/ # 构建产物 静态资源(扩展真正加载的目录) │ │ ├── manifest.json # 扩展清单(MV3) │ │ ├── index.html # 弹出层 UI │ │ ├── background.js # 后台 Service Worker │ │ ├── main.js # Webpack 打包后的主脚本 │ │ ├── styles.css # 弹出层样式 │ │ └── images/ │ ├── src/ │ │ └── index.js # 待实现的主脚本(带编号注释占位) │ └── package.json └── solution/ # 完整参考实现,目录结构同上 ├── src/index.js └── dist/start/package.json 定义了两个与构建相关的 npm 脚本与依赖:{ name: carbon-trigger-extension, engines: { npm: 9.0.0, node: 18.0.0 }, scripts: { watch: webpack --watch, build: webpack }, devDependencies: { webpack: ^5.105.0, webpack-cli: ^5.1.4 }, dependencies: { axios: ^1.15.0 } }要点有三:一是运行环境要求 Node.js 18 / npm 9 及以上;二是构建完全依赖 Webpack 的默认约定——入口为src/index.js,产物输出到dist/main.js(仓库中并未额外提交webpack.config.js,直接由npm run build触发默认打包);三是运行时代码引用了axios库发起 API 请求。扩展的身份证是 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 } }从这份清单可以读出扩展的完整骨架:host_permissions授予跨域请求权限(供调用 CO2 Signal API 使用);background.service_worker指向background.js,它负责不在弹出层中执行的后台工作(更新工具栏图标);action.default_popup指向index.html,即用户点击工具栏图标时弹出的界面。练习版 start/dist/background.js 只有两行注释占位(add listener here / draw the icon here),而 solution/dist/background.js 则补全了完整实现,这正是第三节课的实操内容。快速开始:安装、构建与加载前置条件与操作步骤在 start/README.md 中给出:# 1. 安装依赖(需要本机已安装 npm) npm install # 2. 用 Webpack 构建扩展 npm run build随后在 Edge 中通过右上角三点菜单进入扩展面板,选择 Load Unpacked 并打开dist文件夹,扩展即可加载。课程 1-about-browsers 补充了完整的开发循环:构建后若是首次安装选 Load Unpacked,若是已有版本则点 Reload;并强调测试自建扩展时要打开开发者模式。使用扩展前需要准备两样凭据:API Key:向 CO2 Signal 服务方申请(在 co2signal.com 页面输入邮箱即可获得);地区代码:对应 Electricity Map 的电网分区代码,例如波士顿地区为US-NEISO。课程中提醒:不要把 API Key 提交进代码仓库,本模块将其保存在 LocalStorage 属于教学简化手段,生产环境应使用服务端安全存储。弹出层 UI:配置表单与结果展示双屏扩展采用渐进披露的两屏设计:首次使用显示配置表单,配置完成后显示数据结果。参考实现 solution/dist/index.html 中的核心结构如下:!--form area-- form classform-data autocompleteon div h2New? Add your Information/h2 /div div label forregionRegion Name/label input typetext idregion required classregion-name / /div div label forapiYour API Key from tmrow/label input typetext idapi required classapi-key / /div button classsearch-btnSubmit/button /form !--result area-- div classresult div classloadingloading.../div div classerrors/div div classdata/div div classresult-container pstrongRegion: /strongspan classmy-region/span/p pstrongCarbon Usage: /strongspan classcarbon-usage/span/p pstrongFossil Fuel Percentage: /strongspan classfossil-fuel/span/p /div button classclear-btnChange region/button /div这套结构的每个类名都有明确职责:.form-data/.region-name/.api-key是脚本要操作的输入区;.loading在请求期间显示加载态;.errors承载面向用户的错误文案;.result-container内的三个span(.my-region、.carbon-usage、.fossil-fuel)是 API 数据的落点;.clear-btn允许用户清除已存地区、重新配置。表单的required属性让浏览器在提交前自动校验两个字段均已填写,autocompleteon则开启自动补全改善输入体验。弹出层脚本:DOM 引用、LocalStorage 与 API 调用5-browser-extension/start/src/index.js 用//1~//6的编号注释把实现切成了六个填空段落(表单字段引用、事件监听、初始化检查、提交处理、用户配置、API 调用),solution/src/index.js 则是这些段落的完整答案。按仓库中的实际实现,其逻辑链条如下。1. DOM 引用与事件绑定(solution/src/index.js第 4~15 行、第 125~129 行):// 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); form.addEventListener(submit, (e) handleSubmit(e)); clearBtn.addEventListener(click, (e) reset(e)); //start app init();2. 初始化与重置——用 LocalStorage 区分新用户与老用户(第 89~123 行)://initial checks 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 dont 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; } }; const reset async (e) { e.preventDefault(); //clear local storage for region only localStorage.removeItem(region); init(); };这里体现了 LocalStorage 的完整用法:键值对存取(getItem/setItem/removeItem)、跨会话持久化(关闭浏览器后数据仍在)、键不存在时返回null的判定语义。扩展的 LocalStorage 与普通网页相互隔离,可避免站点间数据冲突;在 DevTools 的 Application → Local Storage 面板中可以直接查看apiKey与region两个键。注意reset只清除region键,API Key 得以保留,再次进入表单时用户只需重新选择地区。3. 提交处理与用户配置(handleSubmit/setUpUser,第 72~86 行):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); }; // handle form submission const handleSubmit async (e) { e.preventDefault(); setUpUser(apiKey.value, region.value); };e.preventDefault()拦截了表单默认提交行为(否则会刷新弹出层页面、丢失全部 JS 状态),使扩展保持单页应用式的状态管理;随后一次性完成持久化凭据 显示加载态 清除旧错误 触发首次 API 调用四件事。4. 异步 API 调用(displayCarbonUsage,第 34~68 行),这是把扩展从静态工具变成实时数据工具的核心: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.; } };这段代码覆盖了 API 集成的标准要素:以查询参数countryCode指定地区、以请求头auth-token携带 API Key 鉴权、用async/await保持扩展在等待网络响应时不冻结、对 HTTP 失败与响应体字段缺失分别设防(可选链 显式判空抛错),最终把carbonIntensity(克 CO₂/千瓦时)与fossilFuelPercentage格式化后写入对应span。catch分支统一负责隐藏加载态 显示用户友好的错误提示。第二节课的 README 还讲解了对应的纯fetch写法与try/catch、加载态、错误态的通用设计思路,可对照学习。动态图标:消息传递 OffscreenCanvas第三节课的增量是把数值变成工具栏上的一枚彩点。solution/src/index.js#L17-L32 中的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 的绿色#2AA364(清洁)、150~600 的黄色#F5EB4D(中等)、600~750 的橙色#9E4229(偏高)、750 以上的深棕#381D02(很高),与init()中默认的green占位色共同构成红绿灯式的直观反馈。关键机制是扩展内部的消息传递:弹出层脚本与后台 Service Worker 处于不同的执行上下文,无法直接互调,chrome.runtime.sendMessage/chrome.runtime.onMessage就是二者间的神经系统。solution/dist/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); }这里有两个值得注意的实现选择:其一,图标不是预置图片而是运行时用 Canvas 2D 绘制——在 200×200 的OffscreenCanvas上画一个半径 50 的实心圆,再用getImageData(50, 50, 100, 100)裁出圆点区域的像素数据,直接喂给chrome.action.setIcon({ imageData });OffscreenCanvas不挂在任何 UI 上,不会阻塞界面渲染。其二,消息结构统一为{ action: updateIcon, value: { color } },这样无论是init()发默认绿色,还是calculateColor发数据驱动色,后台只需一个监听器即可覆盖(该绘制思路借鉴自 Energy Lollipop 扩展,课程也指出 Canvas API 的更多用法可在仓库的 Space Game 绘制章节 学到)。需要说明的一点:从仓库源码结构看,drawIcon中getImageData返回的ImageData直接作为imageData参数传入,这是课程参考实现的原始写法;在部分 Chromium 版本中chrome.action.setIcon期望ImageBitmap/Blob 形式的图像数据,若遇到图标不更新,可在background.js中将其转换为createImageBitmap再传入——这属于课程未覆盖的跨版本兼容细节。性能剖析:用 DevTools 给扩展做体检第三节课的另一半内容是 Web 性能方法论。模块的核心观点是网站性能 页面加载速度 页面上代码的执行速度,并给出了一套可复现的排查流程:打开 DevTools(Windows 上CtrlShiftI,macOS 上OptionCommandI),进入 Performance 标签;点击 Record,刷新页面并触发目标交互,停止录制后查看时间线中脚本、渲染、绘制三个维度的分布;选中时间线片段查看 summary 面板,在 Event Log 中定位耗时超过 15ms 的事件;对照清单判断瓶颈类型:长任务(优化 JS)、大资源(压缩图片/按需加载)、渲染阻塞脚本(加defer)、昂贵绘制(简化样式)。课程同时给出了三类通用优化抓手:资源体积(图片压缩、按设备下发尺寸、CSS/JS 压缩、懒加载)、DOM 复杂度(精简标签与嵌套层级、清理无用 CSS)、JavaScript 执行(使用defer让脚本在 DOM 解析后加载、按需拆分代码)。对扩展开发还有一个专门提示:扩展本身是独立的浏览器上下文,应从扩展自身的 DevTools 入口进入剖析,才能获得扩展专属的性能指标。课程还列了一组感知阈值供建立直觉:约 100ms 的延迟用户可感知,1 秒级别开始失去注意力,3 秒以上会造成显著流失。资料来源与延伸阅读模块 README 的 Credits 部分说明了项目的思想来源:Web 碳排触发器的概念由 Microsoft Green Cloud Advocacy 团队的 Asim Hussain 提出(其 Green Principles 项目,最初是一个网站项目);扩展的结构参考了 Adebola Adeniran 的 COVID 扩展;圆点图标体系则受 Energy Lollipop 扩展启发;课程由 Jen Looper 编写。想继续深入,可以沿仓库内这些路径走:start 目录的练习说明 带你走一遍构建与安装,solution 目录 提供逐文件对照的完整答案,1-about-browsers 的 Restyle your extension 作业适合练习 CSS 定制,而 3-background-tasks-and-performance 的 Analyze a site for performance 作业则把 DevTools 剖析方法应用到真实站点上。【免费下载链接】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),仅供参考