Skip to content

Commit e3769e9

Browse files
committed
RFC: Reduce API fragmentation across platforms
1 parent 5324ef5 commit e3769e9

File tree

1 file changed

+340
-0
lines changed

1 file changed

+340
-0
lines changed
Lines changed: 340 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,340 @@
1+
---
2+
title: Reduce API fragmentation across platforms
3+
author:
4+
- Nicolas Gallagher
5+
date: 2021-08-03
6+
---
7+
8+
# RFC: Reduce API fragmentation across platforms
9+
10+
## Summary
11+
12+
This is a proposal to incrementally reduce the API fragmentation faced by developers using React Native to target multiple platforms, and sharing code between native and web. Each proposed change is additive and can be tackled relatively independently. The aim is to avoid needing to migrate existing React Native code, and to make deprecations an optional and future part of this work.
13+
14+
## Motivation
15+
16+
React Native currently includes many APIs that are modelled on Web APIs but do not conform to the standards of those Web APIs. React Native also includes many APIs that achieve the same results on Android and iOS but are exposed as 2 different props. And React Native includes several APIs that have known performance (network and runtime) drawbacks on Web.
17+
18+
This proposal aims to allow developers to target more platforms with cross-platform APIs, and deliver better performance when targeting browsers. Features for Android, iOS, and Web are unified by aligning with the Web standard to a) minimize the overhead when running in browsers, and b) reduce developer education required to learn these features.
19+
20+
## Detailed design
21+
22+
Many of these additions are already in progress and have been proven in a user-space shim at Meta. The features that cannot be shimmed are explicitly called out below. Those features require core changes to React Native. Other features can be shimmed in user-space JavaScript if desired (with any associated performance cost of doing so).
23+
24+
### New common props
25+
26+
Certain props common to all core components have direct equivalents in React DOM. We should support those props where possible. For example, many of the existing `accessibility*` props in React Native are currently mapped to `aria-*` equivalents by React Native for Web. Supporting the ARIA interface would allow more ARIA-targeting React DOM libraries to work on React Native.
27+
28+
* ARIA props
29+
* Support `aria-*` aliases for `accessibility*` props.
30+
* `aria-busy` => `accessibilityState.busy`
31+
* `aria-checked` => `accessibilityState.checked`
32+
* `aria-disabled` => `accessibilityState.disabled`
33+
* `aria-expanded` => `accessibilityState.expanded`
34+
* `aria-hidden` => `accessibilityElementsHidden` (iOS) + `importantforAccessibility` (Android)
35+
* `aria-label` => `accessibilityLabel`
36+
* `aria-live` => `accessibilityLiveRegion` (and add `'off'` value => `'none'`)
37+
* `aria-modal` => `accessibilityViewIsModal`
38+
* `aria-selected` => `accessibilityState.selected`
39+
* `aria-valuemax` => `accessibilityValue.max`
40+
* `aria-valuemin` => `accessibilityValue.min`
41+
* `aria-valuenow` => `accessibilityValue.now`
42+
* `aria-valuetext` => `accessibilityValue.text`
43+
* `role` => `accessibilityRole`
44+
* See prior work: [RFC Accessibility APIs](https://github.com/react-native-community/discussions-and-proposals/pull/410/files).
45+
* `id`
46+
* Add `id` prop as synonym for `nativeID`.
47+
* `onAuxClick`
48+
* Add React DOM `onAuxClick` as PointerEvent type.
49+
* This cannot be shimmed in user-space.
50+
* `onClick`
51+
* Add React DOM `onClick` as PointerEvent type.
52+
* This cannot be shimmed in user-space.
53+
* `onPointer*`
54+
* Add React DOM PointerEvent props.
55+
* The Android and iOS implementation is currently being developed by Meta.
56+
* This cannot be shimmed in user-space.
57+
* `tabIndex`
58+
* Add `tabIndex` prop, which is similar to React Native `focusable` prop.
59+
* Add support for `0` and `-1` values only.
60+
61+
Example:
62+
63+
```jsx
64+
<View
65+
aria-hidden={false}
66+
aria-label="Accessibility label"
67+
id="native-id"
68+
onClick={handleDOMClick}
69+
onPointerDown={handlePointerDown}
70+
role="button"
71+
tabIndex={0}
72+
>
73+
```
74+
75+
### New props for `<Image>`
76+
77+
Various React DOM `img` props also have direct equivalents in React Native. For example, `srcSet` is a subset of the `source` prop. Features like alternative text content are also required to target web.
78+
79+
* `alt`
80+
* Add `alt` prop for alternative text support.
81+
* `crossOrigin`
82+
* Add prop to declaratively configure cross-origin permissions for loaded images. Equivalent to setting the relevant `Header` in the `source` object.
83+
* `referrerPolicy`
84+
* Add prop to declaratively configure referrer policy. Equivalent to setting the relevant `Header` in the `source` object.
85+
* `src`
86+
* Alias for `source.uri`.
87+
* `srcSet`
88+
* Alias for a `source` array with `uri` and `scale` properties defined.
89+
* `tintColor`
90+
* Image currently sets `tintColor` via the inclusion of a non-standard style property.
91+
* Recommend adding `tintColor` prop as alias for the style property, to avoid the need to parse out `tintColor` from style for platforms like web.
92+
93+
`srcSet` to `source` mapping:
94+
95+
```js
96+
const { crossOrigin, referrerPolicy, srcSet } = props;
97+
const source = [];
98+
const srcList = srcSet.split(', ');
99+
srcList.forEach((src) => {
100+
const [uri, xscale] = src.split(' ');
101+
const scale = parseInt(xscale.split('x')[0], 10);
102+
const headers = {};
103+
if (crossOrigin === 'use-credentials') {
104+
headers['Access-Control-Allow-Credentials'] = true;
105+
}
106+
if (referrerPolicy != null) {
107+
headers['Referrer-Policy'] = referrerPolicy;
108+
}
109+
source.push({ headers, height, scale, uri, width });
110+
});
111+
```
112+
113+
Example:
114+
115+
```jsx
116+
<Image
117+
alt="Alternative text"
118+
crossOrigin="anonymous"
119+
srcSet="https://image.png 1x, https://image2.png 2x"
120+
tintColor="purple"
121+
>
122+
```
123+
124+
### New props for `<TextInput>`
125+
126+
* `autoComplete`
127+
* Unify values for Android (`autoComplete`) and iOS (`textContentType`) with Web (`autoComplete`).
128+
* See below for example mapping
129+
* `enterKeyHint`
130+
* Add `enterKeyHint` prop and map to equivalent `returnKeyType` values.
131+
* `inputMode`
132+
* Add `inputMode` prop and map to equivalent `keyboardType` values.
133+
* `inputMode === 'decimal'` => `keyboardType = 'decimal-pad'`
134+
* `inputMode === 'email'` => `keyboardType = 'email-address'`
135+
* `inputMode === 'numeric'` => `keyboardType = 'numeric'`
136+
* `inputMode === 'search'` => `keyboardType = 'search'`
137+
* `inputMode === 'tel'` => `keyboardType = 'phone-pad'`
138+
* `inputMode === 'url'` => `keyboardType = 'url'`
139+
* `readOnly`
140+
* Add `readOnly` prop as synonym for `editable`.
141+
* `rows`
142+
* Add `rows` prop as synonym for `numberOfLines`.
143+
* Support auto-expanding textarea on native platforms.
144+
145+
`autoComplete` mapping:
146+
147+
```js
148+
// https://reactnative.dev/docs/textinput#autocomplete-android
149+
const autoCompleteAndroid = {
150+
'address-line1': 'postal-address-region',
151+
'address-line2': 'postal-address-locality',
152+
bday: 'birthdate-full',
153+
'bday-day': 'birthdate-day',
154+
'bday-month': 'birthdate-month',
155+
'bday-year': 'birthdate-year',
156+
'cc-csc': 'cc-csc',
157+
'cc-exp': 'cc-exp',
158+
'cc-exp-month': 'cc-exp-month',
159+
'cc-exp-year': 'cc-exp-year',
160+
'cc-number': 'cc-number',
161+
country: 'postal-address-country',
162+
'current-password': 'password',
163+
email: 'email',
164+
name: 'name',
165+
'additional-name': 'name-middle',
166+
'family-name': 'name-family',
167+
'given-name': 'name-given',
168+
'honorific-prefix': 'namePrefix',
169+
'honorific-suffix': 'nameSuffix',
170+
'new-password': 'password-new',
171+
off: 'off',
172+
'one-time-code': 'sms-otp',
173+
'postal-code': 'postal-code',
174+
sex: 'gender',
175+
'street-address': 'street-address',
176+
tel: 'tel',
177+
'tel-country-code': 'tel-country-code',
178+
'tel-national': 'tel-national',
179+
username: 'username'
180+
};
181+
182+
// https://reactnative.dev/docs/textinput#textcontenttype-ios
183+
const autoCompleteIOS = {
184+
'address-line1': 'streetAddressLine1',
185+
'address-line2': 'streetAddressLine2',
186+
'cc-number': 'creditCardNumber',
187+
'current-password': 'password',
188+
country: 'countryName',
189+
email: 'emailAddress',
190+
name: 'name',
191+
'additional-name': 'middleName',
192+
'family-name': 'familyName',
193+
'given-name': 'givenName',
194+
nickname: 'nickname',
195+
'honorific-prefix': 'name-prefix',
196+
'honorific-suffix': 'name-suffix',
197+
'new-password': 'newPassword',
198+
off: 'none',
199+
'one-time-code': 'oneTimeCode',
200+
organization: 'organizationName',
201+
'organization-title': 'jobTitle',
202+
'postal-code': 'postalCode',
203+
'street-address': 'fullStreetAddress',
204+
tel: 'telephoneNumber',
205+
url: 'URL',
206+
username: 'username'
207+
};
208+
```
209+
210+
Example:
211+
212+
```jsx
213+
<TextArea
214+
autoComplete="email"
215+
inputMode="email"
216+
/>
217+
218+
<TextArea
219+
multiline
220+
readOnly
221+
rows={3}
222+
/>
223+
```
224+
225+
### New styles
226+
227+
* CSS Custom Properties
228+
* Support CSS custom property syntax `--variable-name`.
229+
* This can be shimmed in user-space on top of the existing `StyleSheet` API, with a React Context used to provide variables and values to a subtree.
230+
* CSS Lengths
231+
* Support `em`, `rem`, `v*`, `px` length units.
232+
* This can be shimmed in user-space on top of the existing `StyleSheet` API, with a React Context used to provide ancestral font-size information for `rem` values.
233+
* CSS Colors
234+
* Support CSS Colors using [Colorjs.io](https://colorjs.io/).
235+
* This can be shimmed in user-space.
236+
* CSS Media Queries
237+
* Support CSS Media Queries. Although Media Queries are not a preferred long-term solution, [Container Queries](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Container_Queries) are not yet widely supported by browsers.
238+
* This can be shimmed in user-space on top of the existing `StyleSheet` API.
239+
* `aspectRatio`
240+
* Support string values, i.e., `'16 / 9'`, to align with CSS.
241+
* `backgroundImage`
242+
* Support setting background images via `url()`.
243+
* Optional: support CSS gradients.
244+
* `boxShadow`
245+
* Add native support for CSS box shadows.
246+
* This would replace the very buggy iOS-specific `shadow*` styles and Android `elevation` style, allowing developers to unify shadows across native and web platforms.
247+
* `lineClamp`
248+
* Support CSS clamping text to a set number of lines via style.
249+
* This would be equivalent to using the `numberOfLines` prop on `<Text>` components.
250+
* `objectFit`
251+
* Support CSS `objectFit` property for `<Image>`.
252+
* This would be a subset (no "repeat" keyword) of functionality currently supported by the `resizeMode` prop and style for `<Image>`.
253+
* `objectFit === 'contain'` => `resizeMode = 'contain'`
254+
* `objectFit === 'cover'` => `resizeMode = 'cover'`
255+
* `objectFit === 'fill'` => `resizeMode = 'stretch'`
256+
* `objectFit === 'scale-down'` => `resizeMode = 'contain'`
257+
* `pointerEvents`
258+
* Support setting the `pointerEvents` values via style rather than a prop.
259+
* Retain the React Native specific `box-none` and `box-only` values.
260+
* `transform`
261+
* Support using string values to set transforms.
262+
* This can be shimmed in user-space with a parser.
263+
* `verticalAlign`
264+
* Support `verticalAlign` style as alias for `textAlignVertical`.
265+
* `verticalAlign === 'middle'` => `textAlignVertical = 'center';
266+
* userSelect
267+
* Support setting ability to select text via style.
268+
* Equivalent to using `selectable` prop on `<Text>`.
269+
270+
```jsx
271+
<View
272+
style={{
273+
boxShadow: '1px 1px 1px 1px #eee',
274+
backgroundColor: 'hsla(50,50,50,0.5)',
275+
pointerEvents: 'none',
276+
transform: 'scale(0.9)',
277+
width: '5rem'
278+
}}
279+
>
280+
281+
<Text
282+
style={{
283+
lineClamp: 3,
284+
userSelect: 'none',
285+
verticalAlign: 'middle'
286+
}}
287+
>
288+
289+
<Image
290+
style={{
291+
objectFit: 'cover'
292+
}}
293+
>
294+
```
295+
296+
### New imperative APIs
297+
298+
* `EventTarget` API
299+
* The `EventTarget` API is a implemented in JS environments, and should be available on React Native host elements. This feature is commonly used on web, and could be used by React Native developers to support more complex features without first requiring further core API changes to support each use case.
300+
* Promote `node.unstable_addEventListener`, `node.unstable_removeEventListener` to stable APIs.
301+
* Add `node.dispatchEvent` support.
302+
* This cannot be shimmed in user-space.
303+
* `node.getBoundingClientRect`
304+
* Synchronous element measurement API. Current APIs like `node.measure` are async and return slightly different information to what you expect from `getBoundingClientRect` on web.
305+
* This cannot be shimmed in user-space.
306+
307+
### Other
308+
309+
* Revisit the type of all events modeled on W3C events. For example, `onTouchStart` should receive an event object that conforms to that received by the same prop in React DOM.
310+
* Aim to deprecate `StyleSheet.flatten()`.
311+
* This API encourages runtime introspection of styles in render calls. This pattern is a performance cost (flattening arrays of objects), and prevents us from supporting build-time optimizations for web (extracting styles to CSS files).
312+
* Aim to deprecate `StyleSheet.compose()`.
313+
* This API has no real value.
314+
* Aim to restore obfuscation of styles passed to `StyleSheet.create()`.
315+
* Returning the styles from create calls encourages introspection of those styles. On web, we need to be able to remove the JavaScript style objects from bundles to support build-time optimization like extraction to CSS files.
316+
* Aim to deprecate the `Touchable*` components.
317+
* The `Pressable` component was shipped as a replacement for `Touchable*`.
318+
* Aim to support more interaction states with `Pressable`.
319+
* Add ‘hovered’ and ‘focused’ to the state object, and replace ‘testOnly_pressed’ with an object of states.
320+
* Aim to move `Animated` out of core.
321+
* Move Animated out of core to separate package in monorepo, allow React Native for Web to import this package. Eventually could drop Animated from ‘react-native’ export.
322+
* Aim to move `VirtualizedList` and `FlatList` out of core.
323+
* Move VirtualizedList/FlatList out of core to separate package in monorepo, allow React Native for Web to import this package. Eventually could drop Animated from ‘react-native’ export.
324+
* Compile to standard JavaScript before publishing to npm (make this the default for the community). React Native and its community packages currently publish uncompiled, flow-typed JavaScript to npm. This causes common tools to crash on these packages.
325+
* Prevent libraries importing `'react-native'` package internals, by using the Node.js package exports API.
326+
327+
## Drawbacks
328+
329+
* Many of these features can be shimmed in user-space, at the cost of runtime overhead for native or web platforms. The runtime overhead for web is untenable. The runtime overhead of shifting shims into the JS targeting native has yet to be established.
330+
* It adds redundancy to the React Native API, as the proposal is to make additive changes and avoid requiring removal of existing APIs in the short term.
331+
332+
## Adoption strategy
333+
334+
To encourage library and product developers to proactively migrate to these new APIs, React Native for Web plans to only support these W3C standards-based APIs in future versions. This will allow us to incrementally add APIs to React Native without needing to commit to simultaneously deprecating APIs and migrating existing React Native code. Existing React Native developers will adopt these APIs if they wish to support web.
335+
336+
There are few, if any, breaking changes anticipated for React Native itself.
337+
338+
## How we teach this
339+
340+
Teaching these APIs is simplified by developer familiarity with existing DOM / React DOM APIs.

0 commit comments

Comments
 (0)