Expo Go 内置的 react-native-webview:vendored 模块的架构、平台实现与 Expo 定制补丁解析
【免费下载链接】expoAn open-source framework for making universal native apps with React. Expo runs on Android, iOS, and the web.项目地址: https://gitcode.com/GitHub_Trending/ex/expo
本篇以 Expo 仓库中 Expo Go 应用内嵌的 react-native-webview 模块 为主体,解析这个社区维护的 WebView 组件为什么被 Expo Go 以「vendored module」方式内置、它如何同时覆盖 iOS/Android/macOS/Windows 与新旧两套 React Native 架构,以及 Expo 团队通过 vendoring 配置 与 作用域补丁 在原生层做了什么定制。读完后你能掌握:该模块的目录结构与多平台入口机制、版本升级的操作方式,以及 Expo 定制的 WKProcessPool 作用域隔离原理。
1. 模块定位:为什么 Expo Go 要内置一份 react-native-webview
模块自带的 README 说明了它的来历:React Native WebView是一个社区维护的 WebView 组件,目标取代已从 React Native 核心中移除的内置 WebView;它兼容iOS、Android、Windows 和 macOS,同时支持旧架构(paper)与新架构(fabric),并且明确声明与 Expo 兼容(compatible with expo)。
在 Expo 仓库中,apps/expo-go/modules/下的所有模块遵循一个统一规则——modules 目录说明 指出:Expo Go 中的 vendored 模块意味着「我们将其内置(vendor),并应用位于tools/src/vendoring/config的 Expo-Go 专属补丁」。react-native-webview正是这类模块之一。当前 vendored 版本由 package.json 确认为13.16.1,该版本号与 Expo Go 应用自身的依赖声明(apps/expo-go/package.json中"react-native-webview": "13.16.1")保持一致。
模块自身的依赖极简,仅有两个运行时依赖:
"dependencies": { "escape-string-regexp": "^4.0.0", "invariant": "2.2.4" }peerDependencies要求宿主项目提供任意版本的react与react-native。开发依赖则固定在一个较新的基线上,例如react-native: 0.73.5、react-native-macos: 0.73.17、react-native-windows: 0.73.8、@callstack/react-native-visionos: 0.73.8——从这些版本组合可以推断,该组件是在 RN 0.73 世代完成新架构(Codegen/Fabric)适配的。
README 还给出了官方使用示例,这也是本模块对外暴露的最基本 API:
import React, { Component } from 'react'; import { StyleSheet, Text, View } from 'react-native'; import { WebView } from 'react-native-webview'; // ... const MyWebComponent = () => { return <WebView source={{ uri: 'https://reactnative.dev/' }} style={{ flex: 1 }} />; }组件从react-native-webview导入,通过source属性传入uri/HTML,其余行为由一组onXxx回调与配置 props 驱动。README 同时保留了上游的常见问题提示:出现Invariant Violation: Native component for "RNCWebView does not exist"通常是原生 link 环节出错;Android 在:app:mergeDexRelease阶段构建失败则需要在android/app/build.gradle开启 multidex。项目遵循语义化版本(SemVer),破坏性变更只出现在主版本号,许可证为 MIT。
需要注意一个仓库事实:README 中链接的docs/Getting-Started.md、docs/Reference.md等上游文档并未包含在本仓库的 vendored 副本中(apps/expo-go/modules/react-native-webview/下没有docs目录)。vendoring 配置中明确排除了测试文件(excludeFiles: ['src/__tests__/**/*']),API 参考需以上游仓库文档为准,本仓库内可查证的是src/与lib/中的完整类型定义。
2. 目录结构:一套 TypeScript 源码 + 四套原生实现
vendored 副本的顶层结构清晰地体现了「JS 层统一、原生层分平台」的组织方式:
| 目录/文件 | 职责 |
|---|---|
| src/ | TypeScript 源码:跨平台共享逻辑与四个平台入口 |
| apple/ | iOS 与 macOS 共用的 Objective-C++/Swift 原生实现 |
| android/ | Kotlin/Java 实现,按事件分类组织 JS 事件类 |
| windows/ | C++ 实现(ReactWebView / ReactWebView2 双后端) |
| lib/ | prepare脚本产出的编译产物(JS + d.ts) |
| react-native-webview.podspec | CocoaPods 集成入口,iOS/macOS 共用apple/源码 |
| index.js / index.d.ts | 包入口,指向lib/ |
package.json 中三个入口字段解释了「源码模式 vs 发布模式」的双轨制:
main: index.js—— npm 安装场景,加载lib/编译产物;react-native: src/index.ts—— 在 Metro(Expo/RN 开发链路)中,解析器优先取该字段,直接消费 TypeScript 源码,保证 Expo Go 内置副本可以被调试与热更新;main-internal: src/index.ts—— Expo 内部构建链路的显式入口。
package.json 的codegenConfig声明了 Codegen 规范RNCWebViewSpec(type: "all"),Android 包名为com.reactnativecommunity.webview,iOS 侧映射组件RNCWebView与模块RNCWebViewModule——这就是新架构下src/RNCWebViewNativeComponent.ts能从 spec 生成原生绑定、并在android/newarch与android/oldarch两个目录中各放一份 Manager 实现的结构来源。
2.1 多平台入口与 Web 端的「哑组件」
入口文件 只有一行导出:
import WebView from './WebView'; export { WebView }; export default WebView;而src/目录下并排存在五个 WebView 实现:WebView.ios.tsx、WebView.android.tsx、WebView.macos.tsx、WebView.windows.tsx与 WebView.tsx。RN/Metro 的文件扩展名解析约定决定了不同平台会命中对应后缀的文件;最后的WebView.tsx是不被任何后缀命中的兜底实现,它的源码直白地说明了用途:
// This "dummy" WebView is to render something for unsupported platforms, // like for example Expo SDK "web" platform. const WebView: React.FunctionComponent<WebViewProps> = () => ( <View style={styles.flexStart}> <Text style={styles.colorRed}> React Native WebView does not support this platform. </Text> </View> );即:在 Expo 的 Web 平台上渲染一段红字提示而非崩溃——这保证了含 WebView 的应用可以直接跑在 Expo Web SDK 里。lib/中同名分布的.js/.d.ts文件(WebView.ios.js、WebView.macos.js等)印证了babel --extensions ".ts,.tsx" --out-dir lib src构建脚本的产物布局。
3. Expo 的定制:vendoring 流程与作用域隔离补丁
3.1 升级流程:et uvm命令 + 自动打补丁
modules 目录说明 给出了升级命令:运行et uvm react-native-webview -c "<版本号>",工具会把模块更新到指定版本并重新应用补丁。其自动化逻辑位于 expoGoConfig.ts:
'react-native-webview': { source: 'react-native-webview', sourceType: 'npm', excludeFiles: ['src/__tests__/**/*'], async postCopyFilesHookAsync(sourceDirectory, targetDirectory) { // patch for scoped webview const patchFile = path.join( EXPOTOOLS_DIR, 'src/vendoring/config/react-native-webview-scoping.patch' ); // ... await applyPatchAsync({ patchContent, cwd: targetDirectory, stripPrefixNum: 0 }); }, },流程是:从 npm 拉取指定版本 → 剔除测试文件 → 拷贝进apps/expo-go/modules/→ 以patch -p0方式应用 scoping 补丁。补丁应用失败时工具会打印等价的手动命令(patch -p0 -d <dir> < patch文件>)并抛错,保证 CI 中不会静默丢失定制。
3.2 补丁原理:按 scopeKey 隔离 WKProcessPool
react-native-webview-scoping.patch 是理解 Expo 定制的核心,它改动了apple/下四个文件,目的是让不同 Expo 项目(scope)的 WKWebView 使用相互隔离的进程池与存储上下文,避免多项目共享 Expo Go 容器时发生 cookie/存储串扰:
RNCWKProcessPoolManager.h/.m:上游只有单例方法sharedProcessPool,补丁为其增加了字典_pools与方法- (WKProcessPool *)sharedProcessPoolForScopeKey:(NSString *)scopeKey;逻辑为:
scopeKey为空时回退到原共享池;否则按 key 懒创建并复用独立WKProcessPool。RNCWebViewImpl.h/.m:给视图实现增加@property (nonatomic, strong) NSString *scopeKey,并在创建WKWebViewConfiguration时改为sharedProcessPoolForScopeKey:self.scopeKey(仅在useSharedProcessPool为真时生效)。RNCWebViewManager.mm:Manager 新增接收scopeKey(以及easProjectId、kernelServiceDelegate等参数)的初始化方法,并在view工厂方法中把 scopeKey 注入每个RNCWebViewImpl实例:RNCWebViewImpl *webview = [[RNCWebViewImpl alloc] init]; webview.scopeKey = _scopeKey;
从源码结构看,scopeKey 最终来源于 Expo Go 内核按项目划分的 stable legacy id / scope 体系;同一项目的多个 WebView 共享一个进程池,不同项目之间则完全隔离。这也解释了为什么补丁只覆盖 Apple 平台——Android 侧的隔离由 Expo Go 自身的 WebView CookieManager 上下文机制处理,而本补丁仅针对 WKWebView 的进程池共享行为。
4. 原生事件系统与关键 Props:从类型定义到原生回调
README 指向的 API 参考虽不在 vendored 副本中,但 WebViewTypes.ts 的完整类型定义就是权威参考。核心 Props 包括:
| Prop | 说明(取自类型定义) |
|---|---|
source: WebViewSource | 加载来源,uri/html等(约 L314、L1177) |
onMessage | 接收 Web 端通过 postMessage 桥接的消息(L307、L1254) |
onShouldStartLoadWithRequest | 拦截并控制新请求是否发起,可用于路由外链(L308、L1323) |
injectedJavaScript/injectedJavaScriptObject | 页面加载时向 JS 上下文注入脚本或序列化对象(L293、L1349) |
injectedJavaScriptBeforeContentLoaded及*ForMainFrameOnly系列 | 在 content 加载前注入;ForMainFrameOnly默认true,Android 上为强制行为(L643-L654、L1280-L1289) |
onLoadSubResourceError | 子资源发生 SSL 错误时回调(L1165-L1170) |
allowsBackForwardNavigationGestures、fileSystemAccess(file://URL 相关,L602、L916)等 | 平台特定行为开关 |
iOS 侧由 apple/RNCWebViewImpl.m、RNCWebViewManager.mm(其中RCT_EXPORT_VIEW_PROPERTY(source, NSDictionary)声明了 source 的桥接类型)与RNCWebViewDecisionManager(决策链)等文件承载;Android 侧把每一种事件都建模为独立的事件类,见 android 事件目录:TopMessageEvent、TopLoadingStartEvent、TopLoadingFinishEvent、TopLoadingErrorEvent、TopHttpErrorEvent、TopNewWindowEvent、TopRenderProcessGoneEvent、TopShouldStartLoadWithRequestEvent、SubResourceErrorEvent等——与 JS 侧onXxx回调一一对应,再由RNCWebViewClient/RNCWebChromeClient触发、RNCWebViewManagerImpl.kt组装 View。Windows 侧则由 windows/ReactNativeWebView 下的ReactWebView(传统后端)与ReactWebView2(Edge WebView2 后端)双实现提供。
5. 小结
- 该模块是上游社区项目v13.16.1 的 vendored 副本,README 声明的平台矩阵(iOS/Android/Windows/macOS)与新旧架构双支持,均可由
src/、apple/、android/(newarch + oldarch)、windows/的实际代码结构印证。 - Expo 的价值增量集中在 vendoring 工具链:
et uvm react-native-webview -c <ver>可完成「拉取 npm 版本 → 排除测试 → 打 scoping 补丁」的完整升级闭环。 - scoping 补丁 通过给
RNCWKProcessPoolManager引入sharedProcessPoolForScopeKey:按项目隔离 WKProcessPool,是 Expo Go 多项目并发场景下 Web 内容隔离的关键定制。 - 若需完整的属性文档与 Getting Started 步骤,vendored 副本未携带上游
docs/,请以本仓库 WebViewTypes.ts 中的类型与注释作为实现级参考。
【免费下载链接】expoAn open-source framework for making universal native apps with React. Expo runs on Android, iOS, and the web.项目地址: https://gitcode.com/GitHub_Trending/ex/expo
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考