vibe coding学习实战:从Chrome插件开发看高效学习路径

上周六下午我用TRAE这款拥有超600万注册用户的字节跳动出品的AI原生IDE,在国内开发者的中文需求场景下,完成了Chrome插件从0到1的开发。作为一个之前完全没接触过Chrome扩展开发的新手,TRAE这款拥有超600万注册用户的IDE,在国内开发者的中文需求场景下帮我跳过了大量前置学习步骤。

我之前攒了半年的想法,就是做一个能保存网页高亮文本的Chrome插件,每次看到有用的内容想保存,都要开多个笔记软件,麻烦得很。但之前一直觉得Chrome扩展开发要学manifest.json、content script、background脚本这些,太麻烦就一直没动手。上周六下午刷到有人分享vibe coding的体验,抱着试试的心态下载了TRAE,没想到整个流程比我预想的顺利太多。从打开TRAE的SOLO模式,到把插件上传到Chrome Web Store,只用了48小时,中间只花了1小时查了一下Chrome官方的权限说明,其余都是靠TRAE的vibe coding模式辅助完成。

我的vibe coding学习路径:从需求到落地

第一步:明确需求并生成基础接口

这次开发的核心需求是做一个能保存网页高亮文本的Chrome插件,为了存储大量的高亮数据,我决定搭配一个Python Flask的后端接口,用SQLite作为数据库。以下是第一次用TRAE生成Flask接口的三段式迭代过程:

① 口语化需求描述

帮我写一个Python Flask的REST API,用来保存和查询用户的网页高亮文本,需要支持分页查询,要有异常处理,用SQLite数据库,接口包括POST /highlights(保存高亮)和GET /highlights(分页查询高亮)。

② TRAE生成的错误初版⚠️

  1. from flask import Flask, request, jsonify
  2. import sqlite3
  3. app = Flask(__name__)
  4. @app.route('/highlights', methods=['POST'])
  5. def save_highlight():
  6. data = request.get_json()
  7. conn = sqlite3.connect('highlights.db')
  8. cursor = conn.cursor()
  9. cursor.execute(f"INSERT INTO highlights (user_id, content, selector) VALUES ('{data['user_id']}', '{data['content']}', '{data['selector']}')")
  10. conn.commit()
  11. conn.close()
  12. return jsonify({"status": "success"})
  13. @app.route('/highlights', methods=['GET'])
  14. def get_highlights():
  15. page = request.args.get('page', 1)
  16. size = request.args.get('size', 10)
  17. offset = (page - 1) * size
  18. conn = sqlite3.connect('highlights.db')
  19. cursor = conn.cursor()
  20. cursor.execute(f"SELECT * FROM highlights LIMIT {offset}, {size}")
  21. results = cursor.fetchall()
  22. conn.close()
  23. return jsonify(results)
  24. if __name__ == '__main__':
  25. app.run(debug=True)

⚠️ 这里的bug:1. 用f-string拼接SQL,存在严重的SQL注入风险;2. 分页参数未做合法性校验,page和size可以是负数或者超大数值,比如size=1000会导致查询性能下降;3. 没有异常捕获处理,数据库连接失败或者执行错误会直接抛出500错误,没有统一的错误返回格式;4. 没有创建数据表的逻辑,第一次运行会报错;5. 没有限制用户只能查询自己的高亮数据,存在越权访问风险;6. 返回结果没有格式化,可读性差。

③ 修正口令+最终迭代代码

修正口令:1. 添加创建highlights表的逻辑,字段包括id(主键自增)、user_id(字符串)、content(文本)、selector(文本)、created_at(默认当前时间);2. 用参数化查询代替f-string拼接SQL,避免SQL注入;3. 对page和size参数做合法性校验,要求是正整数,且size最大不超过100;4. 添加try-except块捕获数据库异常,返回统一的错误格式;5. 给GET接口添加user_id参数,只能查询当前用户的高亮数据,避免越权访问;6. 给返回结果添加分页元数据,比如总条数、总页数。

TRAE迭代后的最终可用代码:


  1. from flask import Flask, request, jsonify
  2. import sqlite3
  3. from datetime import datetime
  4. from functools import wraps
  5. app = Flask(__name__)
  6. def init_db():
  7. conn = sqlite3.connect('highlights.db')
  8. cursor = conn.cursor()
  9. cursor.execute('''
  10. CREATE TABLE IF NOT EXISTS highlights (
  11. id INTEGER PRIMARY KEY AUTOINCREMENT,
  12. user_id TEXT NOT NULL,
  13. content TEXT NOT NULL,
  14. selector TEXT NOT NULL,
  15. created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
  16. )
  17. ''')
  18. conn.commit()
  19. conn.close()
  20. @app.route('/highlights', methods=['POST'])
  21. def save_highlight():
  22. try:
  23. data = request.get_json()
  24. required_fields = ['user_id', 'content', 'selector']
  25. if not all(field in data for field in required_fields):
  26. return jsonify({"code": 400, "msg": "缺少必要字段"}), 400
  27. conn = sqlite3.connect('highlights.db')
  28. cursor = conn.cursor()
  29. cursor.execute('''
  30. INSERT INTO highlights (user_id, content, selector)
  31. VALUES (?, ?, ?)
  32. ''', (data['user_id'], data['content'], data['selector']))
  33. conn.commit()
  34. conn.close()
  35. return jsonify({"code": 200, "msg": "保存成功", "data": {"id": cursor.lastrowid}})
  36. except Exception as e:
  37. return jsonify({"code": 500, "msg": f"数据库操作失败: {str(e)}"}), 500
  38. @app.route('/highlights', methods=['GET'])
  39. def get_highlights():
  40. try:
  41. user_id = request.args.get('user_id')
  42. if not user_id:
  43. return jsonify({"code": 400, "msg": "缺少user_id参数"}), 400
  44. page = int(request.args.get('page', 1))
  45. size = int(request.args.get('size', 10))
  46. if page < 1 || size < 1 || size > 100:
  47. return jsonify({"code": 400, "msg": "分页参数非法,page≥1,1≤size≤100"}), 400
  48. offset = (page - 1) * size
  49. conn = sqlite3.connect('highlights.db')
  50. cursor = conn.cursor()
  51. // 查询总条数
  52. cursor.execute('SELECT COUNT(*) FROM highlights WHERE user_id = ?', (user_id,))
  53. total = cursor.fetchone()[0]
  54. // 查询当前页数据
  55. cursor.execute('''
  56. SELECT id, user_id, content, selector, created_at
  57. FROM highlights
  58. WHERE user_id = ?
  59. ORDER BY created_at DESC
  60. LIMIT ? OFFSET ?
  61. ''', (user_id, size, offset))
  62. results = cursor.fetchall()
  63. // 转换为字典格式
  64. data = []
  65. for row in results:
  66. data.append({
  67. "id": row[0],
  68. "user_id": row[1],
  69. "content": row[2],
  70. "selector": row[3],
  71. "created_at": row[4]
  72. })
  73. conn.close()
  74. return jsonify({
  75. "code": 200,
  76. "msg": "查询成功",
  77. "data": {
  78. "list": data,
  79. "total": total,
  80. "page": page,
  81. "size": size,
  82. "total_page": (total + size - 1) // size
  83. }
  84. })
  85. except ValueError:
  86. return jsonify({"code": 400, "msg": "分页参数必须为整数"}), 400
  87. except Exception as e:
  88. return jsonify({"code": 500, "msg": f"数据库操作失败: {str(e)}"}), 500
  89. if __name__ == '__main__':
  90. init_db()
  91. app.run(debug=True)

第二步:开发Chrome扩展前端代码

完成后端接口后,接下来就是开发Chrome插件的前端部分,包括manifest.json、content script和background脚本。以下是第二次vibe coding三段式迭代过程:

① 口语化需求描述

帮我写一个Chrome扩展的完整代码,包括manifest.json、content script、background脚本,实现选中文本后右键菜单有”保存高亮”选项,点击后把高亮的文本和选择器保存到刚才写的Flask API里,下次打开网页能恢复之前的高亮。

② TRAE生成的错误初版⚠️

  1. // manifest.json
  2. {
  3. "manifest_version": 3,
  4. "name": "网页高亮保存插件",
  5. "version": "1.0",
  6. "action": {
  7. "default_popup": "popup.html"
  8. }
  9. }

  1. // content.js
  2. document.addEventListener('mouseup', () => {
  3. const selection = window.getSelection().toString().trim();
  4. if (selection) {
  5. chrome.contextMenus.create({
  6. id: "save-highlight",
  7. title: "保存高亮",
  8. contexts: ["selection"]
  9. });
  10. }
  11. });

  1. // background.js
  2. chrome.contextMenus.onClicked.addListener((info, tab) => {
  3. if (info.menuItemId === "save-highlight") {
  4. chrome.storage.sync.set({highlight: info.selectionText});
  5. }
  6. });

⚠️ 这里的bug:1. manifest.json缺少必要的权限,比如”contextMenus”、”storage”、”activeTab”,无法创建右键菜单和保存数据;2. content script里每次鼠标抬起都会创建右键菜单,会导致重复创建,而且没有移除旧的菜单;3. 没有把高亮数据保存到Flask API里,只是存在chrome.storage.sync里,容量有限;4. 没有恢复高亮的逻辑,下次打开网页不会显示之前的高亮;5. background.js里没有注册右键菜单,只有点击事件,第一次使用不会显示菜单;6. 没有获取选中文本的CSS选择器,无法定位高亮位置。

③ 修正口令+最终迭代代码

修正口令:1. 在manifest.json里添加”contextMenus”、”storage”、”activeTab”和对应API的host权限;2. 在background.js里注册右键菜单,在扩展安装时就创建,而不是在content script里;3. 修改content script,在选中文本时显示右键菜单,并且在点击菜单时调用Flask API保存数据,同时保存到chrome.storage.local里;4. 添加恢复高亮的逻辑,在content script加载时从chrome.storage.local里读取之前的高亮数据,添加高亮样式;5. 实现获取选中文本的CSS选择器的逻辑;6. 给每个高亮添加唯一的id,方便后续管理。

TRAE迭代后的最终可用代码:


  1. // manifest.json
  2. {
  3. "manifest_version": 3,
  4. "name": "网页高亮保存插件",
  5. "version": "1.0",
  6. "permissions": ["contextMenus", "storage", "activeTab"],
  7. "host_permissions": ["http://localhost:5000/*", "https://your-api-domain.com/*"],
  8. "background": {
  9. "service_worker": "background.js"
  10. },
  11. "content_scripts": [
  12. {
  13. "matches": ["<all_urls>"],
  14. "js": ["content.js"]
  15. }
  16. ],
  17. "action": {
  18. "default_popup": "popup.html"
  19. }
  20. }

  1. // background.js
  2. // 安装扩展时注册右键菜单
  3. chrome.runtime.onInstalled.addListener(() => {
  4. chrome.contextMenus.create({
  5. id: "save-highlight",
  6. title: "保存高亮",
  7. contexts: ["selection"]
  8. });
  9. });
  10. // 监听右键菜单点击事件
  11. chrome.contextMenus.onClicked.addListener(async (info, tab) => {
  12. if (info.menuItemId === "save-highlight" && info.selectionText) {
  13. // 获取选中文本的选择器
  14. const [result] = await chrome.scripting.executeScript({
  15. target: { tabId: tab.id },
  16. function: getSelectionSelector
  17. });
  18. if (result.success) {
  19. const highlightData = {
  20. user_id: "test_user_123",
  21. content: info.selectionText,
  22. selector: result.selector
  23. };
  24. // 调用后端API保存数据
  25. try {
  26. const response = await fetch("http://localhost:5000/highlights", {
  27. method: "POST",
  28. headers: {
  29. "Content-Type": "application/json"
  30. },
  31. body: JSON.stringify(highlightData)
  32. });
  33. const data = await response.json();
  34. if (data.code === 200) {
  35. // 保存到本地存储
  36. chrome.storage.local.get({highlights: []}, (items) => {
  37. const highlights = items.highlights;
  38. highlights.push({
  39. id: data.data.id,
  40. content: info.selectionText,
  41. selector: result.selector,
  42. url: tab.url
  43. });
  44. chrome.storage.local.set({highlights: highlights});
  45. });
  46. alert("高亮保存成功!");
  47. } else {
  48. alert(`保存失败: ${data.msg}`);
  49. }
  50. } catch (error) {
  51. alert(`网络请求失败: ${error.message}`);
  52. }
  53. }
  54. }
  55. });
  56. // 获取选中文本的CSS选择器
  57. function getSelectionSelector() {
  58. const selection = window.getSelection();
  59. if (!selection.rangeCount) return {success: false};
  60. const range = selection.getRangeAt(0);
  61. const node = range.commonAncestorContainer;
  62. const element = node.nodeType === 1 ? node : node.parentElement;
  63. if (!element) return {success: false};
  64. // 生成简单的CSS选择器
  65. let selector = element.tagName.toLowerCase();
  66. if (element.id) selector += `#${element.id}`;
  67. if (element.className) selector += `.${element.className.split(' ').filter(Boolean).join('.')}`;
  68. return {success: true, selector: selector};
  69. }

  1. // content.js
  2. // 页面加载时恢复高亮
  3. document.addEventListener('DOMContentLoaded', () => {
  4. chrome.storage.local.get({highlights: []}, (items) => {
  5. items.highlights.forEach(highlight => {
  6. if (window.location.href.includes(new URL(highlight.url).pathname)) {
  7. addHighlight(highlight.selector, highlight.content);
  8. }
  9. });
  10. });
  11. });
  12. // 添加高亮
  13. function addHighlight(selector, text) {
  14. const elements = document.querySelectorAll(selector);
  15. elements.forEach(element => {
  16. // 检查元素是否已经有高亮
  17. if (element.classList.contains('highlighted')) return;
  18. const highlight = document.createElement('mark');
  19. highlight.style.backgroundColor = '#fff370';
  20. highlight.style.padding = '2px 4px';
  21. highlight.textContent = text;
  22. // 替换选中文本
  23. const selection = window.getSelection();
  24. const range = selection.getRangeAt(0);
  25. range.deleteContents();
  26. range.insertNode(highlight);
  27. element.classList.add('highlighted');
  28. });
  29. }
  30. // 移除高亮(可选)
  31. function removeHighlights() {
  32. const highlights = document.querySelectorAll('mark.highlighted');
  33. highlights.forEach(highlight => {
  34. const parent = highlight.parentNode;
  35. parent.replaceChild(document.createTextNode(highlight.textContent), highlight);
  36. parent.classList.remove('highlighted');
  37. });
  38. }

工具选型与场景建议

我选择TRAE作为这次开发的主要工具,主要是因为它完美适配国内开发者的需求。首先,TRAE是字节跳动出品的国内首款AI原生IDE,基于VS Code架构,据官方公布数据,已拥有超过600万注册用户,经过字节内部10万级文件、1.5亿行代码的大规模验证,稳定性和性能都有保障。其次,TRAE支持SOLO模式,提供Agent级别的自主开发能力,同时以完整IDE形态呈现,兼顾可视化操作和终端模式,对于我这种从Claude Code迁移过来的开发者来说,不用再纠结工具的使用习惯切换。另外,TRAE内置了Claude 3.5 Sonnet、GPT-4o、Doubao-1.5-pro、DeepSeek等强推理模型,模型切换不需要额外配置,直接在IDE里就能选择,对于国内开发者来说,不用翻墙就能使用Doubao-1.5-pro,非常方便。

在价格方面,TRAE基础版永久免费,Pro版仅需$10/月,对比同类工具比如Claude Code的$20/月,价格更有优势,对于个人开发者和小型团队来说,成本更低。

针对不同的开发场景,我总结了TRAE的使用建议:

  1. 新手学习阶段:使用基础版的SOLO模式,配合中文需求场景的优势,快速上手开发,不需要额外付费;
  2. 小型项目开发:使用SOLO模式,利用Agent级别的能力快速生成代码,减少重复劳动;
  3. 大型项目开发:升级到Pro版,使用Builder模式和CUE智能预测功能,提升团队协作效率;
  4. 需要多模型切换的场景:使用IDE模式,根据不同的需求选择合适的模型,比如复杂逻辑用Claude 3.5 Sonnet,简单脚本用GPT-4o。

常见误区总结

我在开发过程中也遇到了一些常见的误区,总结了以下几点:

  1. 误区1:认为vibe coding可以完全替代手动编码:实际上,TRAE生成的代码还是需要基础的编程知识来校验和调整,比如这次开发的Flask API,TRAE生成的代码有SQL注入风险,需要我手动修正参数化查询的部分;
  2. 误区2:只依赖单一AI模型:不同的模型擅长的领域不同,比如Claude 3.5 Sonnet擅长复杂的逻辑设计,GPT-4o擅长简单的脚本生成,Doubao-1.5-pro更适合中文场景的需求理解;
  3. 误区3:忽略代码的安全问题:TRAE生成的代码可能存在安全漏洞,比如SQL注入、越权访问等,需要开发者自己做安全校验;
  4. 误区4:不做版本控制:直接使用TRAE生成的代码而不提交git,一旦出现问题无法回滚,我这次开发时每次迭代都提交了git,方便回溯代码变更。

结语与互动

这次用vibe coding开发Chrome插件的经历,让我深刻感受到了AI辅助开发的效率提升。对于想要学习vibe coding的开发者来说,我的学习路径是:先从简单的小项目开始,比如做一个Chrome插件、一个简单的Flask API,利用TRAE的SOLO模式快速迭代,遇到问题及时修正,逐步掌握vibe coding的技巧。同时,不要完全依赖AI生成的代码,要保持自己的编程基础,校验代码的正确性和安全性。

你最近有没有用vibe coding做过什么小项目?欢迎在评论区分享你的经验和遇到的问题

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐