ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

web-to-app JS 模块实战:从 module.json 到 main.js 构建带配置 UI 与浮动面板的原生扩展

web-to-app JS 模块实战:从 module.json 到 main.js 构建带配置 UI 与浮动面板的原生扩展 web-to-app JS 模块实战从 module.json 到 main.js 构建带配置 UI 与浮动面板的原生扩展【免费下载链接】web-to-appThe most full featured web-to-app toolkit on Android, a complete APK workshop that runs entirely on your phone项目地址: https://gitcode.com/GitHub_Trending/web/web-to-appJS 模块JS Module是 WebToApp 扩展体系中能力最强的原生扩展格式一个module.json清单、一个main.js脚本、可选 CSS、可选配置 UI 与浮动面板全部打包在一起由应用内的ExtensionManager统一管理并由 WebView 在页面生命周期钩子处注入。读完本文你将能够独立编写、校验并分享一个完整的 WebToApp JS 模块从清单 schema 的每个字段取值到 URL 匹配规则的正则/通配符语义再到main.js运行时契约、配置项系统、浮动面板注册与.wtamod打包分享的全链路实现。一、模块文件布局一个 JS 模块是一个目录包含以下文件my-module/ ├── module.json # 必需 —— 清单 ├── main.js # 必需 —— 在 WebView 中运行 ├── style.css # 可选 —— 当 hasCss / cssCode 存在时自动注入 └── icon.png # 可选 —— ≤256KB;png/svg/webp/jpg/jpegmodule.json是模块的唯一权威描述身份、版本、运行时机、URL 匹配、权限声明、用户可配置字段都定义在这里main.js是实际注入 WebView 执行的 JS 代码style.css的内容会以style节点自动注入页面注入机制见下文 main.js 契约 一节icon.png用于市场展示限制 ≤256KB支持 png/svg/webp/jpg/jpeg。仓库内置的 hello-world 模块 是最小的可用示例auto-scroll 模块 则展示了一个带多项用户配置、权限声明与浮动面板的完整形态二者都可直接作为模板。二、module.json清单 schema完整清单示例{ id: my-module, name: My Module, description: 它做什么, icon: star, category: CONTENT_ENHANCE, tags: [demo], version: { code: 1, name: 1.0.0, changelog: 首次发布 }, author: { name: You, url: https://example.com }, runAt: DOCUMENT_END, urlMatches: [ { pattern: *://example.com/*, isRegex: false, exclude: false } ], permissions: [DOM_ACCESS, STORAGE], configItems: [ { key: greeting, name: 问候语, type: TEXT, defaultValue: Hello, required: true } ] }注意version是一个对象。它包含code整数用于升级比较、namesemver 字符串和changelog三个字段不要把它写成纯字符串。对应实现是 ModuleVersion 数据类code缺省为 1、name缺省为1.0.0。字段参考字段说明id全局唯一导入时若缺失会被重新生成为随机 UUID见下文 校验与缺省。iconMaterial Icons 名称如star、package缺省package。category取值之一CONTENT_FILTER、CONTENT_ENHANCE、STYLE_MODIFIER、THEME、FUNCTION_ENHANCE、AUTOMATION、NAVIGATION、DATA_EXTRACT、DATA_SAVE、INTERACTION、ACCESSIBILITY、MEDIA、VIDEO、IMAGE、AUDIO、SECURITY、ANTI_TRACKING、SOCIAL、SHOPPING、READING、TRANSLATE、DEVELOPER、OTHER共 23 类与 ModuleCategory 枚举 一一对应并在 UI 中归入内容、外观、功能、数据、媒体、安全、生活、开发者等分组展示。runAtDOCUMENT_START、DOCUMENT_END默认、DOCUMENT_IDLE、CONTEXT_MENU、BEFORE_UNLOAD见 ModuleRunTime 枚举。urlMatches[]{pattern, isRegexfalse, excludefalse}见 URL 匹配。permissions[]仅展示用运行时不据此沙箱化。危险项如CAMERA、LOCATION、EVAL、FILE_ACCESS会受到额外审核。configItems[]用户可配置字段见 配置项。源码中权限的“危险”标记是显式建模的ModulePermission 枚举 中每个权限都带有dangerous布尔属性COOKIE、INDEXED_DB、NETWORK、WEBSOCKET、FETCH_INTERCEPT、CLIPBOARD、LOCATION、CAMERA、MICROPHONE、SCREEN_CAPTURE、FILE_ACCESS、EVAL、IFRAME均为危险项全部权限按基础、存储、网络、交互、设备、媒体、文件、高级八个分组组织见 ModulePermissionGroups。另外两个常被忽略的清单字段值得了解uiConfig面板按钮行为对应 ModuleUiConfig ——type当前仅FLOATING_BUTTON、autoHide默认false、autoHideDelay默认 3000ms、initiallyHidden默认false、showOnlyOnMatch默认true即仅在 URL 命中时显示按钮runModeINTERACTIVE默认带面板交互或AUTO静默自动运行对应__MODULE_RUN_MODE__全局。三、URL 匹配urlMatches[]决定模块在哪些页面运行。每条规则为{pattern, isRegex, exclude}三种语义isRegex: false默认—— Chrome 风格 glob。*匹配任意字符*://展开为(https?|ftp|file)://*或all_urls匹配一切。若 glob 翻译无法匹配则回退为子串contains。isRegex: true—— Java 正则KotlinRegex带200ms 超时超时算作不匹配。exclude: true—— 从结果集中移除匹配的 URL。这些行为全部实现在 ExtensionModule.matchesUrl / matchRule 中读源码可以看到几个文档没有展开的实现细节空规则集恒命中urlMatches为空时matchesUrl直接返回true即模块在所有页面生效exclude 优先短路任一 exclude 规则命中立即返回false只有存在 include 规则时才逐条尝试否则返回true纯排除规则集 除被排除 URL 外全部生效glob→正则翻译逐字符扫描*://原位展开为协议组、其余*变为.*、正则特殊字符. ? ^ $ { } ( ) | [ ] \/逐个转义最后锚定^...$翻译后的正则按“大小写不敏感”缓存在一个有界 LRU 中避免每次页面加载重复编译子串回退正则编译失败或匹配异常时退化为url.contains(pattern, ignoreCase true)保证一条写坏的 glob 不会让模块彻底失效正则安全执行isRegex规则通过 safeRegexMatch 在独立守护线程上执行future.get(REGEX_TIMEOUT_MS, ...)等待上限 200ms常量REGEX_TIMEOUT_MS 200L超时抛TimeoutException被吞掉并视为不匹配——这是防止一条病态正则在 URL 匹配热路径上卡死 WebView 的护栏编译后的正则同样进入同一个 64 容量的 LRU 缓存。一个常见写法是只保留一条{pattern: *, isRegex: false, exclude: false}匹配一切hello-world 与 auto-scroll 都如此再按需叠加 exclude 规则剔除特定路径。四、main.js运行时契约注入时你的代码并非裸执行。ExtensionModule.generateExecutableCode() 会把清单元数据序列化为 JS 常量然后把用户代码包进一个带try/catch的 IIFE(function() { use strict; const __MODULE_CONFIG__ {…用户配置…}; const __MODULE_UI_CONFIG__ {…}; const __MODULE_RUN_MODE__ INTERACTIVE; const __MODULE_URL_MATCHES__ […]; const __MODULE_INFO__ { id, name, icon, version, uiConfig, runMode }; const __MODULE_PANEL_HTML__ …你的 panelHtml若有…; function getConfig(key, defaultValue) { … } // CSS 注入若 cssCode 非空 try { …你的 main.js 代码… } catch(e) { console.error([ExtModule: name] Error:, e); } // 自动注册到面板系统若非内置模块且未手动注册 })();关键保证与全局全局值__MODULE_INFO__{id, name, icon, version, uiConfig, runMode}__MODULE_CONFIG__解析后的配置对象key → 字符串值__MODULE_UI_CONFIG__UI 配置即 ModuleUiConfig 的 JSON__MODULE_RUN_MODE__INTERACTIVE或AUTO__MODULE_PANEL_HTML__你的panelHtml若有否则空字符串getConfig(key, defaultValue)读取配置值的便捷访问器__MODULE_CONFIG__[key] ! undefined ? … : defaultValue__MODULE_URL_MATCHES__URL 匹配规则数组面板据此实时显示 Active/Inactive两个工程性事实来自源码错误隔离用户代码段被单独包在try { … } catch (e) { console.error([ExtModule: 名称] Error:, e) }中运行时异常写入console.error绝不破坏宿主页面CSS 自动注入若清单带了cssCode对应style.css注入器会在用户代码执行前创建一个style idext-module-moduleId节点并挂到document.head无 head 时退化为documentElement因此样式模块无需任何 JS 逻辑。最小示例modules/hello-world/main.js// main.js const greeting getConfig(greeting, Hello) const banner document.createElement(div) banner.textContent greeting banner.style.cssText position:fixed;top:0;left:0;z-index:99999;padding:8px;background:#2563eb;color:#fff document.body.appendChild(banner)警告禁止顶层return。因为你的代码被包在 IIFE 里顶层return语句是语法错误会被市场校验器拒绝。这条提示同样内置在应用内的模块编辑器提示文案中“main.js 不要写顶层 return——它会被包进 IIFE顶层 return 是语法错误”。需要条件退出时请改写为普通函数调用。五、配置项系统configItems[]为用户构建设置 UI每一项的完整字段与 ModuleConfigItem 一一对应含缺省值{ key: speedLevel, name: 速度, description: 滚动速度倍数, type: NUMBER, defaultValue: 3, options: [], required: false, placeholder: , validation: }defaultValue是字符串NUMBER/BOOLEAN 也写成3、true在 JS 侧按需parseInt/ 转布尔hello-world 中就示范了parseInt(getConfig(durationMs, 3000), 10) || 3000的防御写法options供SELECT/MULTI_SELECT/RADIO/CHECKBOX类类型提供候选项validation用于附加校验表达式用户保存的值存入模块的configValueskey → value映射运行时整体序列化进__MODULE_CONFIG__。支持的type共 22 种ConfigItemType 枚举TEXT、TEXTAREA、NUMBER、BOOLEAN、SELECT、MULTI_SELECT、RADIO、CHECKBOX、COLOR、URL、EMAIL、PASSWORD、REGEX、CSS_SELECTOR、JAVASCRIPT、JSON、RANGE、DATE、TIME、DATETIME、FILE、IMAGE。auto-scroll 模块 给出了混合类型的实战写法NUMBER的默认速度档位、两个BOOLEAN开关手动交互暂停、反向滚动加一个键盘快捷键开关并在description里写明取值语义“1 ≈ 30 px/s, 10 ≈ 300 px/s”——这直接显示在用户的设置项下方。读取一律走getConfig(key, defaultValue)保证配置缺失时模块仍能按默认值运行。六、交互面板浮动 UI要让模块拥有一个浮动面板而非仅注入一次就跑完的静默脚本需要两件事提供panelHtml面板内容的 HTML 字符串运行时以__MODULE_PANEL_HTML__常量暴露给main.js注册面板按钮在main.js中调用window.__WTA_MODULE_UI__.register({ id: __MODULE_INFO__.id, name: __MODULE_INFO__.name, icon: __MODULE_INFO__.icon })面板内部的事件绑定约定是使用data-wta-actionname属性声明可点击动作处理函数挂到window.__wta_module_action_name上。内置模块 BuiltInModules.kt 中的视频面板就是标准示范window.__wta_module_action_setSpeed function(s) { setSpeed(parseFloat(s)); }; window.__wta_module_action_togglePiP togglePiP; window.__wta_module_action_skipBack function() { var v getVideo(); if (v) { v.currentTime - 10; } };配合data-wta-actionskipBack的按钮即完成“声明式按钮 → 具名处理器”的接线。样式方面面板 HTML/CSS 应使用var(--wta-*)主题变量如--wta-on-surface、--wta-surface-dim、--wta-primary、--wta-accent-soft、--wta-outline均可带缺省回退值做样式使面板自动跟随应用主题切换深浅色——BuiltInModules.kt 中大量使用background:var(--wta-surface-dim,#f9fafb)这类写法。从源码看注册还有一个自动兜底generateExecutableCode()末尾会生成__autoRegister__逻辑——若main.js没有自行调用register()内置模块会跳过以避免覆盖其自带onAction系统会轮询等待window.__WTA_PANEL__初始化完成后自动注册并传入uiConfig、runMode、active由内嵌的__moduleMatchesUrl__()按与宿主端一致的语义计算与panelHtml因此“写了panelHtml但忘了注册”的模块也能正常出现在面板中。七、多文件模块codeFiles是一个Map文件名, 源码ExtensionModule.codeFiles。注入时的执行代码选取逻辑在 generateExecutableCode()若codeFiles非空入口点从main.js、index.js、app.js、script.js、content.js中自动识别优先排序这些入口名所有文件按“入口文件优先、其余按键名排序”拼接每段带// path 头注释因此多文件模块的每个文件都能看到自己在控制台报错时对应的位置若codeFiles为空则直接使用单文件code字段。八、校验、缺省值与导入清洗导入module.json时经过两道防线validate()ExtensionModule.kt#L982-L995name为空报错code、cssCode、codeFiles三者全空报错每个required: true的配置项必须有已填值否则按项名生成“必填项缺失”错误sanitized()ExtensionModule.kt#L702-L734由于 Gson 通过 Unsafe 分配实例、会绕过 Kotlin 默认值清洗函数把每个对象型字段强制回退到声明缺省值icon→package、runAt→DOCUMENT_END、uiConfig→ModuleUiConfig.DEFAULT、runMode→INTERACTIVE等并在id缺失/空白时重新生成 UUID。这解释了清单字段参考中大量“缺省”行为的来源。九、打包与分享模块导出扩展名.wtamod模块打包多模块.wtapkg常量定义在 ExtensionManager.kt#L40-L41导入器同样只接受这两种扩展名分享码前缀WTA1:全量 JSON gzip Base64或WTA2:差异载荷 极限压缩均可通过二维码分享。分享码机制的完整实现在 ExtensionModule companion object几个精确事实发射策略toShareCode()优先生成 V1仅当 V1 字节数超过单码容量时才退化为 V2toShareCodeV2这样旧版本 App 仍能读取新模块的二维码物理容量QR_SINGLE_CODE_MAX_BYTES 2953字节——QR 版本 40、ECC-L、字节模式的单码上限ASCII 分享码恰为 1 字节/字符V2 压缩原理compactShareJson把清单 JSON 与一份“全新默认实例”逐键深比较只保留有差异的键id、createdAt、updatedAt永不携带导入时重新生成再叠加Deflater.BEST_COMPRESSION极限压缩源码注释说明同一模块压缩后约小 40–60%从而让大模块塞进单个二维码解码兼容三代fromShareCode依次识别WTA2:前缀解 gzip 后先把紧凑载荷合并回默认实例再解析避免缺省布尔/整数字段落到 Java 0/false 而非 Kotlin 声明缺省、WTA1:前缀直接解 gzip、以及无远古裸 Base64 的遗留码。十、动手清单从 modules/hello-world 复制目录结构改id/name/category/urlMatches需要用户可调参数时在configItems声明字段并在main.js用getConfig(key, defaultValue)读取注意defaultValue是字符串需要浮动 UI 时写panelHtml按钮用data-wta-action绑定window.__wta_module_action_name样式用var(--wta-*)主题变量记住两条硬约束version必须是对象、main.js顶层不能写return导出为.wtamod分享或在应用内生成二维码超限自动走 V2 紧凑帧。以上全部契约均可在 扩展开发总览 找到四类扩展JS 模块、CSS 模块、油猴脚本、Chrome MV3的横向对比本文对应的原始文档为 docs/zh/extensions/js-module.md。【免费下载链接】web-to-appThe most full featured web-to-app toolkit on Android, a complete APK workshop that runs entirely on your phone项目地址: https://gitcode.com/GitHub_Trending/web/web-to-app创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表