Skip to content

Commit

Permalink
feat: support ssr frameworks, createLinkBridgeProvider (gronxb#33)
Browse files Browse the repository at this point in the history
* fix: modify package dependency paths

* fix: use-sync-external-store shim index.js module

* fix(vue): exports field

* refactor: use-sync-external-store-with-selector

* fix: tsconfig module resolution node

* fix: lint

* fix: exports cts

* feat: createLinkBridgeProvider

* chore: lock
  • Loading branch information
gronxb authored Mar 24, 2024
1 parent 53f5e00 commit b1008b1
Show file tree
Hide file tree
Showing 15 changed files with 135 additions and 63 deletions.
17 changes: 8 additions & 9 deletions packages/react-native/src/createWebView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,14 @@ import React, {
import type { WebViewMessageEvent, WebViewProps } from "react-native-webview";
import WebView from "react-native-webview";

import
{
handleBridge,
handleLog,
INJECT_BRIDGE_METHODS,
INJECT_BRIDGE_STATE,
INJECT_DEBUG,
LogType,
} from "./integrations";
import {
handleBridge,
handleLog,
INJECT_BRIDGE_METHODS,
INJECT_BRIDGE_STATE,
INJECT_DEBUG,
LogType,
} from "./integrations";
import { handleRegisterWebMethod } from "./integrations/handleRegisterWebMethod";
import type { BridgeWebView } from "./types/webview";

Expand Down
2 changes: 1 addition & 1 deletion packages/react-native/src/useBridge.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { Bridge, BridgeStore, ExtractStore } from "@webview-bridge/types";
import { useSyncExternalStore } from "use-sync-external-store/shim";
import { useSyncExternalStore } from "use-sync-external-store/shim/index.js";

export function useBridge<T extends Bridge>(
store: BridgeStore<T>,
Expand Down
1 change: 1 addition & 0 deletions packages/react/esbuild.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import packageJson from "./package.json" assert { type: "json" };

const external = [
...Object.keys(packageJson?.dependencies ?? {}),
...Object.keys(packageJson?.devDependencies ?? {}),
...Object.keys(packageJson?.peerDependencies ?? {}),
];

Expand Down
15 changes: 6 additions & 9 deletions packages/react/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,9 @@
"types": "dist/typescript/index.d.ts",
"exports": {
".": {
"require": {
"default": "./dist/commonjs/index.cjs",
"types": "./dist/typescript/index.d.cts"
},
"import": {
"default": "./dist/module/index.mjs",
"types": "./dist/typescript/index.d.ts"
}
"types": "./dist/typescript/index.d.ts",
"import": "./dist/module/index.mjs",
"require": "./dist/commonjs/index.cjs"
}
},
"scripts": {
Expand All @@ -36,9 +31,11 @@
"test:type": "tsc --noEmit"
},
"devDependencies": {
"@types/react": "^18.2.69",
"@types/use-sync-external-store": "^0.0.6",
"@webview-bridge/web": "workspace:^",
"esbuild": "^0.19.4"
"esbuild": "^0.19.4",
"react": "^18.2.0"
},
"dependencies": {
"use-sync-external-store": "^1.2.0"
Expand Down
67 changes: 67 additions & 0 deletions packages/react/src/createLinkBridgeProvider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import type {
Bridge,
BridgeStore,
LinkBridgeOptions,
} from "@webview-bridge/web";
import { linkBridge } from "@webview-bridge/web";
import { createContext, type ReactNode, useContext, useRef } from "react";

import { useBridge } from "./useBridge";

export interface BridgeProviderProps {
children: ReactNode;
}

export const createLinkBridgeProvider = <
// eslint-disable-next-line @typescript-eslint/no-explicit-any
T extends BridgeStore<T extends Bridge ? T : any>,
>(
options?: LinkBridgeOptions<T>,
) => {
const bridge = linkBridge<T>(options);
const BridgeContext = createContext<BridgeStore | null>(null);

type BridgeStore = typeof bridge;
type BridgeSelector = ReturnType<typeof bridge.store.getState>;

const BridgeProvider = ({ children }: BridgeProviderProps) => {
const storeRef = useRef<BridgeStore>();
if (!storeRef.current) {
storeRef.current = bridge;
}

return (
<BridgeContext.Provider value={storeRef.current}>
{children}
</BridgeContext.Provider>
);
};

const useBridgeStore = <U,>(selector: (state: BridgeSelector) => U): U => {
const bridgeStoreContext = useContext(BridgeContext);

if (!bridgeStoreContext) {
throw new Error(`useBridgeStore must be used within a BridgeProvider`);
}

return useBridge(bridgeStoreContext.store, selector as (state: T) => U);
};

const useBridgeStatus = () => {
const bridgeStoreContext = useContext(BridgeContext);

if (!bridgeStoreContext) {
throw new Error(`useBridgeStatus must be used within a BridgeProvider`);
}

const { isNativeMethodAvailable, isWebViewBridgeAvailable, loose } =
bridgeStoreContext;
return {
isNativeMethodAvailable,
isWebViewBridgeAvailable,
loose,
};
};

return { bridge, BridgeProvider, useBridgeStore, useBridgeStatus };
};
1 change: 1 addition & 0 deletions packages/react/src/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export * from "./createLinkBridgeProvider";
export * from "./useBridge";
32 changes: 12 additions & 20 deletions packages/react/src/useBridge.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,14 @@
import type { Bridge, BridgeStore, ExtractStore } from "@webview-bridge/web";
import { useSyncExternalStore } from "use-sync-external-store/shim";
import type { Bridge, BridgeStore } from "@webview-bridge/web";
import { useSyncExternalStoreWithSelector } from "use-sync-external-store/with-selector.js";

export function useBridge<T extends Bridge>(
export const useBridge = <T extends Bridge, U>(
store: Omit<BridgeStore<T>, "setState">,
): ExtractStore<BridgeStore<T>>;

export function useBridge<
T extends Bridge,
U extends ExtractStore<BridgeStore<T>>,
V,
>(store: Omit<BridgeStore<T>, "setState">, selector?: (state: U) => V): V;

export function useBridge<
T extends Bridge,
U extends ExtractStore<BridgeStore<T>>,
V,
>(store: Omit<BridgeStore<T>, "setState">, selector?: (state: U) => V): V {
const getSnapshot = () =>
selector?.(store.getState() as U) ?? store.getState();
return useSyncExternalStore(store.subscribe, getSnapshot) as V;
}
selector?: (state: T) => U,
): U => {
return useSyncExternalStoreWithSelector(
store.subscribe,
store.getState,
store.getState,
selector ?? ((state: T) => state as unknown as U),
);
};
11 changes: 3 additions & 8 deletions packages/vue/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,9 @@
"types": "dist/typescript/index.d.ts",
"exports": {
".": {
"require": {
"default": "./dist/commonjs/index.cjs",
"types": "./dist/typescript/index.d.cts"
},
"import": {
"default": "./dist/module/index.mjs",
"types": "./dist/typescript/index.d.ts"
}
"types": "./dist/typescript/index.d.ts",
"import": "./dist/module/index.mjs",
"require": "./dist/commonjs/index.cjs"
}
},
"scripts": {
Expand Down
11 changes: 3 additions & 8 deletions packages/web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,9 @@
"types": "dist/typescript/packages/web/src/index.d.ts",
"exports": {
".": {
"require": {
"default": "./dist/commonjs/index.cjs",
"types": "./dist/typescript/packages/web/src/index.d.cts"
},
"import": {
"default": "./dist/module/index.mjs",
"types": "./dist/typescript/packages/web/src/index.d.ts"
}
"types": "./dist/typescript/packages/web/src/index.d.ts",
"import": "./dist/module/index.mjs",
"require": "./dist/commonjs/index.cjs"
}
},
"scripts": {
Expand Down
File renamed without changes.
File renamed without changes.
21 changes: 17 additions & 4 deletions packages/web/src/linkBridge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,16 @@ import type {
ExcludePrimitive,
ExtractStore,
} from "@webview-bridge/types";
import { createRandomId, createResolver, timeout } from "@webview-bridge/util";
import {
createRandomId,
createResolver,
noop,
timeout,
} from "@webview-bridge/util";

import { emitter } from "./emitter";
import { MethodNotFoundError, NativeMethodError } from "./error";
import { linkBridgeStore } from "./linkBridgeStore";
import { emitter } from "./internal/emitter";
import { linkBridgeStore } from "./internal/linkBridgeStore";
import { LinkBridge } from "./types";

export interface LinkBridgeOptions<
Expand Down Expand Up @@ -60,13 +65,21 @@ export const linkBridge = <
throwOnError: false,
},
): LinkBridge<ExcludePrimitive<ExtractStore<T>>, Omit<T, "setState">> => {
if (typeof window === "undefined") {
return {
store: {
getState: () => ({}) as ExcludePrimitive<ExtractStore<T>>,
subscribe: noop,
} as unknown as Omit<T, "setState">,
} as LinkBridge<ExcludePrimitive<ExtractStore<T>>, Omit<T, "setState">>;
}

const {
timeout: timeoutMs = 2000,
throwOnError = false,
onFallback,
onReady,
} = options;

if (!window.ReactNativeWebView) {
console.warn("[WebViewBridge] Not in a WebView environment");
}
Expand Down
2 changes: 1 addition & 1 deletion packages/web/src/registerWebMethod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import type { WebBridge } from "./types";
export const registerWebMethod = <BridgeObject extends WebBridge>(
bridge: BridgeObject,
): BridgeObject => {
if (!window.ReactNativeWebView) {
if (typeof window !== "undefined" && !window.ReactNativeWebView) {
console.warn("[WebViewBridge] Not in a WebView environment");
return bridge;
}
Expand Down
17 changes: 14 additions & 3 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions shared/util/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
"jsx": "react-native",
"lib": ["esnext", "DOM"],
"module": "esnext",
"moduleResolution": "Node",
"strict": true,
"target": "esnext"
}
Expand Down

0 comments on commit b1008b1

Please sign in to comment.