Chrome插件学习笔记(三)

Chrome插件学习笔记(三)

参考文章:

1、项目搭建

在前两篇文章中使用的原生js去操作dom,很费劲并且效果并不是很好,于是决定使用vue3来开发浏览器插件

1.1、创建项目

shell 复制代码
npm create vite@latest

1.2、依赖安装

shell 复制代码
# @types/node、@types/ws:用来补充类型信息
# chokidar:用来监听文件变化
# chrome-types:chrome相关的类型提示
# rollup-plugin-copy:用于复制manifest.json
# ws:用于创建WebSocket
npm install -D @types/node @types/ws chokidar chrome-types rollup-plugin-copy ws

1.3、vite.config.ts

ts 复制代码
import {defineConfig} from 'vite'
import vue from '@vitejs/plugin-vue'
import * as path from "node:path";
import copy from "rollup-plugin-copy";

// https://vite.dev/config/
export default defineConfig({
    server: {
        port: 5500,
    },
    plugins: [
        vue(),
        copy({
            targets: [
                {
                    src: 'src/manifest.json',
                    dest: path.resolve(__dirname, 'dist')
                }
            ],
            hook: 'writeBundle'
        }),
    ],
    build: {
        outDir: path.resolve(__dirname, 'dist'),
        rollupOptions: {
            input: {
                side_panel: path.resolve(__dirname, 'src/index.html'),
                content_script: path.resolve(__dirname, 'src/content_script/content_script.ts'),
                service_worker: path.resolve(__dirname, 'src/service_worker/service_worker.ts'),
            },
            output: {
                assetFileNames: 'assets/[name]-[hash].[ext]',
                chunkFileNames: 'js/[name]-[hash].js',
                entryFileNames: (chunkInfo) => {
                    const facadeModuleId = chunkInfo.facadeModuleId!
                    const baseName = path.basename(facadeModuleId, path.extname(facadeModuleId))
                    const saveArr = ['content_script', 'service_worker']
                    return `[name]/${saveArr.includes(baseName) ? baseName : chunkInfo.name}.js`;
                },
                name: '[name].js'
            }
        }
    }
})

1.4、manifest.json

json 复制代码
{
  "name": "Assistant",
  "description": "A tool for quick conversations with AI",
  "version": "0.0.1",
  "manifest_version": 3,
  "permissions": [
    "sidePanel"
  ],
  "host_permissions": [
    "*://*/*"
  ],
  "action": {
    "default_title": "Click to switch side panel"
  },
  "side_panel": {
    "default_path": "src/index.html"
  },
  "background": {
    "service_worker": "service_worker/service_worker.js",
    "type": "module"
  },
  "content_scripts": [
    {
      "js": [
        "content_script/content_script.js"
      ],
      "matches": [
        "*://*/*"
      ],
      "all_frames": true,
      "run_at": "document_end",
      "match_about_blank": true
    }
  ]
}

1.5、vite-env.d.ts

复制代码
/// <reference types="vite/client" />
/// <reference types="chrome-types/index" />

1.6、service_worker.ts

ts 复制代码
console.log("service_worker");

chrome.sidePanel.setPanelBehavior({openPanelOnActionClick: true})

1.7、content_script.ts

ts 复制代码
console.log("content_script");

1.8、index.html

index.html需要移动到src目录下

1.9、目录结构

1.10、测试

2、热部署

热部署的方式很多,如利用额外的插件(可参考文章https://cm-mng.bilibili.co/agent/#/mlogin),这里使用的是socket通信来实现的热部署

2.1、.env.development

复制代码
VITE_ENV_MODE=development

2.2、 .env.production

复制代码
VITE_ENV_MODE=production

2.3、file_watcher.ts

ts 复制代码
import {WebSocketServer} from "ws";
import chokidar from "chokidar";
import path from "node:path";

export function file_watcher(wss: WebSocketServer) {
    wss.on('connection', (ws) => {
        console.log('Client connected');

        // 监听src文件夹下的所有文件,并排出node_modules下所有文件
        const watcher = chokidar.watch(path.join(__dirname, 'src'), {ignored: [/(^|[\/\\])\../, /node_modules/]});
				
      	// 只有文件变化才会通知,如果设置成all将会通知add、change、addDir等事件
        watcher.on('change', (event, path) => {
            ws.send(JSON.stringify({type: 'reload', event, path}));
        });

        ws.on('close', () => {
            console.log('Client disconnected');
            watcher.close();
        });
    });
}

2.4、vite.config.ts

ts 复制代码
import {defineConfig, loadEnv} from 'vite'
import vue from '@vitejs/plugin-vue'
import * as path from "node:path";
import copy from "rollup-plugin-copy";
import {WebSocketServer} from "ws";
import {file_watcher} from "./file_watcher.ts";

// https://vite.dev/config/
export default defineConfig((configEnv) => {
  	// 这里取的环境变量是.env.xxxxx中的变量,根据加载的环境变量判断是否启动socket服务端,用于通知浏览器插件重新加载
    let env = loadEnv(configEnv.mode, process.cwd(), '');
    if (env.VITE_ENV_MODE === 'development') {
        file_watcher(new WebSocketServer({port: 5501}));
    }
    return {
        server: {
            port: 5500,
        },
        plugins: [
            vue(),
            copy({
                targets: [
                    {
                        src: 'src/manifest.json',
                        dest: path.resolve(__dirname, 'dist')
                    }
                ],
                hook: 'writeBundle'
            }),
        ],
        build: {
            outDir: path.resolve(__dirname, 'dist'),
            rollupOptions: {
                input: {
                    side_panel: path.resolve(__dirname, 'src/index.html'),
                    content_script: path.resolve(__dirname, 'src/content_script/content_script.ts'),
                    service_worker: path.resolve(__dirname, 'src/service_worker/service_worker.ts'),
                },
                output: {
                    assetFileNames: 'assets/[name]-[hash].[ext]',
                    chunkFileNames: 'js/[name]-[hash].js',
                    entryFileNames: (chunkInfo) => {
                        const facadeModuleId = chunkInfo.facadeModuleId!
                        const baseName = path.basename(facadeModuleId, path.extname(facadeModuleId))
                        const saveArr = ['content_script', 'service_worker']
                        return `[name]/${saveArr.includes(baseName) ? baseName : chunkInfo.name}.js`;
                    },
                    name: '[name].js'
                }
            }
        }
    }
})

2.5、hot_reload.ts

ts 复制代码
let timeout: number

export function hot_reload(socket: WebSocket) {
    if (timeout) {
        clearTimeout(timeout);
    }
    socket.onopen = () => {
        console.log('Connected to dev server')
    }

    socket.onmessage = (event) => {
        const message = JSON.parse(event.data)
        if (message.type === 'reload') {
            setTimeout(() => {
                chrome.runtime.reload()
            }, 1000)
        }
    }

    socket.onclose = (event) => {
        console.log(`Socket closed: ${event.reason}`);
      	// 断开连接后5秒后尝试再次连接
        timeout = setTimeout(() => {
            hot_reload(new WebSocket(`ws://localhost:5501/`))
        }, 5000);
    };

    socket.onerror = (error) => {
        console.error('Socket error:', error);
    };
}

2.6、service_worker.ts

ts 复制代码
import {hot_reload} from "../hot_reload.ts";

console.log("service_worker");

chrome.sidePanel.setPanelBehavior({openPanelOnActionClick: true})

// 如果是开发环境才尝试链接socket服务端
if (import.meta.env.VITE_ENV_MODE === 'development') {
    console.log('开发环境');
    hot_reload(new WebSocket(`ws://localhost:5501/`))
} else {
    console.log('生产环境');
}

2.7、package.json

注意这里的scripts需要修改下,开发的时候使用命令npm run watch,修改代码后插件会自动重新加载

json 复制代码
{
  "name": "assistant",
  "private": true,
  "version": "0.0.0",
  "type": "module",
  "scripts": {
    "dev": "vite",
    "watch": "vite build --watch --mode development",
    "build": "vue-tsc -b && vite build",
    "preview": "vite preview"
  },
  "dependencies": {
    "@types/node": "^24.1.0",
    "@types/ws": "^8.18.1",
    "chokidar": "^4.0.3",
    "chrome-types": "^0.1.363",
    "rollup-plugin-copy": "^3.5.0",
    "vue": "^3.5.17",
    "ws": "^8.18.3"
  },
  "devDependencies": {
    "@vitejs/plugin-vue": "^6.0.0",
    "@vue/tsconfig": "^0.7.0",
    "typescript": "~5.8.3",
    "vite": "^7.0.4",
    "vue-tsc": "^2.2.12"
  }
}
相关推荐
疯狂打码的少年7 小时前
【数据库技术】SQL高级查询(分组/排序/聚合函数)
java·数据库·笔记·sql
旖旎夜光7 小时前
【AI入门】大模型介绍全解析:从模型、LLM 到提示词与嵌入
人工智能·笔记·python·学习·ai编程
王的宝库7 小时前
【无标题】
笔记·restful
xxwl5857 小时前
Git常用命令的学习
git·学习
具身AGI7 小时前
从演示到作业,物理AI 人类学习路线 的深意
人工智能·学习
MSTcheng.7 小时前
【Linux】Linux学习第三弹——Linux基本指令2
linux·windows·学习·ubuntu·操作系统
Capricorn19887 小时前
科研智能体出现召回率低与数据覆盖冲突怎么排查?知芽 Notebook Skill 机制拆解
大数据·论文阅读·人工智能·笔记
tju新生代魔迷7 小时前
Verilog HDL学习笔记(三)| 第3章 基本概念(上)——词法约定与数据类型
笔记·学习
Hotchip_MEMS8 小时前
MS2202AB-M15E输出驱动能力:直连MCU GPIO无需三极管
人工智能·笔记·物联网·电脑·制造
Privasa-隐私实验室8 小时前
跨端技术选型|Flutter搭建隐私加密App,安卓与iOS双端差异适配实战
android·笔记·安全·flutter·ios·隐私安全·aes-256