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.
Files changed (64) hide show
  1. package/README.md +170 -0
  2. package/lib/commonjs/PooledWebView.js +112 -0
  3. package/lib/commonjs/PooledWebView.js.map +1 -0
  4. package/lib/commonjs/WebViewManager.js +102 -0
  5. package/lib/commonjs/WebViewManager.js.map +1 -0
  6. package/lib/commonjs/WebViewPoolProvider.js +102 -0
  7. package/lib/commonjs/WebViewPoolProvider.js.map +1 -0
  8. package/lib/commonjs/WebViewSlot.js +91 -0
  9. package/lib/commonjs/WebViewSlot.js.map +1 -0
  10. package/lib/commonjs/constants.js +51 -0
  11. package/lib/commonjs/constants.js.map +1 -0
  12. package/lib/commonjs/index.js +41 -0
  13. package/lib/commonjs/index.js.map +1 -0
  14. package/lib/commonjs/types.js +6 -0
  15. package/lib/commonjs/types.js.map +1 -0
  16. package/lib/commonjs/usePooledWebView.js +54 -0
  17. package/lib/commonjs/usePooledWebView.js.map +1 -0
  18. package/lib/module/PooledWebView.js +105 -0
  19. package/lib/module/PooledWebView.js.map +1 -0
  20. package/lib/module/WebViewManager.js +96 -0
  21. package/lib/module/WebViewManager.js.map +1 -0
  22. package/lib/module/WebViewPoolProvider.js +92 -0
  23. package/lib/module/WebViewPoolProvider.js.map +1 -0
  24. package/lib/module/WebViewSlot.js +84 -0
  25. package/lib/module/WebViewSlot.js.map +1 -0
  26. package/lib/module/constants.js +45 -0
  27. package/lib/module/constants.js.map +1 -0
  28. package/lib/module/index.js +5 -0
  29. package/lib/module/index.js.map +1 -0
  30. package/lib/module/types.js +2 -0
  31. package/lib/module/types.js.map +1 -0
  32. package/lib/module/usePooledWebView.js +48 -0
  33. package/lib/module/usePooledWebView.js.map +1 -0
  34. package/lib/typescript/PooledWebView.d.ts +5 -0
  35. package/lib/typescript/PooledWebView.d.ts.map +1 -0
  36. package/lib/typescript/WebViewManager.d.ts +21 -0
  37. package/lib/typescript/WebViewManager.d.ts.map +1 -0
  38. package/lib/typescript/WebViewPoolProvider.d.ts +6 -0
  39. package/lib/typescript/WebViewPoolProvider.d.ts.map +1 -0
  40. package/lib/typescript/WebViewSlot.d.ts +13 -0
  41. package/lib/typescript/WebViewSlot.d.ts.map +1 -0
  42. package/lib/typescript/__mocks__/react-native-webview.d.ts +12 -0
  43. package/lib/typescript/__mocks__/react-native-webview.d.ts.map +1 -0
  44. package/lib/typescript/__mocks__/react-native.d.ts +18 -0
  45. package/lib/typescript/__mocks__/react-native.d.ts.map +1 -0
  46. package/lib/typescript/constants.d.ts +9 -0
  47. package/lib/typescript/constants.d.ts.map +1 -0
  48. package/lib/typescript/index.d.ts +6 -0
  49. package/lib/typescript/index.d.ts.map +1 -0
  50. package/lib/typescript/types.d.ts +62 -0
  51. package/lib/typescript/types.d.ts.map +1 -0
  52. package/lib/typescript/usePooledWebView.d.ts +3 -0
  53. package/lib/typescript/usePooledWebView.d.ts.map +1 -0
  54. package/package.json +87 -0
  55. package/src/PooledWebView.tsx +105 -0
  56. package/src/WebViewManager.ts +120 -0
  57. package/src/WebViewPoolProvider.tsx +138 -0
  58. package/src/WebViewSlot.tsx +107 -0
  59. package/src/__mocks__/react-native-webview.tsx +16 -0
  60. package/src/__mocks__/react-native.ts +14 -0
  61. package/src/constants.ts +46 -0
  62. package/src/index.tsx +17 -0
  63. package/src/types.ts +72 -0
  64. 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,5 @@
1
+ import React from 'react';
2
+ import type { PooledWebViewProps } from './types';
3
+ declare const _default: React.NamedExoticComponent<PooledWebViewProps>;
4
+ export default _default;
5
+ //# sourceMappingURL=PooledWebView.d.ts.map
@@ -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,3 @@
1
+ import type { UsePooledWebViewReturn } from './types';
2
+ export declare function usePooledWebView(): UsePooledWebViewReturn;
3
+ //# sourceMappingURL=usePooledWebView.d.ts.map
@@ -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;