react-native-instant-webview 0.1.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/README.md +170 -0
- package/lib/commonjs/PooledWebView.js +112 -0
- package/lib/commonjs/PooledWebView.js.map +1 -0
- package/lib/commonjs/WebViewManager.js +102 -0
- package/lib/commonjs/WebViewManager.js.map +1 -0
- package/lib/commonjs/WebViewPoolProvider.js +102 -0
- package/lib/commonjs/WebViewPoolProvider.js.map +1 -0
- package/lib/commonjs/WebViewSlot.js +91 -0
- package/lib/commonjs/WebViewSlot.js.map +1 -0
- package/lib/commonjs/constants.js +51 -0
- package/lib/commonjs/constants.js.map +1 -0
- package/lib/commonjs/index.js +41 -0
- package/lib/commonjs/index.js.map +1 -0
- package/lib/commonjs/types.js +6 -0
- package/lib/commonjs/types.js.map +1 -0
- package/lib/commonjs/usePooledWebView.js +54 -0
- package/lib/commonjs/usePooledWebView.js.map +1 -0
- package/lib/module/PooledWebView.js +105 -0
- package/lib/module/PooledWebView.js.map +1 -0
- package/lib/module/WebViewManager.js +96 -0
- package/lib/module/WebViewManager.js.map +1 -0
- package/lib/module/WebViewPoolProvider.js +92 -0
- package/lib/module/WebViewPoolProvider.js.map +1 -0
- package/lib/module/WebViewSlot.js +84 -0
- package/lib/module/WebViewSlot.js.map +1 -0
- package/lib/module/constants.js +45 -0
- package/lib/module/constants.js.map +1 -0
- package/lib/module/index.js +5 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/types.js +2 -0
- package/lib/module/types.js.map +1 -0
- package/lib/module/usePooledWebView.js +48 -0
- package/lib/module/usePooledWebView.js.map +1 -0
- package/lib/typescript/PooledWebView.d.ts +5 -0
- package/lib/typescript/PooledWebView.d.ts.map +1 -0
- package/lib/typescript/WebViewManager.d.ts +21 -0
- package/lib/typescript/WebViewManager.d.ts.map +1 -0
- package/lib/typescript/WebViewPoolProvider.d.ts +6 -0
- package/lib/typescript/WebViewPoolProvider.d.ts.map +1 -0
- package/lib/typescript/WebViewSlot.d.ts +13 -0
- package/lib/typescript/WebViewSlot.d.ts.map +1 -0
- package/lib/typescript/__mocks__/react-native-webview.d.ts +12 -0
- package/lib/typescript/__mocks__/react-native-webview.d.ts.map +1 -0
- package/lib/typescript/__mocks__/react-native.d.ts +18 -0
- package/lib/typescript/__mocks__/react-native.d.ts.map +1 -0
- package/lib/typescript/constants.d.ts +9 -0
- package/lib/typescript/constants.d.ts.map +1 -0
- package/lib/typescript/index.d.ts +6 -0
- package/lib/typescript/index.d.ts.map +1 -0
- package/lib/typescript/types.d.ts +62 -0
- package/lib/typescript/types.d.ts.map +1 -0
- package/lib/typescript/usePooledWebView.d.ts +3 -0
- package/lib/typescript/usePooledWebView.d.ts.map +1 -0
- package/package.json +87 -0
- package/src/PooledWebView.tsx +105 -0
- package/src/WebViewManager.ts +120 -0
- package/src/WebViewPoolProvider.tsx +138 -0
- package/src/WebViewSlot.tsx +107 -0
- package/src/__mocks__/react-native-webview.tsx +16 -0
- package/src/__mocks__/react-native.ts +14 -0
- package/src/constants.ts +46 -0
- package/src/index.tsx +17 -0
- package/src/types.ts +72 -0
- package/src/usePooledWebView.ts +58 -0
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
2
|
+
import { useWebViewPool } from './WebViewPoolProvider';
|
|
3
|
+
let hookBorrowerIdCounter = 0;
|
|
4
|
+
export function usePooledWebView() {
|
|
5
|
+
const pool = useWebViewPool();
|
|
6
|
+
const instanceIdRef = useRef(null);
|
|
7
|
+
const webViewRefRef = useRef(null);
|
|
8
|
+
const [instanceId, setInstanceId] = useState(null);
|
|
9
|
+
useEffect(() => {
|
|
10
|
+
return () => {
|
|
11
|
+
if (instanceIdRef.current) {
|
|
12
|
+
pool.release(instanceIdRef.current);
|
|
13
|
+
instanceIdRef.current = null;
|
|
14
|
+
webViewRefRef.current = null;
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
}, [pool]);
|
|
18
|
+
const borrow = useCallback(() => {
|
|
19
|
+
if (instanceIdRef.current) {
|
|
20
|
+
return {
|
|
21
|
+
instanceId: instanceIdRef.current,
|
|
22
|
+
webViewRef: webViewRefRef.current
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
const borrowerId = `hook-borrower-${++hookBorrowerIdCounter}`;
|
|
26
|
+
const result = pool.borrow(borrowerId);
|
|
27
|
+
if (!result) return null;
|
|
28
|
+
instanceIdRef.current = result.instanceId;
|
|
29
|
+
webViewRefRef.current = result.webViewRef;
|
|
30
|
+
setInstanceId(result.instanceId);
|
|
31
|
+
return result;
|
|
32
|
+
}, [pool]);
|
|
33
|
+
const release = useCallback(() => {
|
|
34
|
+
if (instanceIdRef.current) {
|
|
35
|
+
pool.release(instanceIdRef.current);
|
|
36
|
+
instanceIdRef.current = null;
|
|
37
|
+
webViewRefRef.current = null;
|
|
38
|
+
setInstanceId(null);
|
|
39
|
+
}
|
|
40
|
+
}, [pool]);
|
|
41
|
+
return {
|
|
42
|
+
borrow,
|
|
43
|
+
release,
|
|
44
|
+
instanceId,
|
|
45
|
+
webViewRef: webViewRefRef.current
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
//# sourceMappingURL=usePooledWebView.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":["useCallback","useEffect","useRef","useState","useWebViewPool","hookBorrowerIdCounter","usePooledWebView","pool","instanceIdRef","webViewRefRef","instanceId","setInstanceId","current","release","borrow","webViewRef","borrowerId","result"],"sourceRoot":"../../src","sources":["usePooledWebView.ts"],"mappings":"AAAA,SAASA,WAAW,EAAEC,SAAS,EAAEC,MAAM,EAAEC,QAAQ,QAAQ,OAAO;AAGhE,SAASC,cAAc,QAAQ,uBAAuB;AAGtD,IAAIC,qBAAqB,GAAG,CAAC;AAE7B,OAAO,SAASC,gBAAgBA,CAAA,EAA2B;EACzD,MAAMC,IAAI,GAAGH,cAAc,CAAC,CAAC;EAC7B,MAAMI,aAAa,GAAGN,MAAM,CAAgB,IAAI,CAAC;EACjD,MAAMO,aAAa,GAAGP,MAAM,CAAmC,IAAI,CAAC;EACpE,MAAM,CAACQ,UAAU,EAAEC,aAAa,CAAC,GAAGR,QAAQ,CAAgB,IAAI,CAAC;EAEjEF,SAAS,CAAC,MAAM;IACd,OAAO,MAAM;MACX,IAAIO,aAAa,CAACI,OAAO,EAAE;QACzBL,IAAI,CAACM,OAAO,CAACL,aAAa,CAACI,OAAO,CAAC;QACnCJ,aAAa,CAACI,OAAO,GAAG,IAAI;QAC5BH,aAAa,CAACG,OAAO,GAAG,IAAI;MAC9B;IACF,CAAC;EACH,CAAC,EAAE,CAACL,IAAI,CAAC,CAAC;EAEV,MAAMO,MAAM,GAAGd,WAAW,CAAC,MAAM;IAC/B,IAAIQ,aAAa,CAACI,OAAO,EAAE;MACzB,OAAO;QACLF,UAAU,EAAEF,aAAa,CAACI,OAAO;QACjCG,UAAU,EAAEN,aAAa,CAACG;MAC5B,CAAC;IACH;IAEA,MAAMI,UAAU,GAAG,iBAAiB,EAAEX,qBAAqB,EAAE;IAC7D,MAAMY,MAAM,GAAGV,IAAI,CAACO,MAAM,CAACE,UAAU,CAAC;IACtC,IAAI,CAACC,MAAM,EAAE,OAAO,IAAI;IAExBT,aAAa,CAACI,OAAO,GAAGK,MAAM,CAACP,UAAU;IACzCD,aAAa,CAACG,OAAO,GAAGK,MAAM,CAACF,UAAU;IACzCJ,aAAa,CAACM,MAAM,CAACP,UAAU,CAAC;IAChC,OAAOO,MAAM;EACf,CAAC,EAAE,CAACV,IAAI,CAAC,CAAC;EAEV,MAAMM,OAAO,GAAGb,WAAW,CAAC,MAAM;IAChC,IAAIQ,aAAa,CAACI,OAAO,EAAE;MACzBL,IAAI,CAACM,OAAO,CAACL,aAAa,CAACI,OAAO,CAAC;MACnCJ,aAAa,CAACI,OAAO,GAAG,IAAI;MAC5BH,aAAa,CAACG,OAAO,GAAG,IAAI;MAC5BD,aAAa,CAAC,IAAI,CAAC;IACrB;EACF,CAAC,EAAE,CAACJ,IAAI,CAAC,CAAC;EAEV,OAAO;IACLO,MAAM;IACND,OAAO;IACPH,UAAU;IACVK,UAAU,EAAEN,aAAa,CAACG;EAC5B,CAAC;AACH","ignoreList":[]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PooledWebView.d.ts","sourceRoot":"","sources":["../../src/PooledWebView.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAmD,MAAM,OAAO,CAAC;AAIxE,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,SAAS,CAAC;;AAoGlD,wBAAyC"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { PoolConfig, PoolState, PoolListener, BorrowResult } from './types';
|
|
2
|
+
declare class WebViewManager {
|
|
3
|
+
private static instance;
|
|
4
|
+
private config;
|
|
5
|
+
private instances;
|
|
6
|
+
private listeners;
|
|
7
|
+
private initialized;
|
|
8
|
+
private constructor();
|
|
9
|
+
static getInstance(): WebViewManager;
|
|
10
|
+
static resetInstance(): void;
|
|
11
|
+
initialize(config?: Partial<PoolConfig>): void;
|
|
12
|
+
borrow(borrowerId: string): BorrowResult | null;
|
|
13
|
+
release(instanceId: string): void;
|
|
14
|
+
markIdle(instanceId: string): void;
|
|
15
|
+
getState(): PoolState;
|
|
16
|
+
getConfig(): PoolConfig;
|
|
17
|
+
subscribe(listener: PoolListener): () => void;
|
|
18
|
+
private notify;
|
|
19
|
+
}
|
|
20
|
+
export default WebViewManager;
|
|
21
|
+
//# sourceMappingURL=WebViewManager.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"WebViewManager.d.ts","sourceRoot":"","sources":["../../src/WebViewManager.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EACV,UAAU,EACV,SAAS,EACT,YAAY,EAEZ,YAAY,EACb,MAAM,SAAS,CAAC;AAEjB,cAAM,cAAc;IAClB,OAAO,CAAC,MAAM,CAAC,QAAQ,CAA+B;IAEtD,OAAO,CAAC,MAAM,CAAmC;IACjD,OAAO,CAAC,SAAS,CAAyB;IAC1C,OAAO,CAAC,SAAS,CAAgC;IACjD,OAAO,CAAC,WAAW,CAAS;IAE5B,OAAO;IAEP,MAAM,CAAC,WAAW,IAAI,cAAc;IAOpC,MAAM,CAAC,aAAa,IAAI,IAAI;IAI5B,UAAU,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC,UAAU,CAAC,GAAG,IAAI;IAqB9C,MAAM,CAAC,UAAU,EAAE,MAAM,GAAG,YAAY,GAAG,IAAI;IAe/C,OAAO,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI;IAcjC,QAAQ,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI;IAUlC,QAAQ,IAAI,SAAS;IAUrB,SAAS,IAAI,UAAU;IAIvB,SAAS,CAAC,QAAQ,EAAE,YAAY,GAAG,MAAM,IAAI;IAO7C,OAAO,CAAC,MAAM;CAIf;AAED,eAAe,cAAc,CAAC"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import type { WebViewPoolContextValue, WebViewPoolProviderProps } from './types';
|
|
3
|
+
export declare function useWebViewPool(): WebViewPoolContextValue;
|
|
4
|
+
export declare const WebViewPoolProvider: React.FC<WebViewPoolProviderProps>;
|
|
5
|
+
export default WebViewPoolProvider;
|
|
6
|
+
//# sourceMappingURL=WebViewPoolProvider.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"WebViewPoolProvider.d.ts","sourceRoot":"","sources":["../../src/WebViewPoolProvider.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA8E,MAAM,OAAO,CAAC;AAMnG,OAAO,KAAK,EAKV,uBAAuB,EACvB,wBAAwB,EACzB,MAAM,SAAS,CAAC;AAIjB,wBAAgB,cAAc,IAAI,uBAAuB,CAMxD;AAED,eAAO,MAAM,mBAAmB,EAAE,KAAK,CAAC,EAAE,CAAC,wBAAwB,CAwGlE,CAAC;AAQF,eAAe,mBAAmB,CAAC"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import type { InstanceLayout, PoolConfig, WebViewInstance } from './types';
|
|
3
|
+
import type { WebViewProps } from 'react-native-webview';
|
|
4
|
+
interface WebViewSlotProps {
|
|
5
|
+
instance: WebViewInstance;
|
|
6
|
+
layout: InstanceLayout | null;
|
|
7
|
+
instanceProps: Partial<WebViewProps> | undefined;
|
|
8
|
+
config: PoolConfig;
|
|
9
|
+
onCleanupComplete: (instanceId: string) => void;
|
|
10
|
+
}
|
|
11
|
+
declare const _default: React.NamedExoticComponent<WebViewSlotProps>;
|
|
12
|
+
export default _default;
|
|
13
|
+
//# sourceMappingURL=WebViewSlot.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"WebViewSlot.d.ts","sourceRoot":"","sources":["../../src/WebViewSlot.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA4D,MAAM,OAAO,CAAC;AAIjF,OAAO,KAAK,EAAE,cAAc,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAC3E,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAEzD,UAAU,gBAAgB;IACxB,QAAQ,EAAE,eAAe,CAAC;IAC1B,MAAM,EAAE,cAAc,GAAG,IAAI,CAAC;IAC9B,aAAa,EAAE,OAAO,CAAC,YAAY,CAAC,GAAG,SAAS,CAAC;IACjD,MAAM,EAAE,UAAU,CAAC;IACnB,iBAAiB,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,IAAI,CAAC;CACjD;;AA6FD,wBAAuC"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
declare const WebView: React.ForwardRefExoticComponent<Omit<any, "ref"> & React.RefAttributes<unknown>>;
|
|
3
|
+
export { WebView };
|
|
4
|
+
export type WebViewProps = Record<string, any>;
|
|
5
|
+
export type WebViewNavigation = {
|
|
6
|
+
url: string;
|
|
7
|
+
title: string;
|
|
8
|
+
loading: boolean;
|
|
9
|
+
canGoBack: boolean;
|
|
10
|
+
canGoForward: boolean;
|
|
11
|
+
};
|
|
12
|
+
//# sourceMappingURL=react-native-webview.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"react-native-webview.d.ts","sourceRoot":"","sources":["../../../src/__mocks__/react-native-webview.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,QAAA,MAAM,OAAO,kFAEX,CAAC;AAGH,OAAO,EAAE,OAAO,EAAE,CAAC;AACnB,MAAM,MAAM,YAAY,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;AAC/C,MAAM,MAAM,iBAAiB,GAAG;IAC9B,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,OAAO,CAAC;IACjB,SAAS,EAAE,OAAO,CAAC;IACnB,YAAY,EAAE,OAAO,CAAC;CACvB,CAAC"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
declare const View: React.ForwardRefExoticComponent<Omit<any, "ref"> & React.RefAttributes<unknown>>;
|
|
3
|
+
declare const StyleSheet: {
|
|
4
|
+
create: (styles: any) => any;
|
|
5
|
+
};
|
|
6
|
+
export { View, StyleSheet };
|
|
7
|
+
export type ViewStyle = Record<string, any>;
|
|
8
|
+
export type LayoutChangeEvent = {
|
|
9
|
+
nativeEvent: {
|
|
10
|
+
layout: {
|
|
11
|
+
x: number;
|
|
12
|
+
y: number;
|
|
13
|
+
width: number;
|
|
14
|
+
height: number;
|
|
15
|
+
};
|
|
16
|
+
};
|
|
17
|
+
};
|
|
18
|
+
//# sourceMappingURL=react-native.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"react-native.d.ts","sourceRoot":"","sources":["../../../src/__mocks__/react-native.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,QAAA,MAAM,IAAI,kFAER,CAAC;AAGH,QAAA,MAAM,UAAU;qBACG,GAAG;CACrB,CAAC;AAEF,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,CAAC;AAC5B,MAAM,MAAM,SAAS,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;AAC5C,MAAM,MAAM,iBAAiB,GAAG;IAAE,WAAW,EAAE;QAAE,MAAM,EAAE;YAAE,CAAC,EAAE,MAAM,CAAC;YAAC,CAAC,EAAE,MAAM,CAAC;YAAC,KAAK,EAAE,MAAM,CAAC;YAAC,MAAM,EAAE,MAAM,CAAA;SAAE,CAAA;KAAE,CAAA;CAAE,CAAC"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { ViewStyle } from 'react-native';
|
|
2
|
+
import type { PoolConfig } from './types';
|
|
3
|
+
export declare const DEFAULT_POOL_CONFIG: PoolConfig;
|
|
4
|
+
export declare const HIDDEN_STYLE: ViewStyle;
|
|
5
|
+
export declare const BLANK_HTML_SOURCE: {
|
|
6
|
+
readonly html: "";
|
|
7
|
+
};
|
|
8
|
+
export declare const CLEANUP_SCRIPT = "\n(function() {\n try {\n window.scrollTo(0, 0);\n var highestTimeoutId = setTimeout(function(){}, 0);\n for (var i = 0; i < highestTimeoutId; i++) {\n clearTimeout(i);\n }\n var highestIntervalId = setInterval(function(){}, 0);\n for (var j = 0; j < highestIntervalId; j++) {\n clearInterval(j);\n }\n if (document.body) {\n document.body.innerHTML = '';\n }\n } catch(e) {}\n true;\n})();\n";
|
|
9
|
+
//# sourceMappingURL=constants.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../../src/constants.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAE1C,eAAO,MAAM,mBAAmB,EAAE,UAGjC,CAAC;AAEF,eAAO,MAAM,YAAY,EAAE,SAO1B,CAAC;AAMF,eAAO,MAAM,iBAAiB;;CAAwB,CAAC;AAMvD,eAAO,MAAM,cAAc,0bAkB1B,CAAC"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { WebViewPoolProvider, useWebViewPool } from './WebViewPoolProvider';
|
|
2
|
+
export { default as PooledWebView } from './PooledWebView';
|
|
3
|
+
export { usePooledWebView } from './usePooledWebView';
|
|
4
|
+
export { default as WebViewManager } from './WebViewManager';
|
|
5
|
+
export type { PoolConfig, InstanceStatus, WebViewInstance, PoolState, BorrowResult, WebViewPoolContextValue, InstanceLayout, PooledWebViewProps, WebViewPoolProviderProps, UsePooledWebViewReturn, } from './types';
|
|
6
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAC5E,OAAO,EAAE,OAAO,IAAI,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAC3D,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,EAAE,OAAO,IAAI,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAE7D,YAAY,EACV,UAAU,EACV,cAAc,EACd,eAAe,EACf,SAAS,EACT,YAAY,EACZ,uBAAuB,EACvB,cAAc,EACd,kBAAkB,EAClB,wBAAwB,EACxB,sBAAsB,GACvB,MAAM,SAAS,CAAC"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import type { RefObject } from 'react';
|
|
2
|
+
import type { StyleProp, ViewStyle } from 'react-native';
|
|
3
|
+
import type { WebView, WebViewProps } from 'react-native-webview';
|
|
4
|
+
export interface PoolConfig {
|
|
5
|
+
poolSize: number;
|
|
6
|
+
cleanupOnReturn: boolean;
|
|
7
|
+
customCleanupScript?: string;
|
|
8
|
+
defaultWebViewProps?: Partial<WebViewProps>;
|
|
9
|
+
}
|
|
10
|
+
export type InstanceStatus = 'idle' | 'borrowed' | 'cleaning';
|
|
11
|
+
export interface WebViewInstance {
|
|
12
|
+
id: string;
|
|
13
|
+
status: InstanceStatus;
|
|
14
|
+
webViewRef: RefObject<WebView | null>;
|
|
15
|
+
borrowerId: string | null;
|
|
16
|
+
createdAt: number;
|
|
17
|
+
borrowedAt: number | null;
|
|
18
|
+
}
|
|
19
|
+
export interface PoolState {
|
|
20
|
+
instances: WebViewInstance[];
|
|
21
|
+
availableCount: number;
|
|
22
|
+
borrowedCount: number;
|
|
23
|
+
initialized: boolean;
|
|
24
|
+
}
|
|
25
|
+
export interface BorrowResult {
|
|
26
|
+
instanceId: string;
|
|
27
|
+
webViewRef: RefObject<WebView | null>;
|
|
28
|
+
}
|
|
29
|
+
export interface InstanceLayout {
|
|
30
|
+
top: number;
|
|
31
|
+
left: number;
|
|
32
|
+
width: number;
|
|
33
|
+
height: number;
|
|
34
|
+
}
|
|
35
|
+
export interface WebViewPoolContextValue {
|
|
36
|
+
state: PoolState;
|
|
37
|
+
borrow: (borrowerId: string) => BorrowResult | null;
|
|
38
|
+
release: (instanceId: string) => void;
|
|
39
|
+
setInstanceLayout: (instanceId: string, layout: InstanceLayout | null) => void;
|
|
40
|
+
setInstanceProps: (instanceId: string, props: Partial<WebViewProps>) => void;
|
|
41
|
+
getInstanceLayout: (instanceId: string) => InstanceLayout | null;
|
|
42
|
+
getInstanceProps: (instanceId: string) => Partial<WebViewProps> | undefined;
|
|
43
|
+
}
|
|
44
|
+
export interface PooledWebViewProps extends Omit<WebViewProps, 'ref'> {
|
|
45
|
+
poolKey?: string;
|
|
46
|
+
containerStyle?: StyleProp<ViewStyle>;
|
|
47
|
+
onPoolExhausted?: () => void;
|
|
48
|
+
onBorrowed?: (instanceId: string) => void;
|
|
49
|
+
onReturned?: (instanceId: string) => void;
|
|
50
|
+
}
|
|
51
|
+
export interface WebViewPoolProviderProps {
|
|
52
|
+
config?: Partial<PoolConfig>;
|
|
53
|
+
children: React.ReactNode;
|
|
54
|
+
}
|
|
55
|
+
export interface UsePooledWebViewReturn {
|
|
56
|
+
borrow: () => BorrowResult | null;
|
|
57
|
+
release: () => void;
|
|
58
|
+
instanceId: string | null;
|
|
59
|
+
webViewRef: RefObject<WebView | null> | null;
|
|
60
|
+
}
|
|
61
|
+
export type PoolListener = (state: PoolState) => void;
|
|
62
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/types.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"usePooledWebView.d.ts","sourceRoot":"","sources":["../../src/usePooledWebView.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,SAAS,CAAC;AAItD,wBAAgB,gBAAgB,IAAI,sBAAsB,CAiDzD"}
|
package/package.json
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "react-native-instant-webview",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Zero-loading WebView pooling for React Native",
|
|
5
|
+
"main": "lib/commonjs/index",
|
|
6
|
+
"module": "lib/module/index",
|
|
7
|
+
"types": "lib/typescript/index.d.ts",
|
|
8
|
+
"react-native": "src/index",
|
|
9
|
+
"source": "src/index",
|
|
10
|
+
"files": [
|
|
11
|
+
"src",
|
|
12
|
+
"lib",
|
|
13
|
+
"!**/__tests__"
|
|
14
|
+
],
|
|
15
|
+
"scripts": {
|
|
16
|
+
"typecheck": "tsc --noEmit",
|
|
17
|
+
"build": "bob build",
|
|
18
|
+
"test": "jest --forceExit",
|
|
19
|
+
"prepare": "bob build"
|
|
20
|
+
},
|
|
21
|
+
"keywords": [
|
|
22
|
+
"react-native",
|
|
23
|
+
"webview",
|
|
24
|
+
"pool",
|
|
25
|
+
"instant",
|
|
26
|
+
"preload"
|
|
27
|
+
],
|
|
28
|
+
"repository": {
|
|
29
|
+
"type": "git",
|
|
30
|
+
"url": "https://github.com/wooBottle/react-native-instant-webview"
|
|
31
|
+
},
|
|
32
|
+
"license": "MIT",
|
|
33
|
+
"peerDependencies": {
|
|
34
|
+
"react": ">=18.0.0",
|
|
35
|
+
"react-native": ">=0.70.0",
|
|
36
|
+
"react-native-webview": ">=13.0.0"
|
|
37
|
+
},
|
|
38
|
+
"devDependencies": {
|
|
39
|
+
"@testing-library/jest-native": "^5.4.0",
|
|
40
|
+
"@testing-library/react-native": "^12.4.0",
|
|
41
|
+
"@types/jest": "^29.5.0",
|
|
42
|
+
"@types/react": "^18.2.0",
|
|
43
|
+
"@types/react-native": "^0.72.0",
|
|
44
|
+
"@types/react-test-renderer": "^19.1.0",
|
|
45
|
+
"jest": "^29.7.0",
|
|
46
|
+
"react": "^18.2.0",
|
|
47
|
+
"react-native": "^0.73.0",
|
|
48
|
+
"react-native-builder-bob": "^0.23.0",
|
|
49
|
+
"react-native-webview": "^13.6.0",
|
|
50
|
+
"react-test-renderer": "^18.2.0",
|
|
51
|
+
"ts-jest": "^29.1.0",
|
|
52
|
+
"typescript": "^5.3.0"
|
|
53
|
+
},
|
|
54
|
+
"react-native-builder-bob": {
|
|
55
|
+
"source": "src",
|
|
56
|
+
"output": "lib",
|
|
57
|
+
"targets": [
|
|
58
|
+
"commonjs",
|
|
59
|
+
"module",
|
|
60
|
+
[
|
|
61
|
+
"typescript",
|
|
62
|
+
{
|
|
63
|
+
"project": "tsconfig.build.json"
|
|
64
|
+
}
|
|
65
|
+
]
|
|
66
|
+
]
|
|
67
|
+
},
|
|
68
|
+
"jest": {
|
|
69
|
+
"preset": "ts-jest",
|
|
70
|
+
"testEnvironment": "node",
|
|
71
|
+
"moduleFileExtensions": [
|
|
72
|
+
"ts",
|
|
73
|
+
"tsx",
|
|
74
|
+
"js",
|
|
75
|
+
"jsx",
|
|
76
|
+
"json"
|
|
77
|
+
],
|
|
78
|
+
"testPathIgnorePatterns": [
|
|
79
|
+
"/node_modules/",
|
|
80
|
+
"/lib/"
|
|
81
|
+
],
|
|
82
|
+
"moduleNameMapper": {
|
|
83
|
+
"^react-native$": "<rootDir>/src/__mocks__/react-native.ts",
|
|
84
|
+
"^react-native-webview$": "<rootDir>/src/__mocks__/react-native-webview.tsx"
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
|
2
|
+
import { View, type LayoutChangeEvent } from 'react-native';
|
|
3
|
+
import { WebView } from 'react-native-webview';
|
|
4
|
+
import type { WebViewProps } from 'react-native-webview';
|
|
5
|
+
import type { PooledWebViewProps } from './types';
|
|
6
|
+
import { useWebViewPool } from './WebViewPoolProvider';
|
|
7
|
+
|
|
8
|
+
let borrowerIdCounter = 0;
|
|
9
|
+
|
|
10
|
+
const PooledWebView: React.FC<PooledWebViewProps> = ({
|
|
11
|
+
poolKey,
|
|
12
|
+
containerStyle,
|
|
13
|
+
onPoolExhausted,
|
|
14
|
+
onBorrowed,
|
|
15
|
+
onReturned,
|
|
16
|
+
source,
|
|
17
|
+
...webViewProps
|
|
18
|
+
}) => {
|
|
19
|
+
const pool = useWebViewPool();
|
|
20
|
+
const instanceIdRef = useRef<string | null>(null);
|
|
21
|
+
const placeholderRef = useRef<View>(null);
|
|
22
|
+
const borrowerIdRef = useRef(poolKey ?? `borrower-${++borrowerIdCounter}`);
|
|
23
|
+
const propsRef = useRef<Partial<WebViewProps>>({ source, ...webViewProps });
|
|
24
|
+
|
|
25
|
+
const [borrowed, setBorrowed] = useState(false);
|
|
26
|
+
const [fallback, setFallback] = useState(false);
|
|
27
|
+
|
|
28
|
+
// Keep propsRef in sync without triggering re-renders
|
|
29
|
+
propsRef.current = { source, ...webViewProps };
|
|
30
|
+
|
|
31
|
+
// Borrow on mount
|
|
32
|
+
useEffect(() => {
|
|
33
|
+
const result = pool.borrow(borrowerIdRef.current);
|
|
34
|
+
if (!result) {
|
|
35
|
+
setFallback(true);
|
|
36
|
+
onPoolExhausted?.();
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
instanceIdRef.current = result.instanceId;
|
|
41
|
+
pool.setInstanceProps(result.instanceId, propsRef.current);
|
|
42
|
+
setBorrowed(true);
|
|
43
|
+
onBorrowed?.(result.instanceId);
|
|
44
|
+
|
|
45
|
+
return () => {
|
|
46
|
+
const id = instanceIdRef.current;
|
|
47
|
+
if (id) {
|
|
48
|
+
pool.setInstanceLayout(id, null);
|
|
49
|
+
pool.release(id);
|
|
50
|
+
onReturned?.(id);
|
|
51
|
+
instanceIdRef.current = null;
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
55
|
+
}, []);
|
|
56
|
+
|
|
57
|
+
// Update props when source changes (the primary prop that changes)
|
|
58
|
+
useEffect(() => {
|
|
59
|
+
const id = instanceIdRef.current;
|
|
60
|
+
if (!id || !borrowed) return;
|
|
61
|
+
|
|
62
|
+
pool.setInstanceProps(id, propsRef.current);
|
|
63
|
+
// Only re-sync when source actually changes
|
|
64
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
65
|
+
}, [source]);
|
|
66
|
+
|
|
67
|
+
// Measure placeholder position
|
|
68
|
+
const handleLayout = useCallback(
|
|
69
|
+
(_event: LayoutChangeEvent) => {
|
|
70
|
+
const id = instanceIdRef.current;
|
|
71
|
+
if (!id || !placeholderRef.current) return;
|
|
72
|
+
|
|
73
|
+
placeholderRef.current.measureInWindow((x, y, width, height) => {
|
|
74
|
+
if (width > 0 && height > 0) {
|
|
75
|
+
pool.setInstanceLayout(id, {
|
|
76
|
+
top: y,
|
|
77
|
+
left: x,
|
|
78
|
+
width,
|
|
79
|
+
height,
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
},
|
|
84
|
+
[pool],
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
// Pool exhausted — fall back to a regular WebView
|
|
88
|
+
if (fallback) {
|
|
89
|
+
return (
|
|
90
|
+
<View style={[{ flex: 1 }, containerStyle]}>
|
|
91
|
+
<WebView source={source} {...webViewProps} style={{ flex: 1 }} />
|
|
92
|
+
</View>
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return (
|
|
97
|
+
<View
|
|
98
|
+
ref={placeholderRef}
|
|
99
|
+
style={[{ flex: 1 }, containerStyle]}
|
|
100
|
+
onLayout={handleLayout}
|
|
101
|
+
/>
|
|
102
|
+
);
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
export default React.memo(PooledWebView);
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { createRef } from 'react';
|
|
2
|
+
import type { WebView } from 'react-native-webview';
|
|
3
|
+
import { DEFAULT_POOL_CONFIG } from './constants';
|
|
4
|
+
import type {
|
|
5
|
+
PoolConfig,
|
|
6
|
+
PoolState,
|
|
7
|
+
PoolListener,
|
|
8
|
+
WebViewInstance,
|
|
9
|
+
BorrowResult,
|
|
10
|
+
} from './types';
|
|
11
|
+
|
|
12
|
+
class WebViewManager {
|
|
13
|
+
private static instance: WebViewManager | null = null;
|
|
14
|
+
|
|
15
|
+
private config: PoolConfig = DEFAULT_POOL_CONFIG;
|
|
16
|
+
private instances: WebViewInstance[] = [];
|
|
17
|
+
private listeners: Set<PoolListener> = new Set();
|
|
18
|
+
private initialized = false;
|
|
19
|
+
|
|
20
|
+
private constructor() {}
|
|
21
|
+
|
|
22
|
+
static getInstance(): WebViewManager {
|
|
23
|
+
if (!WebViewManager.instance) {
|
|
24
|
+
WebViewManager.instance = new WebViewManager();
|
|
25
|
+
}
|
|
26
|
+
return WebViewManager.instance;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
static resetInstance(): void {
|
|
30
|
+
WebViewManager.instance = null;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
initialize(config?: Partial<PoolConfig>): void {
|
|
34
|
+
if (this.initialized) return;
|
|
35
|
+
|
|
36
|
+
this.config = { ...DEFAULT_POOL_CONFIG, ...config };
|
|
37
|
+
this.instances = [];
|
|
38
|
+
|
|
39
|
+
for (let i = 0; i < this.config.poolSize; i++) {
|
|
40
|
+
this.instances.push({
|
|
41
|
+
id: `webview-pool-${i}`,
|
|
42
|
+
status: 'idle',
|
|
43
|
+
webViewRef: createRef<WebView | null>(),
|
|
44
|
+
borrowerId: null,
|
|
45
|
+
createdAt: Date.now(),
|
|
46
|
+
borrowedAt: null,
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
this.initialized = true;
|
|
51
|
+
this.notify();
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
borrow(borrowerId: string): BorrowResult | null {
|
|
55
|
+
const idle = this.instances.find((inst) => inst.status === 'idle');
|
|
56
|
+
if (!idle) return null;
|
|
57
|
+
|
|
58
|
+
idle.status = 'borrowed';
|
|
59
|
+
idle.borrowerId = borrowerId;
|
|
60
|
+
idle.borrowedAt = Date.now();
|
|
61
|
+
this.notify();
|
|
62
|
+
|
|
63
|
+
return {
|
|
64
|
+
instanceId: idle.id,
|
|
65
|
+
webViewRef: idle.webViewRef,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
release(instanceId: string): void {
|
|
70
|
+
const inst = this.instances.find((i) => i.id === instanceId);
|
|
71
|
+
if (!inst || inst.status !== 'borrowed') return;
|
|
72
|
+
|
|
73
|
+
if (this.config.cleanupOnReturn) {
|
|
74
|
+
inst.status = 'cleaning';
|
|
75
|
+
} else {
|
|
76
|
+
inst.status = 'idle';
|
|
77
|
+
inst.borrowerId = null;
|
|
78
|
+
inst.borrowedAt = null;
|
|
79
|
+
}
|
|
80
|
+
this.notify();
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
markIdle(instanceId: string): void {
|
|
84
|
+
const inst = this.instances.find((i) => i.id === instanceId);
|
|
85
|
+
if (!inst) return;
|
|
86
|
+
|
|
87
|
+
inst.status = 'idle';
|
|
88
|
+
inst.borrowerId = null;
|
|
89
|
+
inst.borrowedAt = null;
|
|
90
|
+
this.notify();
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
getState(): PoolState {
|
|
94
|
+
return {
|
|
95
|
+
instances: [...this.instances],
|
|
96
|
+
availableCount: this.instances.filter((i) => i.status === 'idle').length,
|
|
97
|
+
borrowedCount: this.instances.filter((i) => i.status === 'borrowed')
|
|
98
|
+
.length,
|
|
99
|
+
initialized: this.initialized,
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
getConfig(): PoolConfig {
|
|
104
|
+
return { ...this.config };
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
subscribe(listener: PoolListener): () => void {
|
|
108
|
+
this.listeners.add(listener);
|
|
109
|
+
return () => {
|
|
110
|
+
this.listeners.delete(listener);
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
private notify(): void {
|
|
115
|
+
const state = this.getState();
|
|
116
|
+
this.listeners.forEach((listener) => listener(state));
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export default WebViewManager;
|