Skip to content

Commit ee9fe39

Browse files
authored
Merge pull request #4 from mCodex/refactor/bumpDependencies
Release 1.1.0: safer defaults & bridge protocol
2 parents 3eb54d5 + 96bf6ab commit ee9fe39

19 files changed

Lines changed: 3323 additions & 5386 deletions

README.md

Lines changed: 52 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,24 @@ React Native WebView that auto-sizes to match its HTML content—whether you loa
1212
> [!TIP]
1313
> 💡 Works out-of-the-box with dynamic CMS pages, FAQs, marketing landers, local HTML snippets, or full external sites.
1414
15+
## 🚨 Upgrading to 1.1.0
16+
17+
Three defaults changed in the 1.1.x line. Each is a one-line migration:
18+
19+
- **Named import only.** The default export was removed to keep tree-shaking predictable across bundlers.
20+
```diff
21+
- import SizedWebView from 'react-native-sized-webview';
22+
+ import { SizedWebView } from 'react-native-sized-webview';
23+
```
24+
- **`originWhitelist` now defaults to `['http://*', 'https://*']`.** Standard HTTP(S) navigation keeps working; non-web schemes (`file:`, `javascript:`, `data:`, `intent:`) are blocked by default. Tighten it for production if you only load a specific origin:
25+
```diff
26+
<SizedWebView
27+
+ originWhitelist={['https://your-trusted-domain.com']}
28+
source={{ uri: 'https://your-trusted-domain.com/page' }}
29+
/>
30+
```
31+
- **`javaScriptEnabled` is now respected.** Passing `false` disables auto-sizing; the container falls back to `minHeight` (or `containerStyle.height`). This unblocks rendering static HTML on iOS 26 ([#3](https://github.com/mCodex/react-native-sized-webview/issues/3)).
32+
1533
## ✨ Highlights
1634

1735
- 📐 Wrapper-based measurement keeps the WebView content in a dedicated container, so height always reflects the real DOM footprint.
@@ -80,14 +98,25 @@ The example showcases:
8098

8199
| Prop | Type | Default | Description |
82100
| --- | --- | --- | --- |
83-
| `minHeight` | `number` | `0` | Minimum height (dp) applied to the container to avoid layout jumps before content loads. |
84-
| `containerStyle` | `StyleProp<ViewStyle>` || Styles applied to the wrapping `View`. Use it for padding, borders, or shadows. |
85-
| `onHeightChange` | `(height: number) => void` || Callback fired whenever a new height is committed. Great for analytics or debugging. |
86-
| `...WebViewProps` ||| All remaining props are forwarded to the underlying `react-native-webview`. |
101+
| `minHeight` | `number` | `0` | Minimum height (dp) applied to the container. When `0`, the container is unsized until the first measurement arrives (avoids layout flicker and the iOS 26 WKWebView 1px feedback loop). |
102+
| `containerStyle` | `StyleProp<ViewStyle>` || Styles applied to the wrapping `View`. Use it for padding, borders, or shadows. Do not set `height` — it is managed by the hook. |
103+
| `onHeightChange` | `(height: number) => void` || Callback fired whenever a new height is committed. Great for analytics or debugging. Never fires for invalid or out-of-range values. |
104+
| `originWhitelist` | `string[]` | `['http://*', 'https://*']` | Origins the WebView is allowed to navigate to. Blocks non-web schemes (`file:`, `javascript:`, `data:`, `intent:`) by default. Tighten it to a specific origin list for stricter environments. |
105+
| `javaScriptEnabled` | `boolean` | `true` | When `false`, the auto-height bridge is **not** injected and the container falls back to `minHeight`. Use for static HTML that doesn't need JS. |
106+
| `...WebViewProps` ||| All remaining props are forwarded to the underlying `react-native-webview`. User-supplied values always win over the defaults above. |
87107

88108
> [!NOTE]
89109
> 🧩 `scrollEnabled` defaults to `false` so sizing remains deterministic. Only enable it if the WebView should manage its own scroll.
90110
111+
## 🛡️ Security
112+
113+
- **Namespaced message protocol.** The injected bridge posts values prefixed with `__RN_SIZED_WV__:` and the hook rejects everything else, so your own `onMessage` traffic cannot accidentally (or maliciously) mutate the container height.
114+
- **Safe-by-default origin list.** `originWhitelist` defaults to `['http://*', 'https://*']` — HTTP(S) navigation works, but non-web schemes (`file:`, `javascript:`, `data:`, `intent:`) are blocked. Tighten to a specific origin for production apps that only load trusted content.
115+
- **Respected JS toggle.** `javaScriptEnabled={false}` is honored; the bridge is not injected when you disable scripts.
116+
- **Clamped heights.** A shared `MAX_COMMITTED_HEIGHT` (120 000 dp) caps both sides of the bridge to defend against runaway values from broken markup or third-party scripts.
117+
- **No native code.** This package ships only JavaScript/TypeScript — there is no Objective-C, Swift, Java, or Kotlin to audit.
118+
- **Warning.** Never interpolate untrusted strings into `injectedJavaScript` or `injectedJavaScriptBeforeContentLoaded`. Anything passed there runs inside the WebView page context and can reach React Native through `window.ReactNativeWebView`.
119+
91120
## 🧩 Edge Cases Covered
92121

93122
- Trailing `<br>` and empty `<p>` tags are stripped automatically so CMS exports don’t leave phantom padding.
@@ -114,6 +143,25 @@ The example showcases:
114143

115144
Benchmarks were captured on CMS articles up to 3k words in a 60 fps RN dev build. The bridge batches DOM mutations so even long documents resize without thrashing the JS thread.
116145

146+
### 🏎️ Built for speed
147+
148+
Every hot path is designed to run at its theoretical complexity floor — no allocations in steady state, no repeated DOM walks, and at most one forced layout per measurement frame.
149+
150+
| Hot path | Complexity | Notes |
151+
| --- | --- | --- |
152+
| Message parsing (`useAutoHeight`) | **O(1)** | Namespaced-prefix check, single `Number()` coerce, constant-bound clamp. |
153+
| Height commit (rAF-batched) | **O(1)** amortized per frame | Sub-pixel diffs are dropped; at most one React render per animation frame. |
154+
| DOM mutation callback | **O(added nodes)** | Scans only each mutation's `addedNodes`, not the whole tree. Media elements are deduped via a `WeakSet`. |
155+
| `measureHeight` | **1 forced reflow / call** | Reads the wrapper element only — its box is authoritative because every `<body>` child lives inside it. |
156+
| Trailing-node prune DFS | Runs only when the DOM is **dirty** | A mutation-driven dirty flag skips the recursive walk on resize / font / viewport ticks when nothing structural changed. |
157+
158+
The net effect: resize storms, font loads, and viewport changes cost a single `getBoundingClientRect()` per frame — nothing more. Paired with `sideEffects: false` and named-only exports, the library stays fast *and* small in the final bundle.
159+
160+
### 📦 Bundle & tree-shaking
161+
162+
- Ships as ESM-first (`lib/module/**`) with `"sideEffects": false`.
163+
- **Named exports only** — no default export — so every bundler can drop what you don't use.
164+
- Importing only `useAutoHeight` or `composeInjectedScript` does **not** pull the injected-bridge string into your bundle.
117165
## ✅ Testing
118166

119167
```sh

biome.json

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
{
2+
"$schema": "https://biomejs.dev/schemas/2.4.13/schema.json",
3+
"vcs": {
4+
"enabled": true,
5+
"clientKind": "git",
6+
"useIgnoreFile": true
7+
},
8+
"files": {
9+
"includes": [
10+
"**/*.js",
11+
"**/*.jsx",
12+
"**/*.ts",
13+
"**/*.tsx",
14+
"**/*.mjs",
15+
"**/*.cjs",
16+
"**/*.json",
17+
"!**/node_modules",
18+
"!**/lib",
19+
"!**/coverage",
20+
"!**/.yarn",
21+
"!example/ios",
22+
"!example/android",
23+
"!example/.expo"
24+
]
25+
},
26+
"formatter": {
27+
"enabled": true,
28+
"indentStyle": "space",
29+
"indentWidth": 2,
30+
"lineWidth": 80,
31+
"lineEnding": "lf"
32+
},
33+
"javascript": {
34+
"formatter": {
35+
"quoteStyle": "single",
36+
"jsxQuoteStyle": "double",
37+
"trailingCommas": "es5",
38+
"semicolons": "always",
39+
"arrowParentheses": "always",
40+
"bracketSpacing": true,
41+
"quoteProperties": "asNeeded"
42+
}
43+
},
44+
"linter": {
45+
"enabled": true,
46+
"rules": {
47+
"recommended": true,
48+
"correctness": {
49+
"useExhaustiveDependencies": "warn",
50+
"useHookAtTopLevel": "error"
51+
},
52+
"style": {
53+
"useConst": "error",
54+
"useTemplate": "error",
55+
"noNonNullAssertion": "warn"
56+
},
57+
"suspicious": {
58+
"noExplicitAny": "warn"
59+
}
60+
}
61+
},
62+
"assist": {
63+
"enabled": true,
64+
"actions": {
65+
"source": {
66+
"organizeImports": "on"
67+
}
68+
}
69+
},
70+
"overrides": [
71+
{
72+
"includes": ["**/__tests__/**", "**/*.test.ts", "**/*.test.tsx"],
73+
"linter": {
74+
"rules": {
75+
"suspicious": {
76+
"noExplicitAny": "off"
77+
},
78+
"style": {
79+
"noNonNullAssertion": "off"
80+
}
81+
}
82+
}
83+
},
84+
{
85+
"includes": ["src/constants/autoHeightBridge.ts"],
86+
"linter": {
87+
"rules": {
88+
"style": {
89+
"useConst": "off",
90+
"useTemplate": "off"
91+
},
92+
"suspicious": {
93+
"noAssignInExpressions": "off"
94+
}
95+
}
96+
}
97+
}
98+
]
99+
}

eslint.config.mjs

Lines changed: 0 additions & 29 deletions
This file was deleted.

example/babel.config.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
const path = require('path');
1+
const path = require('node:path');
22
const { getConfig } = require('react-native-builder-bob/babel-config');
33
const pkg = require('../package.json');
44

55
const root = path.resolve(__dirname, '..');
66

7-
module.exports = function (api) {
7+
module.exports = (api) => {
88
api.cache(true);
99

1010
return getConfig(

example/metro.config.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
const path = require('path');
1+
const path = require('node:path');
22
const { getDefaultConfig } = require('@expo/metro-config');
33
const { withMetroConfig } = require('react-native-monorepo-config');
44

example/package.json

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,19 +9,19 @@
99
"web": "expo start --web"
1010
},
1111
"dependencies": {
12-
"@expo/metro-runtime": "~6.1.2",
13-
"expo": "~54.0.29",
14-
"expo-status-bar": "~3.0.9",
15-
"react": "19.1.0",
16-
"react-dom": "19.1.0",
17-
"react-native": "0.81.5",
18-
"react-native-safe-area-context": "^5.6.2",
12+
"@expo/metro-runtime": "~55.0.10",
13+
"expo": "~55.0.17",
14+
"expo-status-bar": "~55.0.5",
15+
"react": "19.2.0",
16+
"react-dom": "19.2.0",
17+
"react-native": "0.83.6",
18+
"react-native-safe-area-context": "^5.7.0",
1919
"react-native-web": "~0.21.2",
20-
"react-native-webview": "^13.16.0"
20+
"react-native-webview": "^13.16.1"
2121
},
2222
"private": true,
2323
"devDependencies": {
24-
"react-native-builder-bob": "^0.40.17",
25-
"react-native-monorepo-config": "^0.3.1"
24+
"react-native-builder-bob": "^0.41.0",
25+
"react-native-monorepo-config": "^0.3.3"
2626
}
2727
}

example/src/App.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@ import {
55
StyleSheet,
66
Switch,
77
Text,
8-
View,
98
useColorScheme,
9+
View,
1010
} from 'react-native';
1111
import { SafeAreaView } from 'react-native-safe-area-context';
1212
import { SizedWebView } from 'react-native-sized-webview';

lefthook.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@ pre-commit:
22
parallel: true
33
commands:
44
lint:
5-
glob: "*.{js,ts,jsx,tsx}"
6-
run: npx eslint {staged_files}
5+
glob: "*.{js,ts,jsx,tsx,mjs,cjs,json}"
6+
run: npx biome check --no-errors-on-unmatched {staged_files}
77
types:
8-
glob: "*.{js,ts, jsx, tsx}"
8+
glob: "*.{js,ts,jsx,tsx}"
99
run: npx tsc
1010
# commit-msg:
1111
# parallel: true

package.json

Lines changed: 20 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,9 @@
3939
"example": "yarn workspace react-native-sized-webview-example",
4040
"test": "jest",
4141
"typecheck": "tsc",
42-
"lint": "eslint \"**/*.{js,ts,tsx}\"",
42+
"lint": "biome check",
43+
"lint:fix": "biome check --write",
44+
"format": "biome format --write",
4345
"clean": "del-cli lib",
4446
"prepare": "bob build",
4547
"release": "release-it --only-version"
@@ -80,33 +82,27 @@
8082
"registry": "https://registry.npmjs.org/"
8183
},
8284
"devDependencies": {
83-
"@commitlint/config-conventional": "^20.2.0",
84-
"@eslint/compat": "^2.0.0",
85-
"@eslint/eslintrc": "^3.3.3",
86-
"@eslint/js": "^9.39.2",
87-
"@evilmartians/lefthook": "^2.0.12",
88-
"@react-native/babel-preset": "0.83.0",
89-
"@react-native/eslint-config": "^0.83.0",
90-
"@release-it/conventional-changelog": "^10.0.3",
85+
"@biomejs/biome": "^2.4.13",
86+
"@commitlint/config-conventional": "^20.5.0",
87+
"@evilmartians/lefthook": "^2.1.6",
88+
"@react-native/babel-preset": "0.83.6",
89+
"@react-native/jest-preset": "0.85.2",
90+
"@release-it/conventional-changelog": "^11.0.0",
9191
"@testing-library/react-native": "^13.3.3",
9292
"@types/jest": "^30.0.0",
93-
"@types/react": "^19.2.7",
93+
"@types/react": "^19.2.14",
9494
"@types/react-dom": "^19.2.3",
9595
"babel-plugin-react-compiler": "^1.0.0",
96-
"commitlint": "^20.2.0",
96+
"commitlint": "^20.5.0",
9797
"del-cli": "^7.0.0",
98-
"eslint": "^9.39.2",
99-
"eslint-config-prettier": "^10.1.8",
100-
"eslint-plugin-prettier": "^5.5.4",
101-
"jest": "^30.2.0",
102-
"prettier": "^3.7.4",
103-
"react": "19.2.3",
104-
"react-native": "0.83.0",
105-
"react-native-builder-bob": "^0.40.17",
106-
"react-native-webview": "^13.16.0",
107-
"react-test-renderer": "19.2.3",
108-
"release-it": "^19.1.0",
109-
"typescript": "^5.9.3"
98+
"jest": "^30.3.0",
99+
"react": "19.2.0",
100+
"react-native": "0.83.6",
101+
"react-native-builder-bob": "^0.41.0",
102+
"react-native-webview": "^13.16.1",
103+
"react-test-renderer": "19.2.0",
104+
"release-it": "^20.0.1",
105+
"typescript": "^6.0.3"
110106
},
111107
"peerDependencies": {
112108
"react": "*",
@@ -119,7 +115,7 @@
119115
"sideEffects": false,
120116
"packageManager": "yarn@3.6.1",
121117
"jest": {
122-
"preset": "react-native",
118+
"preset": "@react-native/jest-preset",
123119
"modulePathIgnorePatterns": [
124120
"<rootDir>/example/node_modules",
125121
"<rootDir>/lib/"
@@ -156,13 +152,6 @@
156152
"release": true
157153
}
158154
},
159-
"prettier": {
160-
"quoteProps": "consistent",
161-
"singleQuote": true,
162-
"tabWidth": 2,
163-
"trailingComma": "es5",
164-
"useTabs": false
165-
},
166155
"react-native-builder-bob": {
167156
"source": "src",
168157
"output": "lib",

0 commit comments

Comments
 (0)