ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

零基础开发 Edge 浏览器插件:前端入门与关键词高亮实战

零基础开发 Edge 浏览器插件:前端入门与关键词高亮实战 很多同学第一次接触 Edge 浏览器插件是被各种“开发者模式”“离线安装包”“插件不生效”之类的教程劝退的。实际上开发一个 Edge 插件的门槛比做一个普通网页还要低。你只需要会 HTML、CSS 和 JavaScript就能在半小时内写出一个真正能在浏览器里运行的插件而且不用花钱买服务器、不用配置数据库、不用考虑后端接口。这篇文章要讲清楚一件事Edge 浏览器插件到底是怎么工作的以及前端开发者如何从零开始写一个自己的插件。文章会从核心概念讲起用三个完整的代码示例带你跑通一个“网页关键词高亮标记器”再给出加载调试、发布上架、常见问题排查和工程化建议。这不是一篇只讲“在哪里下载插件”的文章而是一篇真正能让你动手写插件的入门指南。1. 这篇文章真正要解决的问题先问你三个真实场景看看是不是你正在经历的第一个场景你装了一个翻译插件结果发现它把整个页面的排版都搞乱了你想给它加一个“只在划词时翻译”的功能但插件商店里根本没有这个选项。第二个场景你做前端开发每天要在一堆网页里反复检查某些关键词是否出现手动 CtrlF 效率太低你想自己做一个右键选中文本就能高亮标记的小工具。第三个场景你刚学完 Vue 或者 React想做一个小作品练手但不想写后端、不想买服务器想找一个“能立刻在真实环境里被使用”的项目。这三个场景的解决方案其实都是同一个自己写一个 Edge 浏览器插件。这里需要先给出一个明确判断Edge 插件开发是前端开发者最容易获得正反馈的入门方向之一。原因有三点。第一Edge 从 2020 年起就切换到 Chromium 内核插件 API 和 Chrome 扩展基本一致这意味着你学会写 Edge 插件等于同时学会了写 Chrome 插件迁移成本几乎为零。第二Edge 加载项商店在国内可以直接访问你写完插件可以自己打包分发也能发布到商店给其他用户使用整个流程都不需要额外工具。第三插件开发用到的核心语言就是 JavaScript你可以在插件里操作当前网页的 DOM、拦截网络请求、修改页面样式、响应右键菜单这比写一个静态网页有意思得多。这篇文章最适合的读者有三类刚入门的前端开发者、想练手做小作品的学生、以及在工作中需要定制浏览器的开发者。如果你只是想知道“某个现成插件怎么安装”这篇文章不适合你如果你想搞清楚“插件背后的运行机制是什么、我能自己做一个吗”那这篇文章就是为你准备的。2. Edge 插件的基础概念与核心原理2.1 插件本质上是一个“带清单文件的网页应用”很多人把浏览器插件想得很神秘以为它是一种特殊的二进制程序。其实不是。一个 Edge 插件本质上就是一组静态文件HTML 页面、JavaScript 脚本、CSS 样式、图片图标外加一个名为manifest.json的配置文件。你可以把插件理解成一个“被浏览器托管的小型网页应用”。普通网页运行在服务器上用户输入网址才能访问插件则被浏览器下载到本地在浏览器运行时被激活。manifest.json就是插件的“身份证”和“工牌”它告诉浏览器这个插件叫什么名字、版本号是多少、需要哪些权限、在哪些页面上运行、有哪些功能模块。2.2 插件的四个核心模块开发 Chrome 系插件你只需要掌握四个核心概念模块类比作用manifest.json工牌和说明书声明插件名称、版本、权限、入口文件content script临时工注入到网页里操作页面 DOM 和样式background script后台调度员常驻后台处理全局事件、右键菜单、网络请求popup 页面控制面板点击工具栏图标时弹出的交互界面这里需要重点解释content script因为新手最容易困惑它和插件其他部分的区别。content script是被浏览器注入到普通网页里的脚本它和页面共享 DOM但运行在浏览器为插件分配的“隔离世界”中。这个设计意味着它能看到页面上的文字和元素、能修改页面样式但拿不到页面自己定义的全局变量也不会被页面脚本随意干扰。一句话总结就是它生活在页面里但不和页面共用一套 JavaScript 环境。background script在 Manifest V3 里也被称为service worker它不直接操作页面 DOM但它能处理右键菜单点击、浏览器级事件、跨标签页通信。它就像幕后调度员你在网页里点右键它负责接收这个动作然后把指令发给对应标签页里的content script。popup页面则是最容易被理解的部分它就是你点击浏览器工具栏上的插件图标弹出来的那个小窗口。这个窗口里面就是一个 HTML 页面你可以放按钮、输入框、列表所有交互逻辑都写在它对应的 JS 文件里。2.3 插件需要哪些权限这个设计的关键点在哪里权限系统是插件开发的另一个核心概念也是新手最容易忽视、最容易出安全问题的地方。插件在manifest.json里通过permissions和host_permissions声明自己需要的权限。比如要读取收藏夹就声明bookmarks要读取浏览器历史就声明history要操作当前标签页就声明activeTab或tabs。这里有一个容易被误解的细节权限声明得越多插件能力越强但浏览器在安装时会弹窗提醒用户“此插件可读取您的浏览历史、可访问所有网站数据”。一个只要高亮关键词的插件却申请读取全部历史记录任谁都不敢装。所以插件开发有一条铁律权限最小化原则。能用activeTab就不用tabs能在特定域名下运行就不要匹配all_urls。另外要注意content script中写matches字段时并不是所有页面都允许注入。浏览器内置页面比如edge://extensions/、chrome://settings/是禁止普通插件访问的。这一点在后面调试时非常容易踩坑。3. 环境准备与前置条件3.1 你需要的东西其实很少写 Edge 插件不需要安装任何专门的 IDE 或 SDK你只需要三样东西一个较新版本的 Microsoft Edge 浏览器建议使用正式版版本号不用刻意追求最新因为 Manifest V3 早已成为默认标准。一个代码编辑器Visual Studio Code、WebStorm 甚至记事本都可以但建议用 VS Code方便高亮语法和调试。一点 JavaScript 基础至少要会document.querySelector、事件监听和基本的chrome.*API 调用。这篇文章的重点是展示通用开发思路所以下文所有版本信息不会写死你以当前实际使用的 Edge 版本为准。3.2 开启 Edge 开发者模式要加载自己写的插件必须先在浏览器里开启“开发人员模式”。操作路径是在地址栏输入edge://extensions/并回车打开页面左上角的“开发人员模式”开关点击“加载解压缩的扩展”按钮。这一步完成之后Edge 就可以加载你本地目录里的插件源码了。这种加载方式在开发阶段非常重要因为你可以改一行代码、刷新一下扩展、再去目标页面验证效果整个迭代周期只需要几秒钟。这里需要提醒一点开发人员模式是给本地调试使用的不要把未打包的插件目录直接发给普通用户。普通用户安装插件应该使用.crx格式或者通过 Edge 加载项商店安装。3.3 准备项目目录结构我们为后续的例子创建一个项目文件夹my-highlighter/ ├── manifest.json ├── content.js ├── background.js ├── popup.html └── popup.js如果后续要发布到商店还需要准备两个图标文件icon16.png和icon32.png分别对应工具栏小图标和扩展管理页图标。开发阶段可以先用任意占位图但发布阶段必须提供规范尺寸的图标。4. 核心流程拆解从空目录到第一个可运行的插件4.1 第一步写清单文件 manifest.json所有插件开发都从manifest.json开始。它不仅是给浏览器看的配置也是你自己梳理插件结构的过程。我们先写一个最小可运行版本{ manifest_version: 3, name: 关键词高亮标记器, version: 1.0.0, description: 在网页中批量高亮指定关键词支持右键菜单一键高亮选中文本。, permissions: [storage, activeTab, contextMenus], action: { default_popup: popup.html, default_title: 关键词高亮标记器 }, background: { service_worker: background.js }, content_scripts: [ { matches: [all_urls], js: [content.js], run_at: document_idle } ] }关键字段逐个解释manifest_version清单文件版本目前必须写3。Manifest V3 是 Chrome 和 Edge 当前主推的扩展标准它用 service worker 替代了旧的常驻后台页。permissions声明插件需要的权限。这里申请了三个storage用于持久化存储、activeTab用于在用户点击插件图标或右键菜单时访问当前标签页、contextMenus用于创建右键菜单。action设置工具栏图标行为default_popup指向点击图标后弹出的页面。background.service_worker后台脚本文件它是全局事件的处理中枢。content_scripts指定要注入到哪些页面的脚本matches用all_urls表示在所有普通网页上注入run_at: document_idle表示在页面 DOM 加载完成后执行。这里要说明的是all_urls在真实项目里属于“权限过大”的写法适合本地演示。实际发布时会建议改成更精确的域名匹配后面安全章节会详细展开。4.2 第二步编写内容脚本 content.jscontent.js负责在页面中实际执行高亮操作。我们实现一个核心函数遍历页面文本节点把匹配到的关键词用mark标签包起来。// 文件路径content.js let highlightCount 0; chrome.runtime.onMessage.addListener((message, sender, sendResponse) { if (message message.type HIGHLIGHT) { highlightCount highlightKeywords(message.keywords || []); sendResponse({ count: highlightCount }); } return true; }); function highlightKeywords(keywords) { const validKeywords keywords .map((item) (item || ).trim()) .filter((item) item.length 0); if (validKeywords.length 0) { return 0; } const textNodes []; const walker document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT, { acceptNode(node) { if (!node.parentElement) return NodeFilter.FILTER_REJECT; const tag node.parentElement.tagName; if ([SCRIPT, STYLE, NOSCRIPT, TEXTAREA, INPUT].includes(tag)) { return NodeFilter.FILTER_REJECT; } return NodeFilter.FILTER_ACCEPT; } }); while (walker.nextNode()) { textNodes.push(walker.currentNode); } let count 0; textNodes.forEach((node) { const text node.nodeValue || ; if (text.indexOf(★highlighted★) -1) { return; } const fragment document.createDocumentFragment(); const regex new RegExp(validKeywords.map(escapeRegExp).join(|), gi); let lastIndex 0; let match; let matched false; while ((match regex.exec(text)) ! null) { matched true; count; if (match.index lastIndex) { fragment.appendChild(document.createTextNode(text.slice(lastIndex, match.index))); } const mark document.createElement(mark); mark.style.backgroundColor #ffe58f; mark.style.color #333; mark.textContent match[0]; fragment.appendChild(mark); lastIndex match.index match[0].length; if (match.index regex.lastIndex) { regex.lastIndex; } } if (matched) { if (lastIndex text.length) { fragment.appendChild(document.createTextNode(text.slice(lastIndex))); } node.parentNode.replaceChild(fragment, node); } }); return count; } function escapeRegExp(str) { return str.replace(/[.*?^${}()|[\]\\]/g, \\$); }这个脚本解释了content script的几个典型特征它能用chrome.runtime.onMessage接收来自 popup 或 background 的消息它能操作页面的 DOM它运行在页面环境里但代码本身属于插件。写这段代码时有几个细节值得注意。第一我们用TreeWalker而不是简单地在document.body.innerHTML上做字符串替换是因为后者会破坏页面原有的 DOM 结构和事件绑定。第二我们跳过了SCRIPT、STYLE等标签避免在样式文件内容里做无意义的高亮。第三我们通过正则匹配时使用lastIndex手动推进防止正则陷入死循环。4.3 第三步编写弹出页面 popup.html 和 popup.jspopup页面是用户看到的控制面板。我们放一个输入框、一个按钮、一行结果提示功能是用户在输入框里输入关键词点击按钮高亮当前页面里所有匹配的词。!-- 文件路径popup.html -- !DOCTYPE html html langzh-CN head meta charsetUTF-8 meta nameviewport contentwidthdevice-width, initial-scale1.0 title关键词高亮标记器/title style body { width: 260px; margin: 0; padding: 16px; font-family: Microsoft YaHei, sans-serif; } h1 { font-size: 15px; margin: 0 0 12px 0; } textarea { width: 100%; height: 48px; box-sizing: border-box; margin-bottom: 10px; resize: none; border: 1px solid #ccc; border-radius: 4px; padding: 6px 8px; font-size: 13px; } button { width: 100%; height: 32px; border: none; border-radius: 4px; background-color: #2161d9; color: #fff; font-size: 13px; cursor: pointer; } button:hover { background-color: #1b4fb0; } #result { margin-top: 10px; font-size: 12px; color: #666; } /style /head body h1关键词高亮标记器/h1 textarea idkeywordInput placeholder输入关键词用逗号分隔/textarea button idhighlightBtn开始高亮/button div idresult/div script srcpopup.js/script /body /html// 文件路径popup.js const keywordInput document.getElementById(keywordInput); const highlightBtn document.getElementById(highlightBtn); const result document.getElementById(result); highlightBtn.addEventListener(click, async () { const raw keywordInput.value || ; const keywords raw.split(/[,\s]/).filter((item) item.length 0); if (keywords.length 0) { result.textContent 请先输入关键词; return; } try { const [tab] await chrome.tabs.query({ active: true, currentWindow: true }); if (!tab || !tab.id) { result.textContent 没有获取到当前标签页; return; } const response await chrome.tabs.sendMessage(tab.id, { type: HIGHLIGHT, keywords }); result.textContent 已完成高亮共标记 ${response?.count ?? 0} 处; } catch (error) { result.textContent 发送消息失败请刷新页面后重试; } });popup.js的核心逻辑是chrome.tabs.query获取当前活动标签页然后通过chrome.tabs.sendMessage向content script发送消息。这里的activeTab权限在用户点击插件图标时会被临时授予所以不需要申请宽泛的tabs权限。这里需要注意chrome.tabs.sendMessage在content script尚未注入的页面上会抛错比如浏览器内置页面和扩展商店页面。所以展示的代码里加入了try/catch防止报错弹窗影响交互体验。4.4 第四步编写后台脚本 background.jsbackground.js在 Manifest V3 中是一个 service worker它不是常驻内存的页面而是在事件发生时被唤醒。我们用它在安装时创建右键菜单并监听右键菜单点击事件// 文件路径background.js chrome.runtime.onInstalled.addListener(() { chrome.contextMenus.create({ id: highlight-selection, title: 高亮“%s”, contexts: [selection] }); }); chrome.contextMenus.onClicked.addListener((info, tab) { if (info.menuItemId highlight-selection) { const keyword (info.selectionText || ).trim(); if (keyword tab tab.id) { chrome.tabs.sendMessage(tab.id, { type: HIGHLIGHT, keywords: [keyword] }); } } });这段代码的意义在于展示一条完整的事件链路用户在网页上选中一段文字并点击右键选择“高亮”后台脚本收到事件把选中的文字作为关键词发给当前页面的content script高亮操作随即执行。这里要特别提醒一个容易踩坑的地方background.js里的chrome.contextMenus.create不能直接写在顶层而应该放在chrome.runtime.onInstalled监听器里。否则每次 service worker 被唤醒时可能会尝试重复创建同名菜单导致报错。5. 完整示例代码实现汇总把上面所有文件放在一起一个完整可运行的 Edge 插件目录就是my-highlighter/ ├── manifest.json ├── content.js ├── background.js ├── popup.html └── popup.js如果要把插件加载到浏览器请打开edge://extensions/开启“开发人员模式”点击“加载解压缩的扩展”选择my-highlighter目录。加载成功后工具栏会出现插件图标。此时插件的交互流程有两条用户点击工具栏插件图标在 popup 中输入关键词点击“开始高亮”popup 通过chrome.tabs.sendMessage把关键词发给当前页面的content scriptcontent script在页面 DOM 中查找并高亮关键词。用户直接在网页中选中一段文字点击右键选择“高亮”后台脚本接收右键菜单事件把选中的文字作为关键词发送给当前页面的content script。这里要提一下图标的问题。目前manifest.json里没有声明icons字段Edge 会显示一个默认的拼图图标不影响功能测试。如果要发布到商店你需要在manifest.json中补充icons字段并准备icon16.png、icon48.png和icon128.pngicons: { 16: icons/icon16.png, 48: icons/icon48.png, 128: icons/icon128.png }图标文件可以使用任意 PNG 图片临时占位但正式发布时尺寸必须符合规范。6. 运行结果与效果验证6.1 验证步骤插件加载完成之后打开任意一个内容较多的网页比如一篇 CSDN 技术博客按以下步骤验证点击工具栏上的插件图标在 popup 输入框中输入“浏览器 插件”两个关键词逗号分隔点击“开始高亮”。预期结果页面中所有出现“浏览器”和“插件”文字的地方都被黄色背景标出popup 下方显示“已完成高亮共标记 XX 处”。在页面中选择任意一段文字点击右键菜单里的“高亮”该文字所在位置会被标记。在edge://extensions/页面点击“检查视图”下的 service worker 链接打开后台脚本的 DevTools确认没有报错。如果页面没有任何反应优先按下面顺序排查打开目标网页的 DevToolsF12切到 Console 面板查看是否有报错信息。查看content script的日志。注意content script的console.log会输出在页面自身的 DevTools 里而不是插件的 DevTools 里。检查插件是否成功注入。在网页 DevTools 的 Console 中执行chrome.runtime如果返回 undefined说明页面不允许插件注入。6.2 修改代码后如何让修改生效开发过程中我们经常改代码但改完发现页面行为没有变化。这时候容易误以为是代码问题。其实只是因为插件没有重新加载。正确流程是在edge://extensions/页面点击插件卡片上的“重新加载”图标刷新目标网页重新点击插件 popup 验证。如果只改了popup.js而不涉及content.js只需要重新打开 popup 就行了因为 popup 是每次点击时新打开页面它会读取最新代码。但如果你改了content.js或manifest.json就必须先重新加载扩展再刷新目标网页因为旧版 content script 可能还在页面里运行。7. 常见问题与排查方法问题现象可能原因排查方式解决方案插件图标显示出来但点击后没有 popupmanifest.json的 action 字段未配置或配置错误检查default_popup路径是否正确确保default_popup指向真实存在的 HTML 文件修改代码后页面行为不生效扩展未重新加载或页面还运行着旧版 content script查看edge://extensions/的扩展状态点击“重新加载”再刷新目标网页DevTools 里看不到 content script 的日志打开了错误的 DevTools 上下文目标网页 DevTools 的 Console 右上角需保持“页面”上下文content script 日志输出在页面自身的 DevTools 里清除了所有高亮效果但无法重复操作高亮脚本没有处理重复执行的情况查看 content.js 中是否过滤了已处理节点使用“清除高亮”功能或重新加载页面在edge://extensions/页面里插件没反应浏览器内置页面禁止注入 content script使用扩展冲突检测方式换一个普通网页测试popup 点击按钮无反应控制台报 “Could not establish connection. Receiving end does not exist”content script 未注入或页面不允许注入F12 查看报错信息刷新页面重试或检查 matches 配置页面脚本干扰插件运行插件 content script 与页面 JS 隔离检查是否被其他扩展影响在无痕模式下逐个禁用扩展测试背景脚本直接写chrome.contextMenus.create报错在 service worker 顶层创建菜单可能重复执行查看 background.js 控制台把菜单创建放到onInstalled事件里这里补充说明一个高频问题报错信息里的 “Receiving end does not exist” 几乎每个插件开发者都会遇到。它的含义是发送消息的调用方没有找到接收方。常见原因包括页面还没加载完就发送消息、目标标签页是浏览器内置页面、或者content script因为语法错误没有注入成功。排查思路其实很简单打开目标网页的 DevTools看 console 里有没有来自 content script 的报错如果在 DevTools 中手动执行一段content script里定义过的函数发现不存在那就是脚本确实没有注入进去。8. 权限安全、发布流程与工程化建议8.1 别把all_urls当成默认选项本篇文章的演示代码里使用了all_urls这是为了教学方便但在真实项目里不推荐。all_urls意味着插件会在用户访问的所有网站上运行这既是性能负担也是隐私担忧。更稳妥的做法有两种。如果你的插件只针对固定网站那就用精确匹配content_scripts: [ { matches: [https://blog.csdn.net/*], js: [content.js] } ]如果插件需要在用户点击时才有权访问当前网站而不是在用户打开任何网站之前就自动注入那可以考虑把权限改成activeTab并通过chrome.scripting.executeScript动态注入脚本。这种方式既满足需求又不会让用户在安装时看到“可以访问所有网站”的警告。发布到商店的插件审核者会重点检查权限是否合理权限过大的插件很可能被驳回。8.2 发布到 Edge 加载项商店插件开发完成后可以考虑发布到微软的“Edge 加载项”商店让其他用户也能通过商店搜索到你的插件。发布流程大致如下注册微软开发者账号并完成认证在合作伙伴中心创建一个“Microsoft Edge 扩展”项目填写插件名称、描述、隐私政策链接上传打包好的 ZIP 压缩包包含manifest.json、脚本、HTML、图标提交审核等待微软审核通过。这里要特别提醒窗口截图、权限说明和隐私政策是审核重点。如果你的插件会读取页面内容或存储数据必须如实声明并提供一个可访问的隐私政策页面。不要在插件描述里夸大功能更不要试图用插件窃取用户输入这类行为一旦被核实账号会被封禁。8.3 工程化建议当插件代码量变大之后纯手写多个 JS 文件会变得难以维护。这里给出几个建议第一把代码拆成小模块。比如content script里可以把高亮算法、文本处理、消息监听分别放进不同文件最后在manifest.json的js数组里按依赖顺序引入。注意顺序很重要依赖其他模块的脚本要放在后面。第二引入构建工具。如果你会用 Webpack 或 Vite可以把插件代码打包成更规范的产物。许多开源插件的源码都用 TypeScript 编写配合构建工具最终生成浏览器能直接加载的目录。这个对新手不是必须但到了后期工程化阶段很有价值。第三给 content script 的操作做防抖。如果用户频繁点击高亮按钮不要每次都重新遍历整个 DOM更好的做法是先把上一次的高亮效果清除再执行新的高亮或者引入防抖函数避免阻塞页面主线程。第四做好状态管理。插件缺少传统意义上的服务端你需要在storage、popup、content script 之间维护一致状态。简单的项目可以直接在消息里传全部参数复杂项目建议把公共状态保存在chrome.storage.local中页面打开时读取。8.4 安全边界和注意提醒浏览器插件拥有比普通网页更高的权限因此安全要求也更高。写插件时至少要注意下面几个底线不要在你的插件代码里硬编码任何密钥或令牌因为插件包可以被用户解压查看。不要通过插件收集用户数据后上传到不明服务器这是严重违规。不要使用动态执行远程代码的方式比如从远端拉取 JavaScript 再执行Manifest V3 对远程代码有严格限制而且这种设计本身就很危险。涉及生产环境的任何自动化操作都要先在小范围网站测试通过再扩大应用范围。9. 总结与后续学习方向这篇文章做了一次完整的“从零到一”的 Edge 插件开发演示。你学到了插件的四个核心模块manifest 清单、content script、background script、popup 页面理解了权限系统的设计逻辑用“关键词高亮标记器”这个真实例子跑通了从代码编写、本地加载、消息传送到事件回调的完整流程最后了解了发布商店的审核要求、常见问题排查方法以及工程化思路。下一步你可以从三个方向继续深入第一改造这个高亮插件。给它加上“清除所有高亮”的按钮加上关键词保存到chrome.storage后下次自动读取的功能或者在高亮的同时把匹配结果列表显示在 popup 里。每次加一个细微功能都能加深你对消息机制和权限模型的理解。第二尝试做不同领域的小插件。做一个在博客页面上显示“预计阅读时间”的小工具做一个检测当前页面 JavaScript 报错数量的调试辅助插件或者做一个自动整理页面图片尺寸的工具栏插件。这些项目都不需要后端而且能立刻被实际使用特别适合作为前端学习阶段的练手作品。第三阅读一些开源插件的源码。打开 Edge 加载项商店找一个你日常使用的小型插件去 GitHub 搜索同类型开源项目看看别人怎么组织代码、怎么声明权限、怎么处理消息。很多开发灵感都来自阅读优秀开源代码。最后给你一个直接的提醒插件开发入门最快的方式就是动手。不用先把所有 API 背熟先创建一个最小目录写上最简单的manifest.json和content.js加载到浏览器里然后在 Console 里查看效果。当第一次看到自己的代码改变了真实网页时你大概就会理解为什么这个看似“小众”的领域能持续不断地吸引新的开发者。
返回列表