用ChatGPT(o3-mini-high)& Claude-3.7-sonnet开发谷歌浏览器插件!实战+踩坑修复+源码对比

发布于:2025-04-15 ⋅ 阅读:(27) ⋅ 点赞:(0)

不懂前端?没开发过插件?
没关系!现在有了AI助手,比如 ChatGPT(o3-mini-high) 和 Cursor+Claude-3.7-sonnet
开发一个实用Chrome插件,只需要几个Prompt!

今天带你亲身体验:

  • 从0开始,生成Chrome插件

  • 遇坑、调试、修复

  • 最后对比不同AI模型的开发体验

🧠 项目目标

  • 浏览网页时输入关键词

  • 点击【高亮】,标出所有匹配的文本

  • 点击【清除】,一键恢复正常页面

第一阶段|用ChatGPT(o3-mini-high)实战开发(含调试过程)

1️⃣ 初次生成

Prompt:

写一个Chrome扩展程序,支持Manifest V3。用户可以输入一段文字,点击按钮后,在当前网页中高亮所有包含这段文字的内容。

ChatGPT(o3-mini-high)快速生成了三份核心文件:

📄 manifest.json(初版)

{
  "manifest_version": 3,
  "name": "文本高亮器",
  "version": "1.0",
  "permissions": ["scripting", "activeTab"],
  "action": {
    "default_popup": "popup.html"
  }
}

✅ 使用了正确的 Manifest V3

📄 popup.html(初版)

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>文本高亮器</title>
</head>
<body>
<input type="text" id="searchText" placeholder="请输入要高亮的文字" />
<button id="highlightButton">高亮</button>
<script src="popup.js"></script>
</body>
</html>

很基础,一个输入框+一个按钮。

📄 popup.js(初版)

document.getElementById('highlightButton').addEventListener('click', async () => {
const searchText = document.getElementById('searchText').value.trim();
if (!searchText) return;

const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
if (!tab?.id) return;

  chrome.scripting.executeScript({
    target: { tabId: tab.id },
    function: highlightText,
    args: [searchText]
  });
});

function highlightText(searchText) {
const regex = newRegExp(searchText, 'gi');
const walker = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT, null, false);
let node;
while ((node = walker.nextNode())) {
    if (node.parentNode && !['SCRIPT', 'STYLE', 'NOSCRIPT'].includes(node.parentNode.nodeName)) {
      node.parentNode.innerHTML = node.parentNode.innerHTML.replace(regex, match =>
        `<span style="background: yellow;">${match}</span>`);
    }
  }
}

2️⃣ 第一次测试:出现问题

加载后,初步测试可以高亮,但很快发现严重问题:

  • 连续搜索时,之前的高亮内容不会清除!

  • 页面会越叠越多高亮,阅读体验变差。

3️⃣ 尝试修正:引入清除高亮逻辑

于是我让ChatGPT帮我补充:

Prompt:

请在每次高亮前,清除上次的高亮内容。

ChatGPT生成了一个新的removeHighlights方法,并在highlightText中先调用它。

但是!在测试时又遇到新报错:

Uncaught ReferenceError: removeHighlights is not defined

原因:在 Chrome 插件中注入的函数,只能使用自身作用域内定义的函数,不能引用外部函数!

4️⃣ 正确修正:内嵌removeHighlights

于是进一步优化,把removeHighlights写成highlightText函数内部函数

最终修正后的 popup.js:

📄 popup.js(修正版 ✅ 正常运行)

document.getElementById('highlightButton').addEventListener('click', async () => {
const searchText = document.getElementById('searchText').value.trim();
if (!searchText) return;

const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
if (!tab?.id) return;

  chrome.scripting.executeScript({
    target: { tabId: tab.id },
    function: highlightText,
    args: [searchText]
  });
});

function highlightText(searchText) {
function removeHighlights() {
    document.querySelectorAll('span.my-highlighter-highlight').forEach(span => {
      span.replaceWith(span.textContent);
    });
    document.querySelectorAll('span[data-highlighter]').forEach(span => {
      span.replaceWith(span.textContent);
    });
  }

  removeHighlights();

if (!searchText) return;

const escapeRegExp = string => string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
const escapedText = escapeRegExp(searchText);
const regex = newRegExp(escapedText, 'gi');

const walker = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT, null, false);
let node;
const nodesToHighlight = [];

while ((node = walker.nextNode())) {
    if (node.parentNode && !['SCRIPT', 'STYLE', 'NOSCRIPT'].includes(node.parentNode.nodeName)) {
      if (regex.test(node.nodeValue)) {
        nodesToHighlight.push(node);
      }
    }
  }

  nodesToHighlight.forEach(textNode => {
    const wrapper = document.createElement('span');
    wrapper.setAttribute('data-highlighter', 'true');
    wrapper.innerHTML = textNode.nodeValue.replace(regex, match =>
      `<span class="my-highlighter-highlight" style="background-color: yellow;">${match}</span>`
    );
    textNode.parentNode.replaceChild(wrapper, textNode);
  });
}

✅ 这次测试完全OK:

  • 每次点击前清除旧高亮

  • 新的关键词正常高亮

  • 页面干净不乱

第二阶段|用Cursor + Claude-3.7-sonnet开发进阶版插件

同样的Prompt,我使用Cursor编辑器调用Claude-3.7-sonnet模型重新生成。

这次Claude输出了更完整、更专业的项目结构,而且代码一次生成就能直接用,不需要再调试!

📦 项目结构(Claude版)

TextHighlighterClaude/
├── manifest.json
├── popup.html
├── popup.js
├── content.js

📄 manifest.json

{
  "manifest_version": 3,
"name": "Text Highlighter",
"version": "1.0",
"description": "Highlights specified text on the current webpage",
"action": {
    "default_popup": "popup.html"
  },
"permissions": ["activeTab", "scripting"],
"content_scripts": [
    {
      "matches": ["<all_urls>"],
      "js": ["content.js"]
    }
  ]
}

📄 popup.html

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Text Highlighter</title>
<style>
    body {
      width: 300px;
      padding: 15px;
      font-family: Arial, sans-serif;
    }
    #searchText {
      width: 100%;
      padding: 8px;
      margin-bottom: 10px;
    }
    #highlightButton, #clearButton {
      width: 100%;
      padding: 8px;
      margin-top: 5px;
      border: none;
      border-radius: 4px;
      cursor: pointer;
    }
    #highlightButton {
      background-color: #4285f4;
      color: white;
    }
    #clearButton {
      background-color: #f44336;
      color: white;
    }
  </style>
</head>
<body>
<h2>Text Highlighter</h2>
<p>Enter text to highlight on the current page:</p>
<input type="text" id="searchText" placeholder="Enter text...">
<button id="highlightButton">Highlight Text</button>
<button id="clearButton">Clear Highlights</button>
<script src="popup.js"></script>
</body>
</html>

📄 popup.js

document.addEventListener('DOMContentLoaded', function() {
const highlightButton = document.getElementById('highlightButton');
const clearButton = document.getElementById('clearButton');
const searchText = document.getElementById('searchText');

  highlightButton.addEventListener('click', function() {
    const textToHighlight = searchText.value.trim();
    if (!textToHighlight) {
      alert('Please enter text to highlight');
      return;
    }

    chrome.tabs.query({active: true, currentWindow: true}, function(tabs) {
      chrome.scripting.executeScript({
        target: {tabId: tabs[0].id},
        function: highlightTextOnPage,
        args: [textToHighlight]
      });
    });
  });

  clearButton.addEventListener('click', function() {
    chrome.tabs.query({active: true, currentWindow: true}, function(tabs) {
      chrome.scripting.executeScript({
        target: {tabId: tabs[0].id},
        function: clearHighlightsOnPage
      });
    });
  });
});

function highlightTextOnPage(searchText) {
const event = new CustomEvent('highlight-text', { detail: { text: searchText } });
document.dispatchEvent(event);
}

function clearHighlightsOnPage() {
const event = new CustomEvent('clear-highlights');
document.dispatchEvent(event);
}

📄 content.js

document.addEventListener('highlight-text', function(e) {
const searchText = e.detail.text;
  highlightText(searchText);
});

document.addEventListener('clear-highlights', function() {
  clearHighlights();
});

let highlightElements = [];

function highlightText(searchText) {
  clearHighlights();

if (!searchText) return;

const searchRegex = newRegExp(escapeRegExp(searchText), 'gi');
const walker = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT, null, false);
let node;
const textNodes = [];

while (node = walker.nextNode()) {
    if (!['SCRIPT', 'STYLE', 'NOSCRIPT'].includes(node.parentNode.nodeName)) {
      textNodes.push(node);
    }
  }

  textNodes.forEach(function(textNode) {
    const matches = textNode.nodeValue.match(searchRegex);
    if (matches && matches.length > 0) {
      const fragment = document.createDocumentFragment();
      let lastIndex = 0;
      let match;
      searchRegex.lastIndex = 0;

      while ((match = searchRegex.exec(textNode.nodeValue)) !== null) {
        const matchIndex = match.index;
        if (matchIndex > lastIndex) {
          fragment.appendChild(document.createTextNode(textNode.nodeValue.substring(lastIndex, matchIndex)));
        }

        const highlightSpan = document.createElement('span');
        highlightSpan.className = 'text-highlighter-extension';
        highlightSpan.style.backgroundColor = '#ffff00';
        highlightSpan.style.color = '#000000';
        highlightSpan.appendChild(document.createTextNode(match[0]));

        highlightElements.push(highlightSpan);
        fragment.appendChild(highlightSpan);

        lastIndex = matchIndex + match[0].length;
      }

      if (lastIndex < textNode.nodeValue.length) {
        fragment.appendChild(document.createTextNode(textNode.nodeValue.substring(lastIndex)));
      }

      textNode.parentNode.replaceChild(fragment, textNode);
    }
  });
}

function clearHighlights() {
  highlightElements.forEach(function(el) {
    if (el.parentNode) {
      const textNode = document.createTextNode(el.textContent);
      el.parentNode.replaceChild(textNode, el);
    }
  });
  highlightElements = [];
}

function escapeRegExp(string) {
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}

✅ Claude版不仅功能更全,还额外支持【一键清除】,并且代码模块化、维护性更强!

📈 最后总结|ChatGPT vs Claude体验对比

体验项目

ChatGPT(o3-mini-high)

Cursor+Claude-3.7-sonnet

开发速度

快,但需中途多次调试

直接一版成型

功能完整度

需要自己补充清除功能

自带完整高亮+清除

代码规范性

简单直接

工程结构标准、可扩展性高

适合场景

快速原型、练手

正式产品、小工具开发

AI已经大大提升了开发生产力,每个人都有机会用简单的指令打造自己的小工具、小产品!


网站公告

今日签到

点亮在社区的每一天
去签到