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"
  }
}
相关推荐
武子康4 小时前
Java-82 深入浅出 MySQL 内部架构:服务层、存储引擎与文件系统全覆盖
java·开发语言·数据库·学习·mysql·spring·微服务
守城小轩5 小时前
WebRTC指纹识别——未来展望(下篇)
chrome·webrtc·chrome devtools·指纹浏览器·浏览器开发
月阳羊6 小时前
【硬件-笔试面试题】硬件/电子工程师,笔试面试题-26,(知识点:硬件电路的调试方法:信号追踪,替换,分段调试)
笔记·嵌入式硬件·面试·职场和发展
Star在努力7 小时前
14-C语言:第14天笔记
c语言·笔记·算法
霜绛8 小时前
机器学习笔记(三)——决策树、随机森林
人工智能·笔记·学习·决策树·随机森林·机器学习
站住前面的二哈9 小时前
Cartographer安装测试与模块开发(三)--Cartographer在Gazebo仿真环境下的建图以及建图与定位阶段问题(实车也可参考)
学习·ubuntu
★YUI★9 小时前
学习游戏制作记录(克隆技能)7.25
学习·游戏·unity·c#
屁股割了还要学10 小时前
【C语言进阶】柔性数组
c语言·开发语言·数据结构·c++·学习·算法·柔性数组
woodykissme11 小时前
UG创建的实体橘黄色实体怎么改颜色?
学习·齿轮·ug建模
Feather_7411 小时前
从Taro的Dialog.open出发,学习远程控制组件之【事件驱动】
javascript·学习·taro