资讯动态

Bilibili-Evolved 深度解析:用户脚本注入、模块化架构与自定义插件开发

发布时间:2026/10/13 17:05:14 来源:尧图企业网站定制
简介Bilibili-Evolved 是一款面向哔哩哔哩网页端的增强脚本基于 JavaScript 编写适合希望深度定制 B 站使用体验的前端爱好者与进阶用户。它通过浏览器脚本管理器加载可扩展播放器、界面与交互功能解决原生页面功能有限、操作繁琐等问题。资源包共约 2000 个文件以 1529 个 TypeScript 源码和 295 个 JavaScript 文件为核心辅以 86 个 CSS、35 个 SCSS 样式文件及 26 个 Vue 组件另含图片、JSON 配置与构建脚本等压缩包整体约 67.36MB结构完整便于二次开发与功能裁剪。目前已有 955 人学习下载。脚本内置正式版、预览版、离线版等多种安装形态并针对跨域资源访问、破坏性更新等场景给出提示读者可借此理解大型用户脚本的模块划分、构建流程与样式组织方式获得可直接运行或改造的完整工程参考。1. 从「Bilibili-Evolved」说起一个把网页端体验重写到极致的前端增强方案如果你每天在浏览器里刷视频却总觉得官方网页端少了点什么——播放器不够顺手、界面元素太杂、批量操作几乎为零——那 Bilibili-Evolved 这个名字大概率已经出现在你的搜索记录里。它不是某个官方功能而是一套运行在浏览器里的用户脚本增强方案核心思路是用 JavaScript 在页面加载后动态注入、改写和扩展原有交互。你可以把它理解成给网页端装了一个可编程的「外挂层」不改动服务端不依赖客户端所有能力都在本地浏览器里完成。它解决的问题非常具体网页端功能更新慢、个性化程度低、重复操作多。适合两类人——一类是想按自己习惯重塑界面的重度用户另一类是前端工程师想找一个真实、复杂、持续维护的脚本工程来学习模块化、样式隔离和 API 拦截。接下来的内容会从架构拆到代码从安装讲到自定义插件中间穿插我踩过的坑目标是让你能自己跑起来、改得动、知道边界在哪。2. 拆开 Bilibili-Evolved 的运行时脚本注入、样式隔离与 API 拦截三件事2.1 它到底在浏览器里做了什么要理解 Bilibili-Evolved 的工作方式先得知道一个用户脚本从加载到生效经历了什么。浏览器扩展或脚本管理器在页面 DOM 构建的早期阶段注入代码此时页面自身的 JavaScript 还没执行完。Bilibili-Evolved 利用这个时间窗口做三件事第一注册自己的模块系统把功能拆成独立组件第二挂载样式表用高优先级选择器覆盖原有 UI第三拦截关键的网络请求和全局对象为后续功能提供数据入口。这套机制的关键在于「时机」。注入太早页面元素还不存在选择器找不到目标注入太晚页面已经渲染完成用户会看到明显的闪烁。常见做法是监听document.readyState和DOMContentLoaded再配合MutationObserver监听后续动态加载的节点。Bilibili-Evolved 内部有一套自己的生命周期管理每个组件声明自己依赖的 DOM 条件满足后才执行初始化。从工程角度看它更像一个微前端框架而不是一堆零散脚本的集合。组件之间通过事件总线和共享状态通信样式通过前缀和 Shadow DOM 做隔离避免和页面原有样式互相污染。这也是它比普通「油猴脚本」更稳定的原因。2.2 最小可运行环境与安装路径你不需要克隆整个仓库就能跑起来。最常见的做法是通过脚本管理器加载构建产物。下面是一个本地验证用的最小步骤假设你已经安装了支持用户脚本的浏览器扩展。# 1. 准备一个本地目录用来存放构建产物和调试文件 mkdir -p ~/dev/be-local cd ~/dev/be-local # 2. 如果你拿到的是源码先安装依赖并构建 # 常见做法是使用项目自带的包管理配置 npm install npm run build # 3. 构建完成后产物通常是一个 .user.js 文件 # 在脚本管理器中新建脚本把文件内容粘贴进去 # 或者直接通过文件 URL 加载部分管理器支持 ls -lh dist/*.user.js这段命令的核心是「构建」和「加载」两个动作。npm install拉取依赖npm run build触发打包流程最终产出一个自包含的脚本文件。参数上要注意不同脚本管理器对match和grant的支持有差异如果加载后没反应先检查脚本头部元数据里的匹配规则是否覆盖了你访问的域名。加载成功后页面右下角通常会出现一个入口按钮点击后展开功能面板。如果按钮没出现打开浏览器控制台看有没有报错最常见的是Uncaught ReferenceError说明某个依赖没被打进产物需要检查构建配置里的外部依赖声明。2.3 模块化结构与自定义组件的接入点Bilibili-Evolved 的功能不是写死的它暴露了一套组件注册接口。你可以把自己写的逻辑挂进去享受它的生命周期管理和样式隔离。下面是一个简化的自定义组件示例展示注册、初始化和销毁三个环节。// 自定义组件在视频页标题旁插入一个标记 // 注意实际接入时需要遵循项目的组件注册规范 const myComponent { name: custom-title-marker, // 声明依赖的 DOM 条件满足后才执行 dependencies: [video-title], // 初始化逻辑 async setup() { const title document.querySelector(.video-title); if (!title) return; // 创建一个标记元素 const marker document.createElement(span); marker.textContent [已标记]; marker.style.cssText color:#fb7299;font-size:12px;margin-left:8px;; title.appendChild(marker); // 保存引用便于销毁时清理 this.marker marker; }, // 销毁逻辑避免内存泄漏 dispose() { if (this.marker this.marker.parentNode) { this.marker.parentNode.removeChild(this.marker); } } }; // 注册到全局组件管理器具体 API 名称以实际项目为准 window.__BE_REGISTRY__?.register(myComponent);这段代码的关键点有三个。第一dependencies声明了组件生效的前提避免在元素不存在时执行。第二setup里做了空值判断这是前端注入脚本的基本素养页面结构随时可能变。第三dispose负责清理单页应用切换路由时如果不销毁旧组件会出现重复插入和内存泄漏。参数方面name要全局唯一否则注册会被覆盖dependencies的字符串需要和项目内部定义的依赖名一致写错了组件永远不会触发。如果你不确定有哪些依赖名可用可以在控制台打印注册表内容或者直接监听 DOM 变化自己判断。3. 从零配置一套可调试的本地开发流构建、热重载与日志分级3.1 构建产物的组成与调试入口当你第一次拿到构建后的文件可能会被它的体积吓到——一个.user.js动辄几百 KB。这很正常因为它把样式、工具函数、组件逻辑全部内联在一起目的是减少运行时请求。调试时不要直接读产物而是回到源码用开发模式启动。常见做法是配置一个开发构建关闭压缩和混淆保留 source map。这样在浏览器控制台里报错时你能直接定位到源码行号。下面是一个典型的开发脚本配置片段。// 开发模式构建配置示例基于常见打包工具 module.exports { mode: development, devtool: inline-source-map, // 关键保留源码映射 optimization: { minimize: false // 关闭压缩便于阅读 }, output: { filename: bilibili-evolved.dev.user.js }, // 外部依赖不打包由脚本管理器提供 externals: { vue: Vue } };devtool设为inline-source-map后控制台报错会直接指向源码文件。minimize: false让产物保持可读方便你搜索关键字。externals的作用是把体积大的库排除出去前提是运行环境里已经有这些库否则会报Vue is not defined。调试入口有两个一是浏览器控制台的 Sources 面板找到注入的脚本文件打断点二是项目自带的日志系统通常支持分级输出。把日志级别调到 debug能看到组件加载、依赖满足、API 拦截等详细过程。3.2 热重载为什么容易翻车以及怎么绕过去热重载在普通 Web 项目里很常见但在用户脚本场景下特别容易出问题。原因是脚本注入的时机和页面生命周期强绑定热重载触发时页面状态可能已经变化旧组件没清理干净新组件又插进来结果就是界面错乱或者功能重复。我一般会分两步走。第一步开发阶段不追求真正的热重载而是用「手动重载 状态清理」代替。在控制台暴露一个全局函数调用后先执行所有组件的dispose再重新注册。// 手动重载辅助函数挂在全局便于控制台调用 window.__reloadBE async function() { // 1. 销毁所有已注册组件 const registry window.__BE_REGISTRY__; if (registry) { for (const comp of registry.getAll()) { try { await comp.dispose?.(); } catch (e) { console.warn(dispose failed:, comp.name, e); } } } // 2. 清空注册表 registry?.clear(); // 3. 重新执行入口逻辑具体函数名以项目为准 window.__BE_BOOTSTRAP__?.(); console.log([BE] reloaded); };这个函数的核心是「先清理再重建」。dispose用 try-catch 包住是因为某个组件清理失败不应该阻塞其他组件。clear清空注册表避免旧组件残留。最后调用入口函数重新初始化。第二步如果确实需要自动重载用文件监听触发页面刷新而不是模块热替换。刷新页面虽然慢一点但状态最干净不会出现玄学问题。参数上监听范围要排除node_modules和构建产物目录否则会陷入「构建触发监听、监听触发构建」的死循环。3.3 日志分级与线上问题定位开发时日志越多越好上线后日志越少越好。Bilibili-Evolved 这类脚本运行在别人的页面上控制台输出太多会被用户当成报错。所以日志分级不是可选项是必选项。常见做法是定义一个日志工具支持 error、warn、info、debug 四个级别默认只输出 warn 以上。用户遇到问题时引导他们在设置里打开 debug 模式再复现一次把控制台输出截图或导出。// 简易日志工具支持运行时切换级别 const LOG_LEVELS { error: 0, warn: 1, info: 2, debug: 3 }; let currentLevel LOG_LEVELS.warn; function setLogLevel(level) { currentLevel LOG_LEVELS[level] ?? LOG_LEVELS.warn; } function log(level, ...args) { if (LOG_LEVELS[level] currentLevel) return; const prefix [BE][${level}]; console[level debug ? log : level](prefix, ...args); } // 使用示例 log(debug, component setup, componentName); log(error, api intercept failed, error);setLogLevel可以在设置面板里绑定一个下拉框让用户自己选。log函数根据级别决定是否输出debug 级别映射到console.log避免控制台里出现一堆 debug 样式的输出。参数上要注意生产构建时可以把currentLevel默认值设为 warn减少噪音。定位线上问题时先让用户打开 debug然后看三类信息组件加载顺序、API 拦截结果、DOM 依赖是否满足。大部分问题出在 DOM 依赖上页面改版后选择器失效组件静默不执行用户以为功能坏了。4. 避坑与排查样式冲突、API 变动和性能损耗的 5 个真实翻车记录4.1 样式覆盖不生效反而把原页面搞乱现象自定义样式写进去了但目标元素没变化反而旁边几个无关元素变了样。原因通常是选择器优先级不够或者用了过于宽泛的选择器。页面原有样式可能带!important或者通过动态类名控制。解决不要用元素选择器裸写加上自己的命名空间前缀并且用:where()降低优先级或者用!important精确打击。更稳妥的做法是把样式挂到 Shadow DOM 里彻底隔离。如果必须全局覆盖先在小范围验证再逐步扩大。4.2 页面改版后组件集体失效现象某天打开页面所有增强功能都没了控制台一堆 null 错误。原因是页面 DOM 结构变了组件依赖的选择器找不到目标。这是注入类方案最常见的翻车方式没有之一。解决把选择器集中管理不要散落在各个组件里。页面改版时只需要改一处。另外给关键组件加上「找不到目标时输出 warn 日志」的逻辑而不是静默失败。这样用户反馈问题时你能快速判断是哪个依赖断了。4.3 拦截 API 导致页面自身功能异常现象增强功能正常但页面原本的某些操作失灵比如点赞没反应、评论加载不出来。原因是拦截了全局的请求方法修改了参数或返回值影响了页面自身的调用。解决拦截时遵循「最小改动」原则只读不写或者只对特定 URL 生效。修改返回值前先确认页面自身是否依赖原始数据。我一般会先打印原始请求和响应确认没有副作用后再加逻辑。另外拦截代码要加 try-catch异常时放行原始请求避免把页面搞崩。4.4 组件重复初始化导致内存泄漏现象页面用久了越来越卡控制台里同一个组件的日志反复出现。原因是单页应用路由切换时旧组件没有销毁新组件又注册了一遍。解决在组件注册前检查是否已存在同名实例存在就先销毁再注册。同时确保dispose里清理了所有事件监听、定时器和 DOM 引用。定时器尤其容易漏setInterval不清除会一直跑。4.5 构建产物体积失控现象产物从几百 KB 涨到几 MB加载明显变慢。原因是引入了体积大的依赖或者把开发用的库打进了生产包。解决检查externals配置把运行环境已有的库排除。用打包分析工具看哪些模块占体积按需引入。样式文件如果很大考虑拆分非首屏需要的样式延迟加载。参数上设置一个体积阈值超过就报警防止不知不觉膨胀。5. 进阶玩法用自定义插件把 Bilibili-Evolved 改造成你的专属工作台当你熟悉了组件注册和生命周期管理就可以跳出「改改样式」的阶段做一些真正省时间的自动化。我自己的习惯是把它当成一个浏览器内的轻量工作台把重复操作封装成插件用快捷键触发。一个典型场景是批量处理视频信息。比如你想把当前页面所有视频的标题、时长、链接导出成结构化数据手动复制粘贴效率太低。下面是一个插件思路展示如何组合 DOM 查询、数据清洗和导出。// 自定义插件导出当前列表页的视频信息 // 依赖列表容器已渲染完成 async function exportVideoList() { // 1. 定位列表容器选择器需要根据实际页面调整 const items document.querySelectorAll(.video-list-item); if (!items.length) { console.warn([BE] no items found, check selector); return; } // 2. 提取字段做空值兜底 const data Array.from(items).map((item, index) { const titleEl item.querySelector(.title); const linkEl item.querySelector(a); const durationEl item.querySelector(.duration); return { index: index 1, title: titleEl?.textContent?.trim() ?? , url: linkEl?.href ?? , duration: durationEl?.textContent?.trim() ?? }; }).filter(item item.title); // 过滤掉空标题的脏数据 // 3. 转成 CSV 格式方便导入表格 const header index,title,url,duration\n; const rows data.map(d ${d.index},${d.title.replace(//g, )},${d.url},${d.duration} ).join(\n); const csv header rows; // 4. 触发下载 const blob new Blob([csv], { type: text/csv;charsetutf-8; }); const a document.createElement(a); a.href URL.createObjectURL(blob); a.download video-list.csv; a.click(); URL.revokeObjectURL(a.href); console.log([BE] exported ${data.length} items); } // 绑定到快捷键比如 CtrlShiftE window.addEventListener(keydown, (e) { if (e.ctrlKey e.shiftKey e.key E) { e.preventDefault(); exportVideoList(); } });这段代码的要点在数据清洗和导出环节。querySelectorAll拿到的是类数组转成真数组才能用 map 和 filter。每个字段都做了空值兜底因为页面结构不一定规整。CSV 里的引号需要转义否则标题里带逗号会破坏格式。最后用 Blob 和 URL.createObjectURL 触发下载记得 revoke 释放内存。参数上选择器是最容易翻车的地方建议先在控制台里验证document.querySelectorAll的结果数量再写进插件。快捷键绑定要加preventDefault避免和页面自身快捷键冲突。如果导出数据量大考虑分页或加进度提示避免主线程卡死。验证插件是否生效可以看三个信号控制台有没有输出导出条数、下载目录里有没有 CSV 文件、文件内容是否和页面显示一致。如果条数为零检查选择器如果文件为空检查 filter 条件如果格式错乱检查转义逻辑。我自己的习惯是每写一个新插件先在控制台里把核心逻辑跑一遍确认数据正确后再封装成组件。这样能省掉大量「改代码、刷新页面、看结果」的循环时间。另外插件不要贪多一个插件只做一件事组合起来才灵活。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑