Wenext Game Plugin(游戏通用插件)
面向 H5 游戏的可插拔组件插件包,提供游戏房间(Game Room)、榜单(Ranking)、活动入口(Activity Entrance)等通用 UI 能力,并在宿主(Host)项目中以“配置驱动”的方式自动挂载(Mount)。
定位与价值(Positioning & Value)
- 在宿主项目内按需挂载(mount)游戏相关 UI 组件,减少重复开发成本
- 通过
plugins.json配置实现跨 App / Game 场景的通用化与差异化输出 - 统一事件总线(Event Bus)与全局桥(Global Bridge)接口,简化与客户端交互
构建模式(Build Mode,库模式 Library Mode)
运行时挂载(Runtime Mount)
事件总线(Event Bus)
可选 Tailwind 3(Tailwind CSS optional)
国际化(i18n)扩展与组件内局部实例
开发环境要求(Dev Requirements)
Node.js:
>= 18(Vite 5 要求)包管理器(Package Manager):
npm/pnpmTypeScript:
^5.9.3Vite:
^5.0.0Vue:
^2.7.7(peer deps)Vue I18n:
^8.28.2(peer deps)Tailwind:
3.x(可选)PostCSS:
^8.4.0,Autoprefixer:^10.4.0SCSS:
sass ^1.80.0集成方式(Integration)
- 宿主侧提供全局桥(Global Bridge)与全局共享变量(Global Properties)
- 通过
loadPlugins(App, gameId, props, globalProperties)启动插件加载与挂载管理 - 使用
plugins.json为不同 App / Game 场景声明组件清单、挂载点、样式定位与启用条件
关键配置项:
- Tailwind 作为可选依赖:
postcss.config.js通过环境变量启用或降级- i18n 集成支持两种模式:
extendI18n或createLocalI18n(由组件选择)
项目引入(Host Integration)
- 安装包与样式引入:
pnpm add @wenext/game-pluginimport '@wenext/game-plugin/style.css'
- 引入 API:
loadPlugins(App, gameId, props, globalProperties):按plugins.json自动注册与挂载registerWenextGamePluginsGlobal(globals):注入MonoNetwork/http、MonoBridge(如getLanguageCode、KVStorageGet、getUserRegion)等event:导出游戏事件常量,供宿主与插件协同使用
- 全局 Vue 要求:
- 目标环境为 Vue 2.7(peer 依赖)。宿主需提供
window.Vue(或直接使用 Vue 2.7 工程)。 - 若宿主为 Vue 3,请确保兼容提供
window.Vue.createApp;当前组件主要以 Vue 2 构建,推荐宿主使用 Vue 2.7。
- 目标环境为 Vue 2.7(peer 依赖)。宿主需提供
- 修改插件配置:
- 当前由库内
plugins.json驱动。如需调整挂载点与组件清单,请在本仓库修改packages/wenext-game-plugin/plugins.json后重新构建与发布。
- 当前由库内
- CDN 与资源:
- 图片引入需要使用import形式,如:
import rankBg1 from './assets/rank_bg_1.webp' - 组件内静态资源(如图片、图标等)建议托管至 CDN(参考
src/core/cdn.ts的getCdnUrl(cdnPath, file))。 - 上传路径:阿里云OSS https://oss.console.aliyun.com/bucket/oss-ap-southeast-1/activity-system/object?path=common-libs%2Fgame-plugin%2F
- 图片引入需要使用import形式,如:
export const getCdnUrl = (cdnPath: string, fileName: string) => {
if (!cdnPath || !fileName) return '';
return `https://web-resource-cf.wenext-activity.chat/common-libs/game-plugin/${cdnPath}/${fileName}`;
};cdnPath 为组件名称,对应cdn上的文件夹,示例:
game-room:游戏房间组件ranking:榜单组件activity-entrance:活动入口组件
fileName 为组件内静态资源文件名,示例:
rank_bg_1.webp:榜单组件背景图entrance_icon_1.svg:活动入口组件图标
使用说明(Usage)
入口与导出
- 引入样式:
import '@wenext/game-plugin/style.css' - 包入口导出:
import { loadPlugins, registerWenextGamePluginsGlobal } from '@wenext/game-plugin' - 事件常量:
import { eventBus, GameCoreEvents } from '@wenext/game-plugin'
宿主侧初始化
- 全局桥注入(网络、端能力、工具等)通过
registerWenextGamePluginsGlobal:tsimport VueI18n from "vue-i18n"; import * as MonoUtils from "@mono/utils"; import * as MonoBridge from "@mono/bridge"; import MonoNetwork from "@mono/network"; import * as MonoNetworkConstant from "@mono/network/constant"; import AvatarView from "@mono/ui/components/AvatarView.vue"; import { loadPlugins, registerWenextGamePluginsGlobal, eventBus, GameCoreEvents } from '@wenext/game-plugin'; import '@wenext/game-plugin/style.css'; // Vue 2.7 环境 // loadPlugins之前需注入全局桥(Mono*):网络/http、端能力/Bridge、工具/Utils、UI等 registerWenextGamePluginsGlobal({ MonoUtils, MonoBridge, MonoNetwork: { http: MonoNetwork, ...MonoNetworkConstant }, MonoUI: { AvatarView }, }); const App = 'wyak'; const gameId = 'wyak-greedy'; // 或 'wyak-greedy-personal' 等 const props = {}; const globalProperties = { stores: {}, i18n: new VueI18n(), constants: {}, components: {}, }; // 加载插件:根据 plugins.json 自动挂载组件 loadPlugins(App, gameId, props, globalProperties); // 页面卸载时清理 eventBus.clear();
锚点与动态挂载
- 固定锚点:需在宿主页面 DOM 中提供
plugins.json指定的anchorIdhtml<div id="rank-top-3"></div> <div id="game-activity-entrance"></div> - 动态锚点(
game-room):使用anchorId: 'game-room-entrance-$',按下注项替换$为betOption.idhtml<div id="game-room-entrance-1"></div> <div id="game-room-entrance-2"></div> - 位置样式(
anchorStyle):posY/posX基于设计稿(750px)转换为vw,内部通过setElementTopAndStart适配 RTL/LTR。
动态启用(enabledFunc)
- 在
plugins.json中通过enabledFunc绑定函数名,函数实现位于src/utils/dynamic-enable-funcs.tsjson{ "componentName": "rank-top-3", "anchorId": "rank-top-3", "props": { "gameType": 11 }, "enabledFunc": "checkRankingsEnabled" } - 示例实现:ts
// src/utils/dynamic-enable-funcs.ts export const dynamicEnableFuncs = { checkRankingsEnabled: async () => { const { KVStorageGet } = getMonoBridge(); const { res, err } = await KVStorageGet({ key: KVStorageKey.KEY_LUCK_GAME_RANK_REGION_ENABLE, defaultValue: 'false', }); return !(err || res !== 'true'); }, };
销毁与生命周期
- 插件管理器内部有加载轮询与超时保护,退出页面需调用
destroy()释放定时器与队列。
开发调试(Dev & Debug)
工作区准备:
- 进入插件包:
cd packages/wenext-game-plugin
- 进入插件包:
构建与类型:
- 构建:
pnpm run build - 监听构建:
pnpm run build:dev(Vite--watch+ 生成类型)
- 构建:
本地联调(推荐 pnpm link)
- 在插件包中:bash
pnpm run build:dev pnpm link - 在宿主项目中:bash
pnpm link @wenext/game-plugin - 取消链接:bash
# 宿主侧 pnpm unlink @wenext/game-plugin # 插件包侧 cd packages/wenext-game-plugin && pnpm unlink - 路径链接(备选,跨仓库无全局):bash
pnpm link /absolute-path-to/wenext-h5-common-libs/packages/wenext-game-plugin
- 在插件包中:
Tailwind 可选启用:
- 通过环境变量开启:
WN_ENABLE_TAILWIND=1 pnpm -F @wenext/game-plugin build - 未安装
tailwindcss时自动降级(移除@tailwind指令),详见postcss.config.js
- 通过环境变量开启:
调试要点:
- 控制台日志:
[Plugin register]列出待注册组件清单[Plugin] mounted: <name> ...成功挂载提示
- i18n:组件若提供
createLocalI18n会优先使用局部实例;否则尝试扩展宿主传入的i18n。
- 控制台日志:
事件总线升级说明(Event Bus Upgrade)
本版本在不改变公开 API(on/emit/off/clear)的前提下,为事件总线新增以下能力:
- 来源追踪:为每个监听器记录来源(组件/模块名)
- 重复注册防护:同一事件、同一回调、同一来源的重复注册将被忽略
- 性能统计:记录每个事件的触发次数(emits)、监听数(listeners)、错误次数(errors)
- 控制台可视化:开发环境下定期打印事件统计(节流控制)
使用方式(保持不变)
import { eventBus } from '@wenext/game-plugin';
// 订阅(保持原有签名)
eventBus.on('game:start', (payload) => {
// ...
});
// 触发(保持原有签名)
eventBus.emit('game:start', { id: 123 });
// 移除或清空(保持前两个参数不变,支持可选第三参数来源)
eventBus.off('game:start');
// 按来源移除监听器(不指定 callback 时批量移除该来源的监听)
eventBus.off('game:start', undefined, { source: 'RankingModule' });
eventBus.clear();声明来源(可选)
监听器可以选择性地声明来源:
- 通过
on(event, handler, { source })传入来源
未声明来源时不设置来源标识(通配),任何来源的 emit 都会触发该监听器。
日志与统计
- 是否输出日志与统计由编译时配置
dev控制(节流防刷屏) - 可通过全局配置覆盖默认参数(无需更改 API):
迁移说明(Migration)
- 旧代码无需变更:原有
on/emit/off/clear均保持不变 - 来源策略:未指定来源时不设置来源标识(通配),任何来源的
emit均会触发该监听器;若监听器显式指定了来源,则仅触发相同来源的消息 - 重复注册行为:同一事件、同一回调、同一来源的重复注册将被忽略(旧版会叠加);如需并行处理,请使用不同回调或不同来源标识
来源声明的可选第三参数
为保持前两个参数类型不变,on 支持可选第三参数:
eventBus.on('game:start', handler, { source: 'RankingModule' });触发过滤的可选第三参数
为保持前两个参数类型不变,emit 支持可选第三参数进行来源过滤:
// 仅触发来源为 RankingModule 的监听器
eventBus.emit('game:start', { id: 123 }, { source: 'RankingModule' });
// 未提供来源时:在 requireSourceMatch=true(默认)下,仅触发“未显式声明来源”的监听器
eventBus.emit('game:start', { id: 123 });当指定了 source 时,仅与该来源完全匹配的监听器会被触发; 在 requireSourceMatch=true(默认)时,未指定来源不会触发“具显式来源”的监听器;仅触发未显式声明来源的监听器(兼容旧版)。
破坏性变更(Breaking Changes)
无。公开接口与默认行为保持兼容;新增的重复注册防护仅抑制冗余监听,不影响正常业务逻辑。