@granite-js/micro-frontend 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/GraniteMicroFrontendRuntime.podspec +40 -0
- package/LICENSE +202 -0
- package/LICENSE.react-native-teleport +21 -0
- package/NOTICE +17 -0
- package/README.md +508 -0
- package/android/CMakeLists.txt +34 -0
- package/android/build.gradle +74 -0
- package/android/gradle.properties +4 -0
- package/android/src/main/AndroidManifest.xml +1 -0
- package/android/src/main/cpp/BundleEvaluator.cpp +64 -0
- package/android/src/main/cpp/BundleEvaluator.h +20 -0
- package/android/src/main/cpp/FileReader.cpp +92 -0
- package/android/src/main/cpp/FileReader.h +27 -0
- package/android/src/main/cpp/onLoad.cpp +9 -0
- package/android/src/main/java/com/teleport/extensions/Float.kt +12 -0
- package/android/src/main/java/com/teleport/extensions/View.kt +10 -0
- package/android/src/main/java/com/teleport/global/PortalRegistry.kt +117 -0
- package/android/src/main/java/com/teleport/host/PortalHostView.kt +97 -0
- package/android/src/main/java/com/teleport/host/PortalHostViewManager.kt +41 -0
- package/android/src/main/java/com/teleport/host/PortalReactRootView.kt +116 -0
- package/android/src/main/java/com/teleport/managers/TeleportViewManager.kt +27 -0
- package/android/src/main/java/com/teleport/portal/PortalLayoutStateController.kt +107 -0
- package/android/src/main/java/com/teleport/portal/PortalView.kt +309 -0
- package/android/src/main/java/com/teleport/portal/PortalViewManager.kt +51 -0
- package/android/src/main/java/run/granite/microfrontend/ActivitySessionBinding.kt +87 -0
- package/android/src/main/java/run/granite/microfrontend/BundleEvaluator.kt +46 -0
- package/android/src/main/java/run/granite/microfrontend/GraniteMicroFrontendEvent.kt +62 -0
- package/android/src/main/java/run/granite/microfrontend/GraniteMicroFrontendRuntimeEventRouter.kt +186 -0
- package/android/src/main/java/run/granite/microfrontend/GraniteMicroFrontendRuntimeHost.kt +42 -0
- package/android/src/main/java/run/granite/microfrontend/GraniteMicroFrontendRuntimeModule.kt +56 -0
- package/android/src/main/java/run/granite/microfrontend/GraniteMicroFrontendRuntimePackage.kt +35 -0
- package/android/src/main/java/run/granite/microfrontend/GraniteMicroFrontendSessionStore.kt +116 -0
- package/android/src/main/jni/CMakeLists.txt +64 -0
- package/android/src/test/cpp/CMakeLists.txt +35 -0
- package/android/src/test/cpp/FileReaderTest.cpp +44 -0
- package/android/src/test/java/com/teleport/host/PortalReactRootViewContractTest.kt +76 -0
- package/android/src/test/java/run/granite/microfrontend/GraniteMicroFrontendRuntimeEventRouterTest.kt +216 -0
- package/android/src/test/java/run/granite/microfrontend/GraniteMicroFrontendSessionStoreTest.kt +190 -0
- package/android/test-cpp.sh +12 -0
- package/common/cpp/react/renderer/components/GraniteMicroFrontendRuntimeSpec/ComponentDescriptors.h +24 -0
- package/common/cpp/react/renderer/components/GraniteMicroFrontendRuntimeSpec/PortalShadowRegistry.cpp +31 -0
- package/common/cpp/react/renderer/components/GraniteMicroFrontendRuntimeSpec/PortalShadowRegistry.h +29 -0
- package/common/cpp/react/renderer/components/GraniteMicroFrontendRuntimeSpec/RNTPortalHostViewComponentDescriptor.h +22 -0
- package/common/cpp/react/renderer/components/GraniteMicroFrontendRuntimeSpec/RNTPortalHostViewShadowNode.cpp +7 -0
- package/common/cpp/react/renderer/components/GraniteMicroFrontendRuntimeSpec/RNTPortalHostViewShadowNode.h +43 -0
- package/common/cpp/react/renderer/components/GraniteMicroFrontendRuntimeSpec/RNTPortalHostViewState.h +21 -0
- package/common/cpp/react/renderer/components/GraniteMicroFrontendRuntimeSpec/RNTPortalViewComponentDescriptor.h +54 -0
- package/common/cpp/react/renderer/components/GraniteMicroFrontendRuntimeSpec/RNTPortalViewShadowNode.cpp +7 -0
- package/common/cpp/react/renderer/components/GraniteMicroFrontendRuntimeSpec/RNTPortalViewShadowNode.h +51 -0
- package/common/cpp/react/renderer/components/GraniteMicroFrontendRuntimeSpec/RNTPortalViewState.h +43 -0
- package/dist/module/createMicroFrontendRuntime.js +18 -0
- package/dist/module/createMicroFrontendRuntime.js.map +1 -0
- package/dist/module/createRoute.js +43 -0
- package/dist/module/createRoute.js.map +1 -0
- package/dist/module/host/PendingHostComponent.js +70 -0
- package/dist/module/host/PendingHostComponent.js.map +1 -0
- package/dist/module/host/index.js +6 -0
- package/dist/module/host/index.js.map +1 -0
- package/dist/module/host/pendingHostComponentStore.js +164 -0
- package/dist/module/host/pendingHostComponentStore.js.map +1 -0
- package/dist/module/host/resolveParams.js +60 -0
- package/dist/module/host/resolveParams.js.map +1 -0
- package/dist/module/host/routeMatcher.js +92 -0
- package/dist/module/host/routeMatcher.js.map +1 -0
- package/dist/module/host/types.js +4 -0
- package/dist/module/host/types.js.map +1 -0
- package/dist/module/index.js +12 -0
- package/dist/module/index.js.map +1 -0
- package/dist/module/package.json +1 -0
- package/dist/module/plugin/index.js +4 -0
- package/dist/module/plugin/index.js.map +1 -0
- package/dist/module/plugin/intoShared.js +32 -0
- package/dist/module/plugin/intoShared.js.map +1 -0
- package/dist/module/plugin/microFrontendPlugin.js +58 -0
- package/dist/module/plugin/microFrontendPlugin.js.map +1 -0
- package/dist/module/plugin/prelude.js +23 -0
- package/dist/module/plugin/prelude.js.map +1 -0
- package/dist/module/plugin/resolver.js +25 -0
- package/dist/module/plugin/resolver.js.map +1 -0
- package/dist/module/plugin/types.js +4 -0
- package/dist/module/plugin/types.js.map +1 -0
- package/dist/module/runtime/createMicroFrontendRuntime.js +79 -0
- package/dist/module/runtime/createMicroFrontendRuntime.js.map +1 -0
- package/dist/module/runtime/errors.js +63 -0
- package/dist/module/runtime/errors.js.map +1 -0
- package/dist/module/runtime/parseAppRequest.js +16 -0
- package/dist/module/runtime/parseAppRequest.js.map +1 -0
- package/dist/module/runtime/parseNativeRuntimeEvent.js +54 -0
- package/dist/module/runtime/parseNativeRuntimeEvent.js.map +1 -0
- package/dist/module/runtime/registry.js +87 -0
- package/dist/module/runtime/registry.js.map +1 -0
- package/dist/module/session/MicroFrontendSessionContext.js +32 -0
- package/dist/module/session/MicroFrontendSessionContext.js.map +1 -0
- package/dist/module/session/useMicroFrontendSessions.js +47 -0
- package/dist/module/session/useMicroFrontendSessions.js.map +1 -0
- package/dist/module/specs/NativeGraniteMicroFrontendRuntime.js +21 -0
- package/dist/module/specs/NativeGraniteMicroFrontendRuntime.js.map +1 -0
- package/dist/module/specs/PortalHostViewNativeComponent.ts +9 -0
- package/dist/module/specs/PortalViewNativeComponent.ts +9 -0
- package/dist/module/types.js +2 -0
- package/dist/module/types.js.map +1 -0
- package/dist/typescript/createMicroFrontendRuntime.d.ts +6 -0
- package/dist/typescript/createRoute.d.ts +23 -0
- package/dist/typescript/host/PendingHostComponent.d.ts +14 -0
- package/dist/typescript/host/index.d.ts +5 -0
- package/dist/typescript/host/pendingHostComponentStore.d.ts +11 -0
- package/dist/typescript/host/resolveParams.d.ts +10 -0
- package/dist/typescript/host/routeMatcher.d.ts +7 -0
- package/dist/typescript/host/types.d.ts +32 -0
- package/dist/typescript/index.d.ts +16 -0
- package/dist/typescript/plugin/index.d.ts +2 -0
- package/dist/typescript/plugin/intoShared.d.ts +3 -0
- package/dist/typescript/plugin/microFrontendPlugin.d.ts +3 -0
- package/dist/typescript/plugin/prelude.d.ts +6 -0
- package/dist/typescript/plugin/resolver.d.ts +3 -0
- package/dist/typescript/plugin/types.d.ts +5 -0
- package/dist/typescript/runtime/createMicroFrontendRuntime.d.ts +31 -0
- package/dist/typescript/runtime/errors.d.ts +35 -0
- package/dist/typescript/runtime/parseAppRequest.d.ts +6 -0
- package/dist/typescript/runtime/parseNativeRuntimeEvent.d.ts +3 -0
- package/dist/typescript/runtime/registry.d.ts +35 -0
- package/dist/typescript/session/MicroFrontendSessionContext.d.ts +13 -0
- package/dist/typescript/session/useMicroFrontendSessions.d.ts +8 -0
- package/dist/typescript/specs/NativeGraniteMicroFrontendRuntime.d.ts +21 -0
- package/dist/typescript/specs/PortalHostViewNativeComponent.d.ts +6 -0
- package/dist/typescript/specs/PortalViewNativeComponent.d.ts +6 -0
- package/dist/typescript/types.d.ts +49 -0
- package/ios/GraniteMicroFrontendRuntimeHost+Internal.h +18 -0
- package/ios/GraniteMicroFrontendRuntimeHost.h +34 -0
- package/ios/GraniteMicroFrontendRuntimeHost.mm +485 -0
- package/ios/GraniteMicroFrontendRuntimeModule.h +6 -0
- package/ios/GraniteMicroFrontendRuntimeModule.mm +138 -0
- package/ios/PortalHostContainerView.h +56 -0
- package/ios/PortalHostContainerView.mm +133 -0
- package/ios/PortalHostView.h +39 -0
- package/ios/PortalHostView.mm +153 -0
- package/ios/PortalRegistry.h +38 -0
- package/ios/PortalRegistry.mm +228 -0
- package/ios/PortalView.h +32 -0
- package/ios/PortalView.mm +426 -0
- package/package.json +113 -0
- package/react-native.config.js +13 -0
- package/src/createMicroFrontendRuntime.ts +22 -0
- package/src/createRoute.ts +84 -0
- package/src/host/PendingHostComponent.tsx +92 -0
- package/src/host/index.ts +26 -0
- package/src/host/pendingHostComponentStore.ts +241 -0
- package/src/host/resolveParams.ts +70 -0
- package/src/host/routeMatcher.ts +117 -0
- package/src/host/types.ts +45 -0
- package/src/index.ts +57 -0
- package/src/plugin/index.ts +2 -0
- package/src/plugin/intoShared.ts +50 -0
- package/src/plugin/microFrontendPlugin.ts +57 -0
- package/src/plugin/prelude.ts +73 -0
- package/src/plugin/resolver.ts +73 -0
- package/src/plugin/types.ts +6 -0
- package/src/runtime/createMicroFrontendRuntime.ts +115 -0
- package/src/runtime/errors.ts +86 -0
- package/src/runtime/parseAppRequest.ts +22 -0
- package/src/runtime/parseNativeRuntimeEvent.ts +54 -0
- package/src/runtime/registry.ts +131 -0
- package/src/session/MicroFrontendSessionContext.tsx +45 -0
- package/src/session/useMicroFrontendSessions.ts +73 -0
- package/src/specs/NativeGraniteMicroFrontendRuntime.ts +44 -0
- package/src/specs/PortalHostViewNativeComponent.ts +9 -0
- package/src/specs/PortalViewNativeComponent.ts +9 -0
- package/src/types.ts +55 -0
package/README.md
ADDED
|
@@ -0,0 +1,508 @@
|
|
|
1
|
+
# @granite-js/micro-frontend
|
|
2
|
+
|
|
3
|
+
Micro-frontend contracts for Granite React Native brownfield applications.
|
|
4
|
+
|
|
5
|
+
This package is the complete micro-frontend feature boundary. It contains:
|
|
6
|
+
|
|
7
|
+
- the build plugin that produces self-registering remote bundles;
|
|
8
|
+
- the JavaScript runtime that loads, evaluates, and imports remote modules;
|
|
9
|
+
- the Mono-Hermes native session and visibility contract;
|
|
10
|
+
- the host pending component contract; and
|
|
11
|
+
- the Portal primitive that attaches a mounted React subtree to an
|
|
12
|
+
`Activity`- or `UIViewController`-owned destination.
|
|
13
|
+
|
|
14
|
+
`@granite-js/portal` is not a separate dependency. Portal is coupled to the
|
|
15
|
+
shared-runtime session contract and is exported from this package.
|
|
16
|
+
|
|
17
|
+
## Ownership model
|
|
18
|
+
|
|
19
|
+
```text
|
|
20
|
+
Granite brownfield host
|
|
21
|
+
├── native screen lifecycle
|
|
22
|
+
│ ├── session identity
|
|
23
|
+
│ └── presentation visibility
|
|
24
|
+
├── Brownfield Brick registry
|
|
25
|
+
│ └── close the current native view
|
|
26
|
+
├── one retained React Native runtime
|
|
27
|
+
│ ├── evaluate remote bundle
|
|
28
|
+
│ ├── keep one React tree per session
|
|
29
|
+
│ └── render each tree through Portal
|
|
30
|
+
└── native Portal destination
|
|
31
|
+
└── attach content by the same sessionId
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Native screen lifecycle is the source of truth for visibility. Portal only
|
|
35
|
+
owns where content is attached. A Portal host can remain attached while its
|
|
36
|
+
screen is stopped or covered, so Portal attachment must not be used to infer
|
|
37
|
+
presentation visibility.
|
|
38
|
+
|
|
39
|
+
The session identifier joins the contracts:
|
|
40
|
+
|
|
41
|
+
```text
|
|
42
|
+
register native session(sessionId)
|
|
43
|
+
→ openApp(sessionId, appName, scheme)
|
|
44
|
+
→ <MicroFrontendSessionProvider sessionId={sessionId}>
|
|
45
|
+
→ <Portal hostName={sessionId}>
|
|
46
|
+
→ native Portal host named sessionId
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Installation
|
|
50
|
+
|
|
51
|
+
```sh
|
|
52
|
+
yarn add @granite-js/micro-frontend
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
The package autolinks one Android library and one iOS Pod. Its React Native
|
|
56
|
+
Codegen library, `GraniteMicroFrontendRuntimeSpec`, contains both the
|
|
57
|
+
`GraniteMicroFrontendRuntime` TurboModule and the `PortalView` / `PortalHostView`
|
|
58
|
+
Fabric components.
|
|
59
|
+
|
|
60
|
+
## Build plugin
|
|
61
|
+
|
|
62
|
+
The app name comes from `granite.config.ts`. Hosts do not maintain a separate
|
|
63
|
+
remote-app list.
|
|
64
|
+
|
|
65
|
+
```ts
|
|
66
|
+
// Remote app
|
|
67
|
+
import { microFrontend } from '@granite-js/micro-frontend/plugin';
|
|
68
|
+
import { defineConfig } from '@granite-js/react-native/config';
|
|
69
|
+
|
|
70
|
+
export default defineConfig({
|
|
71
|
+
appName: 'cart',
|
|
72
|
+
plugins: [
|
|
73
|
+
microFrontend({
|
|
74
|
+
exposes: {
|
|
75
|
+
'./App': './src/_app.tsx',
|
|
76
|
+
},
|
|
77
|
+
shared: ['react', 'react-native'],
|
|
78
|
+
}),
|
|
79
|
+
],
|
|
80
|
+
});
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
```ts
|
|
84
|
+
// Host app
|
|
85
|
+
import { microFrontend } from '@granite-js/micro-frontend/plugin';
|
|
86
|
+
import { defineConfig } from '@granite-js/react-native/config';
|
|
87
|
+
|
|
88
|
+
export default defineConfig({
|
|
89
|
+
appName: 'shared',
|
|
90
|
+
plugins: [
|
|
91
|
+
microFrontend({
|
|
92
|
+
shared: {
|
|
93
|
+
react: { eager: true },
|
|
94
|
+
'react-native': { eager: true },
|
|
95
|
+
},
|
|
96
|
+
}),
|
|
97
|
+
],
|
|
98
|
+
});
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
When evaluated, a remote bundle registers its `appName` container and exposed
|
|
102
|
+
modules in the shared runtime registry.
|
|
103
|
+
|
|
104
|
+
## JavaScript runtime
|
|
105
|
+
|
|
106
|
+
The adapter owns bundle selection, download, integrity verification, and
|
|
107
|
+
caching. It returns an absolute local bundle path.
|
|
108
|
+
|
|
109
|
+
```ts
|
|
110
|
+
import { createMicroFrontendRuntime } from '@granite-js/micro-frontend';
|
|
111
|
+
import type { ComponentType } from 'react';
|
|
112
|
+
|
|
113
|
+
const runtime = createMicroFrontendRuntime({
|
|
114
|
+
adapter: {
|
|
115
|
+
async loadBundle({ appName }) {
|
|
116
|
+
const filePath = await bundleStore.loadBundle(appName);
|
|
117
|
+
return { filePath };
|
|
118
|
+
},
|
|
119
|
+
},
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
await runtime.preloadApp('cart');
|
|
123
|
+
|
|
124
|
+
const App = await runtime.importApp<{
|
|
125
|
+
readonly default: ComponentType<{ readonly scheme: string }>;
|
|
126
|
+
}>('cart/App');
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
`importApp('cart/App')` performs:
|
|
130
|
+
|
|
131
|
+
```text
|
|
132
|
+
adapter.loadBundle({ appName: 'cart' })
|
|
133
|
+
→ GraniteMicroFrontendRuntime.evaluateScript({ filePath })
|
|
134
|
+
→ verify that the cart container registered itself
|
|
135
|
+
→ return the cart container's ./App module
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
Concurrent preload/import calls for one app share an evaluation. A successful
|
|
139
|
+
evaluation remains cached until its last native session closes. Failed
|
|
140
|
+
evaluation removes the partial container so a later request can retry.
|
|
141
|
+
Native `preloadApp` events invoke `preloadApp()` inside the runtime and are not
|
|
142
|
+
forwarded to session listeners. `onPreloadError` is optional and lets the host
|
|
143
|
+
route best-effort native preload failures to its observability provider.
|
|
144
|
+
|
|
145
|
+
The public runtime API is:
|
|
146
|
+
|
|
147
|
+
| API | Responsibility |
|
|
148
|
+
| --- | --- |
|
|
149
|
+
| `preloadApp(appName)` | Load and evaluate one app without importing an exposed module. |
|
|
150
|
+
| `importApp(request)` | Ensure the app is evaluated and import `appName/exposedModule`. |
|
|
151
|
+
| `evaluateScript(filePath)` | Evaluate an already-local bundle in the retained runtime. |
|
|
152
|
+
| `onEvent(listener)` | Subscribe to native open, close, and visibility events. |
|
|
153
|
+
|
|
154
|
+
## Session rendering
|
|
155
|
+
|
|
156
|
+
The brownfield host owns the product-specific session track. Compose the Portal
|
|
157
|
+
destination with `MicroFrontendSessionProvider` so native presentation state
|
|
158
|
+
joins Granite's existing visibility context.
|
|
159
|
+
|
|
160
|
+
```tsx
|
|
161
|
+
const sessions = useMicroFrontendSessions(runtime);
|
|
162
|
+
|
|
163
|
+
function SessionRoot({ session }: { readonly session: MicroFrontendSessionState }) {
|
|
164
|
+
const App = useMemo(
|
|
165
|
+
() => lazy(() => runtime.importApp(`${session.appName}/App`)),
|
|
166
|
+
[session.appName]
|
|
167
|
+
);
|
|
168
|
+
|
|
169
|
+
return (
|
|
170
|
+
<Portal hostName={session.sessionId}>
|
|
171
|
+
<MicroFrontendSessionProvider
|
|
172
|
+
sessionId={session.sessionId}
|
|
173
|
+
presentationVisibility={session.isVisible}
|
|
174
|
+
>
|
|
175
|
+
<App scheme={session.scheme} />
|
|
176
|
+
</MicroFrontendSessionProvider>
|
|
177
|
+
</Portal>
|
|
178
|
+
);
|
|
179
|
+
}
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
`useMicroFrontendSessions(runtime)` owns the fixed React subscription and folds
|
|
183
|
+
native open, close, and visibility events into session descriptors. The host
|
|
184
|
+
continues to own module selection and the rendered Portal tree.
|
|
185
|
+
|
|
186
|
+
The provider exposes the native session identity and combines
|
|
187
|
+
`presentationVisibility` with Granite's existing `VisibilityChangedProvider`.
|
|
188
|
+
Remote apps continue to read the final app, navigation, and native-session
|
|
189
|
+
visibility through `useVisibility()`.
|
|
190
|
+
|
|
191
|
+
Remote apps use Granite's `useVisibility()` for visibility and
|
|
192
|
+
`closeView()` to close the current brownfield view. They do not receive
|
|
193
|
+
`sessionId` as an application prop.
|
|
194
|
+
|
|
195
|
+
## Host pending component
|
|
196
|
+
|
|
197
|
+
Remote apps register a route-level pending component with this package's
|
|
198
|
+
`createRoute` wrapper.
|
|
199
|
+
|
|
200
|
+
```tsx
|
|
201
|
+
import { createRoute, hidePendingHostComponent } from '@granite-js/micro-frontend';
|
|
202
|
+
import { useEffect } from 'react';
|
|
203
|
+
|
|
204
|
+
export const Route = createRoute('/products/:productId', {
|
|
205
|
+
component: ProductPage,
|
|
206
|
+
validateParams: parseProductParams,
|
|
207
|
+
hostPendingComponent: ({ thumbnailUrl }) => (
|
|
208
|
+
<ProductPendingComponent thumbnailUrl={thumbnailUrl} />
|
|
209
|
+
),
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
function ProductPage() {
|
|
213
|
+
useEffect(() => {
|
|
214
|
+
hidePendingHostComponent();
|
|
215
|
+
}, []);
|
|
216
|
+
|
|
217
|
+
return <Product />;
|
|
218
|
+
}
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
The host resolves `PendingHostComponent` from the incoming scheme. The route
|
|
222
|
+
registry and hidden state live on the JavaScript global object, so separately
|
|
223
|
+
bundled host and remote package instances share the same state.
|
|
224
|
+
|
|
225
|
+
## Native event contract
|
|
226
|
+
|
|
227
|
+
The Codegen TurboModule is named `GraniteMicroFrontendRuntime`.
|
|
228
|
+
|
|
229
|
+
```ts
|
|
230
|
+
interface Spec extends TurboModule {
|
|
231
|
+
evaluateScript(request: { readonly filePath: string }): Promise<void>;
|
|
232
|
+
startEventDelivery(): void;
|
|
233
|
+
readonly onEvent: CodegenTypes.EventEmitter<NativeMicroFrontendRuntimeEvent>;
|
|
234
|
+
}
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
These methods are implemented by this package. A brownfield application does
|
|
238
|
+
not implement another TurboModule. Request objects are used so optional fields
|
|
239
|
+
can be added without changing positional arguments. `startEventDelivery()`
|
|
240
|
+
is internal runtime plumbing.
|
|
241
|
+
|
|
242
|
+
Native emits the following events:
|
|
243
|
+
|
|
244
|
+
| Event | Required params | Meaning |
|
|
245
|
+
| --- | --- | --- |
|
|
246
|
+
| `preloadApp` | `appName` | Warm an app. |
|
|
247
|
+
| `openApp` | `sessionId`, `appName`, `scheme` | Create the session's React tree. |
|
|
248
|
+
| `sessionVisibilityChanged` | `sessionId`, `isVisible` | Update presentation visibility from native lifecycle. |
|
|
249
|
+
| `closeApp` | `sessionId` | Unmount the tree and release the app when its last session closes. |
|
|
250
|
+
|
|
251
|
+
Events emitted before JavaScript subscribes are queued. Event delivery starts
|
|
252
|
+
after the runtime installs its first listener.
|
|
253
|
+
|
|
254
|
+
## Brownfield integration checklist
|
|
255
|
+
|
|
256
|
+
The brownfield application must:
|
|
257
|
+
|
|
258
|
+
1. retain one React Native runtime and the controller surface that renders the
|
|
259
|
+
session track;
|
|
260
|
+
2. create a unique `sessionId` for every native destination screen;
|
|
261
|
+
3. resolve `appName` and the incoming `scheme` at the native navigation
|
|
262
|
+
boundary;
|
|
263
|
+
4. bind native screen lifecycle to the session APIs below;
|
|
264
|
+
5. install a Portal destination with the same `sessionId`;
|
|
265
|
+
6. provide `adapter.loadBundle()` and return an absolute verified bundle path;
|
|
266
|
+
7. keep the React tree mounted until native teardown emits `closeApp`; and
|
|
267
|
+
8. keep Granite's base brownfield view APIs, such as scheme resolution and
|
|
268
|
+
closing the current Granite view, in the Granite host integration. They are
|
|
269
|
+
not duplicated by this package.
|
|
270
|
+
|
|
271
|
+
The container-owned Brownfield Brick registry defines how the current native
|
|
272
|
+
view closes. JavaScript uses Granite's `closeView()` command. The session
|
|
273
|
+
lifecycle remains separate: native emits `closeApp(sessionId)` only when the
|
|
274
|
+
container actually tears down so JavaScript can unmount its React tree.
|
|
275
|
+
|
|
276
|
+
## Android native API
|
|
277
|
+
|
|
278
|
+
All public Android APIs live in `run.granite.microfrontend`, except the Portal
|
|
279
|
+
destination views in `com.teleport.host`.
|
|
280
|
+
|
|
281
|
+
### Session APIs
|
|
282
|
+
|
|
283
|
+
| API | Lifetime / behavior |
|
|
284
|
+
| --- | --- |
|
|
285
|
+
| `ActivitySessionBinding.bind(activity, sessionId, appName, scheme)` | Convenience binding for an `Activity`. Emits open, derives visibility from start/stop, and emits close on destroy. Bindings are keyed by `sessionId`, so a destroy caused by a configuration change keeps the session open and the next `bind()` with the same id rebinds the recreated instance instead of opening a second session. Use a `sessionId` that identifies the destination, not the Activity instance. Retain it for the Activity lifetime. |
|
|
286
|
+
| `GraniteMicroFrontendRuntimeHost.registerSession(sessionId)` | Register a custom native container and return a `GraniteMicroFrontendSessionRegistration`. `sessionId` must be unique for every live destination; reuse only after the previous registration is closed/invalidated. |
|
|
287
|
+
| `GraniteMicroFrontendSessionRegistration.openApp(appName, scheme)` | Emit `openApp` once. |
|
|
288
|
+
| `GraniteMicroFrontendSessionRegistration.setVisible(isVisible)` | Emit a visibility event only when the value changes. |
|
|
289
|
+
| `GraniteMicroFrontendSessionRegistration.closeApp()` | Emit `closeApp` once after open. |
|
|
290
|
+
| `GraniteMicroFrontendSessionRegistration.close()` | Unregister the native session. Call after `closeApp()`. |
|
|
291
|
+
| `GraniteMicroFrontendRuntimeHost.emitPreloadApp(appName)` | Fire-and-forget preload. |
|
|
292
|
+
|
|
293
|
+
### Portal destination APIs
|
|
294
|
+
|
|
295
|
+
| API | Lifetime / behavior |
|
|
296
|
+
| --- | --- |
|
|
297
|
+
| `PortalHostView.setName(name)` | Register/unregister the destination name. Use `sessionId`. |
|
|
298
|
+
| `PortalHostView.cleanup()` | Permanently unregister the destination during teardown. |
|
|
299
|
+
| `PortalReactRootView(context, reactHost, surfaceId, moduleName)` | Detached Fabric root that forwards touch/pointer events through the retained `ReactHost`; it does not start another runtime or surface. |
|
|
300
|
+
|
|
301
|
+
`PortalHostView` also re-registers on window attachment and temporarily
|
|
302
|
+
unregisters while detached. `nextInsertionIndexForChildAt()` is renderer
|
|
303
|
+
plumbing, not an application integration API.
|
|
304
|
+
|
|
305
|
+
#### Props the Portal components do not apply
|
|
306
|
+
|
|
307
|
+
`Portal` and the Portal host component are renderer plumbing, not styleable
|
|
308
|
+
views. Their TypeScript props extend `ViewProps` because React Native's codegen
|
|
309
|
+
only accepts `ViewProps` as a base, so the type advertises more than the native
|
|
310
|
+
managers apply. The following are accepted by the type checker and silently
|
|
311
|
+
ignored on Android: `pointerEvents` (the native manager pins it to `box-none`
|
|
312
|
+
so touches reach the portalled tree), `hitSlop`, `focusable`, `accessible`,
|
|
313
|
+
`nativeBackgroundAndroid`, `nativeForegroundAndroid`, `borderStyle`,
|
|
314
|
+
`overflow`, `backfaceVisibility`, `collapsable`, `collapsableChildren`,
|
|
315
|
+
`needsOffscreenAlphaCompositing`, `background*` shorthands, `hasTVPreferredFocus`
|
|
316
|
+
and the `nextFocus*` family.
|
|
317
|
+
|
|
318
|
+
Wrap the Portal in your own `<View>` when you need any of these.
|
|
319
|
+
|
|
320
|
+
### Activity example
|
|
321
|
+
|
|
322
|
+
```kotlin
|
|
323
|
+
import android.os.Bundle
|
|
324
|
+
import android.widget.FrameLayout
|
|
325
|
+
import androidx.appcompat.app.AppCompatActivity
|
|
326
|
+
import com.facebook.react.ReactHost
|
|
327
|
+
import com.facebook.react.bridge.ReactApplicationContext
|
|
328
|
+
import com.facebook.react.uimanager.ThemedReactContext
|
|
329
|
+
import com.teleport.host.PortalHostView
|
|
330
|
+
import com.teleport.host.PortalReactRootView
|
|
331
|
+
import run.granite.microfrontend.ActivitySessionBinding
|
|
332
|
+
|
|
333
|
+
class CartActivity : AppCompatActivity() {
|
|
334
|
+
// The session id identifies the destination, not the Activity instance. A value generated per
|
|
335
|
+
// instance changes on every configuration change, so rotation would close and reopen the session
|
|
336
|
+
// and remount the React tree. Derive it from what the caller navigated to.
|
|
337
|
+
private val sessionId: String
|
|
338
|
+
get() = requireNotNull(intent.data?.host) {
|
|
339
|
+
"CartActivity requires the destination name as the URI host"
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
private lateinit var sessionBinding: ActivitySessionBinding
|
|
343
|
+
private var portalHostView: PortalHostView? = null
|
|
344
|
+
|
|
345
|
+
override fun onCreate(savedInstanceState: Bundle?) {
|
|
346
|
+
super.onCreate(savedInstanceState)
|
|
347
|
+
val scheme = requireNotNull(intent.data).toString()
|
|
348
|
+
|
|
349
|
+
sessionBinding = ActivitySessionBinding.bind(
|
|
350
|
+
activity = this,
|
|
351
|
+
sessionId = sessionId,
|
|
352
|
+
appName = "cart",
|
|
353
|
+
scheme = scheme,
|
|
354
|
+
)
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
fun installPortalHost(
|
|
358
|
+
reactContext: ReactApplicationContext,
|
|
359
|
+
reactHost: ReactHost,
|
|
360
|
+
controllerSurfaceId: Int,
|
|
361
|
+
controllerModuleName: String,
|
|
362
|
+
) {
|
|
363
|
+
val themedContext = ThemedReactContext(
|
|
364
|
+
reactContext,
|
|
365
|
+
this,
|
|
366
|
+
controllerModuleName,
|
|
367
|
+
controllerSurfaceId,
|
|
368
|
+
)
|
|
369
|
+
val rootView = PortalReactRootView(
|
|
370
|
+
themedContext,
|
|
371
|
+
reactHost,
|
|
372
|
+
controllerSurfaceId,
|
|
373
|
+
controllerModuleName,
|
|
374
|
+
)
|
|
375
|
+
val hostView = PortalHostView(themedContext).apply { setName(sessionId) }
|
|
376
|
+
|
|
377
|
+
rootView.addView(
|
|
378
|
+
hostView,
|
|
379
|
+
FrameLayout.LayoutParams(
|
|
380
|
+
FrameLayout.LayoutParams.MATCH_PARENT,
|
|
381
|
+
FrameLayout.LayoutParams.MATCH_PARENT,
|
|
382
|
+
),
|
|
383
|
+
)
|
|
384
|
+
portalHostView = hostView
|
|
385
|
+
setContentView(rootView)
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
override fun onDestroy() {
|
|
389
|
+
portalHostView?.cleanup()
|
|
390
|
+
portalHostView = null
|
|
391
|
+
super.onDestroy()
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
```
|
|
395
|
+
|
|
396
|
+
Install the Portal host only after the retained `ReactHost` has an active
|
|
397
|
+
`ReactApplicationContext`. The brownfield runtime owner still forwards normal
|
|
398
|
+
resume, pause, destroy, and back events to its retained `ReactHost`.
|
|
399
|
+
|
|
400
|
+
Custom screen containers use `registerSession()` and drive `openApp()`,
|
|
401
|
+
`setVisible()`, `closeApp()`, and `close()` from their own lifecycle.
|
|
402
|
+
|
|
403
|
+
## iOS native API
|
|
404
|
+
|
|
405
|
+
Import public APIs from `GraniteMicroFrontendRuntime`.
|
|
406
|
+
|
|
407
|
+
```objc
|
|
408
|
+
#import <GraniteMicroFrontendRuntime/GraniteMicroFrontendRuntimeHost.h>
|
|
409
|
+
#import <GraniteMicroFrontendRuntime/PortalHostContainerView.h>
|
|
410
|
+
```
|
|
411
|
+
|
|
412
|
+
### Session APIs
|
|
413
|
+
|
|
414
|
+
| API | Lifetime / behavior |
|
|
415
|
+
| --- | --- |
|
|
416
|
+
| `+[GraniteMicroFrontendViewControllerSessionBinding bindViewController:sessionId:appName:scheme:]` | Convenience binding for a `UIViewController`. Emits open and derives visibility from `viewWillAppear` / `viewWillDisappear` combined with app foreground and background transitions. Returns `nil` if `sessionId` is already registered. Retain until teardown. |
|
|
417
|
+
| `-[GraniteMicroFrontendViewControllerSessionBinding invalidate]` | Emit close and detach lifecycle observation. |
|
|
418
|
+
| `+[GraniteMicroFrontendRuntimeHost registerSession:]` | Register a custom container and return a session registration, or `nil` if `sessionId` is already registered. Use a unique id per destination and call `invalidate` before reuse. |
|
|
419
|
+
| `-[GraniteMicroFrontendSessionRegistration openAppWithAppName:scheme:]` | Emit `openApp` once. |
|
|
420
|
+
| `-[GraniteMicroFrontendSessionRegistration setVisible:]` | Emit visibility only when it changes. |
|
|
421
|
+
| `-[GraniteMicroFrontendSessionRegistration closeApp]` | Emit `closeApp` once after open. |
|
|
422
|
+
| `-[GraniteMicroFrontendSessionRegistration invalidate]` | Unregister the session. Call after `closeApp`. |
|
|
423
|
+
| `+[GraniteMicroFrontendRuntimeHost emitPreloadApp:]` | Fire-and-forget preload. |
|
|
424
|
+
|
|
425
|
+
### Portal destination APIs
|
|
426
|
+
|
|
427
|
+
| API | Lifetime / behavior |
|
|
428
|
+
| --- | --- |
|
|
429
|
+
| `-initWithFrame:` | Create and immediately activate a UIKit-owned Portal destination after React has booted. |
|
|
430
|
+
| `-initWithFrame:deferredActivation:` | Create before React boot without reading React feature flags. |
|
|
431
|
+
| `-setName:` | Register/unregister the destination name. Use `sessionId`. |
|
|
432
|
+
| `-activateIfNeeded` | Create the Fabric host, attach its touch handler, and apply the pending name. Main thread only, after React boot. |
|
|
433
|
+
| `isActivated` | Whether the underlying Fabric host exists. |
|
|
434
|
+
| `hasAttachedContent` | Whether teleported content is currently attached. This is readiness, not presentation visibility. |
|
|
435
|
+
| `onContentDidAttach` / `onContentDidDetach` | Main-thread readiness callbacks for the first attach and last detach. |
|
|
436
|
+
| `-invalidate` | Unregister the host and clear callbacks during teardown. |
|
|
437
|
+
|
|
438
|
+
### UIViewController example
|
|
439
|
+
|
|
440
|
+
```swift
|
|
441
|
+
import GraniteMicroFrontendRuntime
|
|
442
|
+
import UIKit
|
|
443
|
+
|
|
444
|
+
final class CartViewController: UIViewController {
|
|
445
|
+
/// Create a new id for every push/present. Reusing an id before the previous
|
|
446
|
+
/// binding is invalidated fails registration.
|
|
447
|
+
private let sessionId = UUID().uuidString
|
|
448
|
+
private var sessionBinding: GraniteMicroFrontendViewControllerSessionBinding?
|
|
449
|
+
private var portalHostView: PortalHostContainerView!
|
|
450
|
+
|
|
451
|
+
override func viewDidLoad() {
|
|
452
|
+
super.viewDidLoad()
|
|
453
|
+
|
|
454
|
+
portalHostView = PortalHostContainerView(
|
|
455
|
+
frame: view.bounds,
|
|
456
|
+
deferredActivation: true
|
|
457
|
+
)
|
|
458
|
+
portalHostView.setName(sessionId)
|
|
459
|
+
view.addSubview(portalHostView)
|
|
460
|
+
|
|
461
|
+
sessionBinding = GraniteMicroFrontendViewControllerSessionBinding.bindViewController(
|
|
462
|
+
self,
|
|
463
|
+
sessionId: sessionId,
|
|
464
|
+
appName: "cart",
|
|
465
|
+
scheme: "granite://cart/products/1"
|
|
466
|
+
)
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
func reactRuntimeDidStart() {
|
|
470
|
+
portalHostView.activateIfNeeded()
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
deinit {
|
|
474
|
+
sessionBinding?.invalidate()
|
|
475
|
+
portalHostView?.invalidate()
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
```
|
|
479
|
+
|
|
480
|
+
Create, activate, and invalidate these objects on the main thread. Call
|
|
481
|
+
`invalidate` during teardown so the binding and Portal destination are released
|
|
482
|
+
with the controller.
|
|
483
|
+
|
|
484
|
+
## Portal primitive and example
|
|
485
|
+
|
|
486
|
+
`Portal` remains public for a Portal-only integration or test fixture:
|
|
487
|
+
|
|
488
|
+
```tsx
|
|
489
|
+
import { Portal } from '@granite-js/micro-frontend';
|
|
490
|
+
|
|
491
|
+
<Portal hostName="store">
|
|
492
|
+
<StoreNavigationContainer />
|
|
493
|
+
</Portal>;
|
|
494
|
+
```
|
|
495
|
+
|
|
496
|
+
The React subtree keeps the same owner, context, and state while its native
|
|
497
|
+
views move to the named destination. Host names are application data; a
|
|
498
|
+
generic native destination should read the requested name at runtime.
|
|
499
|
+
|
|
500
|
+
See [examples/portal/README.md](examples/portal/README.md) for the retained
|
|
501
|
+
Portal-only cross-Activity / UIViewController example.
|
|
502
|
+
|
|
503
|
+
## License and credit
|
|
504
|
+
|
|
505
|
+
Apache-2.0. The Portal implementation is based on
|
|
506
|
+
[react-native-teleport](https://github.com/kirillzyusko/react-native-teleport)
|
|
507
|
+
by Kiryl Ziusko; see [NOTICE](NOTICE) and
|
|
508
|
+
[LICENSE.react-native-teleport](LICENSE.react-native-teleport).
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
cmake_minimum_required(VERSION 3.13)
|
|
2
|
+
project(granite-micro-frontend)
|
|
3
|
+
|
|
4
|
+
set(CMAKE_CXX_STANDARD 20)
|
|
5
|
+
set(CMAKE_CXX_EXTENSIONS OFF)
|
|
6
|
+
|
|
7
|
+
# This file is the module's own externalNativeBuild entry point and builds only the library that
|
|
8
|
+
# ships inside the AAR. The codegen target lives in src/main/jni/CMakeLists.txt, which autolinking
|
|
9
|
+
# builds inside the consuming app.
|
|
10
|
+
|
|
11
|
+
add_library(
|
|
12
|
+
granite-micro-frontend
|
|
13
|
+
SHARED
|
|
14
|
+
src/main/cpp/BundleEvaluator.cpp
|
|
15
|
+
src/main/cpp/FileReader.cpp
|
|
16
|
+
src/main/cpp/onLoad.cpp
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
find_package(fbjni REQUIRED CONFIG)
|
|
20
|
+
find_package(ReactAndroid REQUIRED CONFIG)
|
|
21
|
+
|
|
22
|
+
target_include_directories(
|
|
23
|
+
granite-micro-frontend
|
|
24
|
+
PRIVATE
|
|
25
|
+
src/main/cpp
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
target_link_libraries(
|
|
29
|
+
granite-micro-frontend
|
|
30
|
+
android
|
|
31
|
+
fbjni::fbjni
|
|
32
|
+
ReactAndroid::jsi
|
|
33
|
+
ReactAndroid::reactnative
|
|
34
|
+
)
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
buildscript {
|
|
2
|
+
ext.safeExtGet = { name, fallback ->
|
|
3
|
+
rootProject.ext.has(name) ? rootProject.ext.get(name) : fallback
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
repositories {
|
|
7
|
+
google()
|
|
8
|
+
mavenCentral()
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
dependencies {
|
|
12
|
+
classpath "com.android.tools.build:gradle:8.7.2"
|
|
13
|
+
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:${safeExtGet('kotlinVersion', project.properties['GraniteMicroFrontendRuntime_kotlinVersion'])}"
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
apply plugin: 'com.android.library'
|
|
18
|
+
apply plugin: 'kotlin-android'
|
|
19
|
+
apply plugin: 'com.facebook.react'
|
|
20
|
+
|
|
21
|
+
def getIntegerProperty(name) {
|
|
22
|
+
return safeExtGet(name, project.properties["GraniteMicroFrontendRuntime_${name}"]).toInteger()
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
android {
|
|
26
|
+
namespace 'run.granite.microfrontend'
|
|
27
|
+
compileSdkVersion getIntegerProperty('compileSdkVersion')
|
|
28
|
+
|
|
29
|
+
defaultConfig {
|
|
30
|
+
minSdkVersion getIntegerProperty('minSdkVersion')
|
|
31
|
+
targetSdkVersion getIntegerProperty('targetSdkVersion')
|
|
32
|
+
|
|
33
|
+
externalNativeBuild {
|
|
34
|
+
cmake {
|
|
35
|
+
// ANDROID_SUPPORT_FLEXIBLE_PAGE_SIZES is what aligns the AAR's .so to a 16 KB page
|
|
36
|
+
// boundary. React Native passes it for the app build; a library build has to pass it
|
|
37
|
+
// itself, otherwise the .so this module ships stays 4 KB aligned.
|
|
38
|
+
arguments '-DANDROID_STL=c++_shared', '-DANDROID_SUPPORT_FLEXIBLE_PAGE_SIZES=ON'
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
buildFeatures {
|
|
44
|
+
buildConfig true
|
|
45
|
+
prefab true
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
externalNativeBuild {
|
|
49
|
+
cmake {
|
|
50
|
+
path 'CMakeLists.txt'
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
compileOptions {
|
|
55
|
+
sourceCompatibility JavaVersion.VERSION_17
|
|
56
|
+
targetCompatibility JavaVersion.VERSION_17
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
kotlinOptions {
|
|
60
|
+
jvmTarget = '17'
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
testOptions {
|
|
64
|
+
unitTests.returnDefaultValues = true
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
dependencies {
|
|
69
|
+
implementation 'com.facebook.react:react-android'
|
|
70
|
+
implementation 'com.facebook.fbjni:fbjni:0.7.0'
|
|
71
|
+
implementation "org.jetbrains.kotlin:kotlin-stdlib:${safeExtGet('kotlinVersion', project.properties['GraniteMicroFrontendRuntime_kotlinVersion'])}"
|
|
72
|
+
testImplementation 'junit:junit:4.13.2'
|
|
73
|
+
testImplementation 'org.mockito:mockito-core:5.14.2'
|
|
74
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<manifest xmlns:android="http://schemas.android.com/apk/res/android" />
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
#include "BundleEvaluator.h"
|
|
2
|
+
#include "FileReader.h"
|
|
3
|
+
|
|
4
|
+
#include <jsi/jsi.h>
|
|
5
|
+
|
|
6
|
+
namespace granite::microfrontend {
|
|
7
|
+
|
|
8
|
+
using facebook::jni::JString;
|
|
9
|
+
using facebook::jni::alias_ref;
|
|
10
|
+
using facebook::jsi::Runtime;
|
|
11
|
+
using facebook::jsi::StringBuffer;
|
|
12
|
+
|
|
13
|
+
void BundleEvaluator::registerNatives() {
|
|
14
|
+
registerHybrid(
|
|
15
|
+
{makeNativeMethod("evaluateFileSync", BundleEvaluator::evaluateFileSync)});
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
void BundleEvaluator::evaluateFileSync(
|
|
19
|
+
alias_ref<jhybridobject>,
|
|
20
|
+
jlong runtimePointer,
|
|
21
|
+
alias_ref<JString> filePath,
|
|
22
|
+
alias_ref<JString> sourceUrl) {
|
|
23
|
+
if (runtimePointer == 0) {
|
|
24
|
+
facebook::jni::throwNewJavaException(
|
|
25
|
+
"java/lang/IllegalStateException", "JavaScript runtime is unavailable");
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const std::string path = filePath->toStdString();
|
|
30
|
+
std::string source;
|
|
31
|
+
try {
|
|
32
|
+
source = io::readFileToMemory(path);
|
|
33
|
+
} catch (const io::FileReaderError &error) {
|
|
34
|
+
switch (error.kind()) {
|
|
35
|
+
case io::FileReaderErrorKind::NotFound:
|
|
36
|
+
facebook::jni::throwNewJavaException(
|
|
37
|
+
"java/io/FileNotFoundException", "%s", error.what());
|
|
38
|
+
break;
|
|
39
|
+
case io::FileReaderErrorKind::StatFailed:
|
|
40
|
+
case io::FileReaderErrorKind::ReadFailed:
|
|
41
|
+
facebook::jni::throwNewJavaException(
|
|
42
|
+
"java/io/IOException", "%s", error.what());
|
|
43
|
+
break;
|
|
44
|
+
case io::FileReaderErrorKind::AllocationFailed:
|
|
45
|
+
facebook::jni::throwNewJavaException(
|
|
46
|
+
"java/lang/OutOfMemoryError", "%s", error.what());
|
|
47
|
+
break;
|
|
48
|
+
}
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (source.empty()) {
|
|
53
|
+
facebook::jni::throwNewJavaException(
|
|
54
|
+
"java/io/IOException", "Bundle file is empty: %s", path.c_str());
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
auto *runtime = reinterpret_cast<Runtime *>(runtimePointer);
|
|
59
|
+
runtime->evaluateJavaScript(
|
|
60
|
+
std::make_shared<StringBuffer>(std::move(source)),
|
|
61
|
+
sourceUrl->toStdString());
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
} // namespace granite::microfrontend
|