「web 插件」可以有几种不同的形态,我先帮你梳理一下常见场景,再讲开发思路和落地步骤:
🔎 常见的 Web 插件形态
-
浏览器扩展(Browser Extension)
- 插件运行在 Chrome/Firefox/Edge 等浏览器里,可以修改网页 DOM、拦截请求、注入脚本。
- 典型例子:广告屏蔽插件、油猴脚本、翻译插件。
-
嵌入式 Web SDK(网站接入用的 JS SDK)
- 以一个 JS 文件形式(如
script src="https://xxx.com/sdk.js"
)被接入到第三方页面。 - 可以控制弹窗、埋点、表单、问卷等。
- 典型例子:客服聊天插件、埋点统计 SDK。
- 以一个 JS 文件形式(如
-
Web Components 自定义组件
- 基于原生标准(Custom Elements、Shadow DOM)封装组件。
- 使用方式:
<my-widget></my-widget>
,可跨框架(Vue/React/Angular)使用。 - 典型例子:支付按钮、视频播放器、分享按钮。
-
前端框架插件(Vue/React 插件)
- 主要用于在特定框架中扩展功能,比如 Vue 插件、React Hook 库。
⚙️ 开发 Web 插件的通用思路
1. 明确目标与运行环境
- 如果要跑在浏览器里:用 浏览器扩展 API。
- 如果要被别人网页引入:用 SDK + Web Components。
- 如果只是内部项目扩展:用 前端框架插件。
2. 技术选型
A. 浏览器扩展
-
技术:JavaScript/TypeScript + Manifest V3 + Chrome APIs
-
文件结构:
cssmanifest.json background.js content.js popup.html options.html
B. 嵌入式 Web SDK
-
技术:原生 JS + Rollup/Vite 打包成 IIFE/UMD 格式
-
思路:
- 提供一个全局入口函数,如
window.MyPlugin.init({ ... })
- 支持在页面 DOM 中创建挂载点(div 或 shadow DOM)
- 可结合 React/Vue/Svelte 来渲染内部 UI
- 需要考虑隔离样式(建议用 shadow DOM)
- 提供一个全局入口函数,如
C. Web Components
-
技术:Custom Elements + Shadow DOM + Lit/Svelte/Vue
-
示例:
scalaclass MyWidget extends HTMLElement { connectedCallback() { this.attachShadow({ mode: "open" }); this.shadowRoot.innerHTML = `<button>Click Me</button>`; } } customElements.define('my-widget', MyWidget);
用法:
<my-widget></my-widget>
3. 核心功能点
- 入口 :
script
标签自动加载,或用户手动调用init()
- UI 隔离:用 Shadow DOM 或 iframe 防止污染宿主页面
- 通信 :提供全局 API(如
window.MyPlugin.show()
),或事件派发 - 配置:通过参数决定插件行为(如样式、接口地址)
- 打包发布 :打包成
sdk.js
,上传到 CDN,用户只需引入即可
4. 进阶能力
- 多框架适配(Vue/React/Angular 都能用)
- 国际化支持
- 主题/样式定制
- 数据上报(埋点、日志)
- 安全防护(防止 XSS、API 泄露)
🚀 开发步骤示例(嵌入式 Web SDK)
-
初始化项目:用 Vite/Rollup 建立 TS 项目。
-
插件核心逻辑 :写一个
init()
方法,挂载到window
。 -
UI 渲染:用 Web Components + Shadow DOM 渲染。
-
打包 :打成单个
sdk.js
文件(UMD/IIFE 格式)。 -
发布:放到 CDN 上。
-
接入测试:
xml<script src="https://cdn.xxx.com/sdk.js"></script> <script> MyPlugin.init({ theme: "dark", lang: "zh" }); </script>
最小可用的 Web 插件(SDK)示例
实现一个「一行代码接入 → 点击按钮弹出对话框」的插件。
📦 项目结构
lua
web-plugin-demo/
├── src/
│ └── index.ts
├── vite.config.ts
├── package.json
📝 核心代码
src/index.ts
ini
// 插件命名空间挂到 window 上
;(function () {
class MyPlugin {
private container: HTMLElement | null = null;
init(options: { text?: string } = {}) {
if (this.container) return; // 避免重复初始化
this.container = document.createElement("div");
document.body.appendChild(this.container);
// 使用 shadow DOM 隔离样式
const shadow = this.container.attachShadow({ mode: "open" });
shadow.innerHTML = `
<style>
.my-btn {
position: fixed;
bottom: 20px;
right: 20px;
background: #4f46e5;
color: white;
border: none;
border-radius: 8px;
padding: 10px 16px;
cursor: pointer;
font-size: 14px;
}
.my-dialog {
position: fixed;
bottom: 70px;
right: 20px;
background: white;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0,0,0,0.15);
padding: 16px;
min-width: 200px;
display: none;
color: black;
}
.my-dialog.active {
display: block;
}
</style>
<button class="my-btn">打开插件</button>
<div class="my-dialog">${options.text || "Hello, Web Plugin!"}</div>
`;
const btn = shadow.querySelector(".my-btn") as HTMLButtonElement;
const dialog = shadow.querySelector(".my-dialog") as HTMLDivElement;
btn.addEventListener("click", () => {
dialog.classList.toggle("active");
});
}
destroy() {
this.container?.remove();
this.container = null;
}
}
// 挂载到全局 window
(window as any).MyPlugin = new MyPlugin();
})();
vite.config.ts
php
import { defineConfig } from "vite";
export default defineConfig({
build: {
lib: {
entry: "src/index.ts",
name: "MyPlugin",
fileName: "my-plugin",
formats: ["iife"], // 单文件,直接给 script 引入
},
},
});
🚀 打包 & 使用
-
安装依赖:
csharpnpm init -y npm i vite typescript -D
并添加
tsconfig.json
。 -
打包:
arduinonpm run build
生成的产物:
dist/my-plugin.iife.js
-
在任意网页接入:
xml<script src="my-plugin.iife.js"></script> <script> MyPlugin.init({ text: "你好,这里是我的Web插件!" }) </script>
打开页面 → 右下角会出现按钮,点击按钮会弹出一个对话框。