Skip to content

Commit 78329a2

Browse files
authored
JSX runtime (part 2 of 3) - more formal rendering logic for both classic and new jsx runtimes (#3947)
* rework jsx helpers for both new and old runtimes * implement both new and old render patterns for classic and jsx-runtimes * Change files * move package README.md files to be next to the source code * update documentation * Change files * prettier format updates to documentation * tweak documentation just a bit
1 parent edcf0d6 commit 78329a2

23 files changed

+730
-484
lines changed

apps/fluent-tester/macos/Podfile.lock

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,10 @@ PODS:
33
- DoubleConversion (1.1.6)
44
- FBLazyVector (0.74.30)
55
- fmt (9.1.0)
6-
- FRNAvatar (0.21.4):
6+
- FRNAvatar (0.21.11):
77
- MicrosoftFluentUI (= 0.13.1)
88
- React
9-
- FRNCallout (0.27.2):
9+
- FRNCallout (0.27.9):
1010
- DoubleConversion
1111
- glog
1212
- RCT-Folly (= 2024.01.01.00)
@@ -28,13 +28,13 @@ PODS:
2828
- ReactCommon/turbomodule/bridging
2929
- ReactCommon/turbomodule/core
3030
- Yoga
31-
- FRNCheckbox (0.17.9):
31+
- FRNCheckbox (0.17.16):
3232
- React
33-
- FRNMenuButton (0.13.19):
33+
- FRNMenuButton (0.13.26):
3434
- React
35-
- FRNRadioButton (0.21.16):
35+
- FRNRadioButton (0.21.23):
3636
- React
37-
- FRNVibrancyView (0.3.0):
37+
- FRNVibrancyView (0.3.4):
3838
- React
3939
- glog (0.3.5)
4040
- MicrosoftFluentUI (0.13.1):
@@ -123,7 +123,7 @@ PODS:
123123
- fmt (= 9.1.0)
124124
- glog
125125
- RCTDeprecation (0.74.30)
126-
- RCTFocusZone (0.21.0):
126+
- RCTFocusZone (0.21.7):
127127
- React
128128
- RCTRequired (0.74.30)
129129
- RCTTypeSafety (0.74.30):
@@ -1262,11 +1262,11 @@ DEPENDENCIES:
12621262
- FBLazyVector (from `../../../node_modules/react-native-macos/Libraries/FBLazyVector`)
12631263
- fmt (from `../../../node_modules/react-native-macos/third-party-podspecs/fmt.podspec`)
12641264
- FRNAvatar (from `../../../packages/experimental/Avatar/FRNAvatar.podspec`)
1265-
- "FRNCallout (from `../../../node_modules/@fluentui-react-native/callout`)"
1265+
- "FRNCallout (from `../node_modules/@fluentui-react-native/callout`)"
12661266
- FRNCheckbox (from `../../../packages/experimental/Checkbox/FRNCheckbox.podspec`)
12671267
- FRNMenuButton (from `../../../packages/components/MenuButton/FRNMenuButton.podspec`)
12681268
- FRNRadioButton (from `../../../packages/components/RadioGroup/FRNRadioButton.podspec`)
1269-
- "FRNVibrancyView (from `../../../node_modules/@fluentui-react-native/vibrancy-view`)"
1269+
- "FRNVibrancyView (from `../node_modules/@fluentui-react-native/vibrancy-view`)"
12701270
- glog (from `../../../node_modules/react-native-macos/third-party-podspecs/glog.podspec`)
12711271
- RCT-Folly (from `../../../node_modules/react-native-macos/third-party-podspecs/RCT-Folly.podspec`)
12721272
- RCT-Folly/Fabric (from `../../../node_modules/react-native-macos/third-party-podspecs/RCT-Folly.podspec`)
@@ -1341,15 +1341,15 @@ EXTERNAL SOURCES:
13411341
FRNAvatar:
13421342
:path: "../../../packages/experimental/Avatar/FRNAvatar.podspec"
13431343
FRNCallout:
1344-
:path: "../../../node_modules/@fluentui-react-native/callout"
1344+
:path: "../node_modules/@fluentui-react-native/callout"
13451345
FRNCheckbox:
13461346
:path: "../../../packages/experimental/Checkbox/FRNCheckbox.podspec"
13471347
FRNMenuButton:
13481348
:path: "../../../packages/components/MenuButton/FRNMenuButton.podspec"
13491349
FRNRadioButton:
13501350
:path: "../../../packages/components/RadioGroup/FRNRadioButton.podspec"
13511351
FRNVibrancyView:
1352-
:path: "../../../node_modules/@fluentui-react-native/vibrancy-view"
1352+
:path: "../node_modules/@fluentui-react-native/vibrancy-view"
13531353
glog:
13541354
:podspec: "../../../node_modules/react-native-macos/third-party-podspecs/glog.podspec"
13551355
RCT-Folly:
@@ -1466,17 +1466,17 @@ SPEC CHECKSUMS:
14661466
DoubleConversion: 5b92c4507c560bb62e7aa1acdf2785ea3ff08b3b
14671467
FBLazyVector: 0aa0591844f7fe4736f3aba70d30232edbd21eb5
14681468
fmt: 03574da4b7ba40de39da59677ca66610ce8c4a02
1469-
FRNAvatar: ec4d219c71bfd3d74306a1fcf94f71393b63359f
1470-
FRNCallout: 1989375a3f3f704d7f85a560d979c222a5d2dc5c
1471-
FRNCheckbox: 80e3700277629ce802b1f07123e564f954d0e9b1
1472-
FRNMenuButton: 43dd93252a1a6a0a3f4546b6d8021c2a572bf586
1473-
FRNRadioButton: 8260e87a2df63c5a67ba23ce87dc8234ef137643
1474-
FRNVibrancyView: 975f7e8ea14999015c3224743057418574833afa
1469+
FRNAvatar: 1eccbe629f3034e2caa11f62e16db120ac3c7836
1470+
FRNCallout: 3eca65ff4ee29de3881a8402842b230626024ff6
1471+
FRNCheckbox: 1d87e81b71e6706b6e94e69ab6923e50755c4a29
1472+
FRNMenuButton: d87749093d3418d9c9377144f9e17e09af707335
1473+
FRNRadioButton: 8b6c2d6c2d5c513ce493568ac0597d114359cb57
1474+
FRNVibrancyView: 0fb5d289c2a4b934596fb26d81166151d66fa28c
14751475
glog: ba31c1afa7dcf1915a109861bccdb4421be6175b
14761476
MicrosoftFluentUI: dde98d8ed3fc306d9ddd0a6f0bc0c1f24fe5275e
14771477
RCT-Folly: f47da9a444aae485a0528b3bccf0336156009d60
14781478
RCTDeprecation: 6c1d8fdaf3e34933c33a56531bd984bc2d22ef9e
1479-
RCTFocusZone: 50bf108af173c92cb8c4a776c17c37d010db31bc
1479+
RCTFocusZone: 999b4c2acb2193fd189f9ef6d1e970c2a6676250
14801480
RCTRequired: 5266165e3b6c7ca1554c5a75fb4c1ebe1bc60b53
14811481
RCTTypeSafety: ced894df76a17b8f7331d24e2efa862a7a616e89
14821482
React: 620dbf1e10232c8517a8b89d0def5b29e04ad24e
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"type": "minor",
3+
"comment": "implement both new and old render patterns for classic and jsx-runtimes",
4+
"packageName": "@fluentui-react-native/framework-base",
5+
"email": "[email protected]",
6+
"dependentChangeType": "patch"
7+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"type": "none",
3+
"comment": "update documentation",
4+
"packageName": "@fluentui-react-native/immutable-merge",
5+
"email": "[email protected]",
6+
"dependentChangeType": "none"
7+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"type": "none",
3+
"comment": "update documentation",
4+
"packageName": "@fluentui-react-native/memo-cache",
5+
"email": "[email protected]",
6+
"dependentChangeType": "none"
7+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"type": "none",
3+
"comment": "update documentation",
4+
"packageName": "@fluentui-react-native/merge-props",
5+
"email": "[email protected]",
6+
"dependentChangeType": "none"
7+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"type": "patch",
3+
"comment": "rework jsx helpers for both new and old runtimes",
4+
"packageName": "@fluentui-react-native/tester",
5+
"email": "[email protected]",
6+
"dependentChangeType": "patch"
7+
}

packages/framework-base/README.md

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,19 @@
1-
# Framework Base package
1+
# `@fluentui-react-native/framework-base`
22

3-
This package provides core types and helpers used by both the old and new versions of the framework.
3+
This package provides core implementations and types to support both the legacy and current frameworks.
44

55
Several previously standalone packages have had their implementations moved into this package. This allows them to share certain typings and helpers without having to work around circular dependency issues. The moved packages are:
66

7-
- `@fluentui-react-native/immutable-merge`
8-
- `@fluentui-react-native/memo-cache`
9-
- `@fluentui-react-native/merge-props`
7+
- [`@fluentui-react-native/immutable-merge`](./src/immutable-merge/README.md)
8+
- [`@fluentui-react-native/memo-cache`](./src/memo-cache/README.md)
9+
- [`@fluentui-react-native/merge-props`](./src/merge-props/README.md)
1010

11-
The functionality in these packages can be imported either by the base entry point for the package, or by using dedicated exports. The previous packages will continue to exist for the time being but are now just references to their individual exports. Note that export maps require special handling for metro bundling (with the exception of the jsx-runtime export) so the export maps are primarily for use
12-
in JS/web projects.
11+
The functionality in these packages is now exposed as part of this package.
1312

14-
## Type Helpers
13+
## Component Patterns
1514

16-
- TODO: There are a number of issues with the way types are handled in the larger fluentui-react-native project, helpers and core types will be added here to help solve inference issues, avoid hard typecasts, and help the project eventually move to typescript 5.x.
15+
The shared patterns for rendering components, as well as the JSX handlers have been centralized in this package. More information can be found [here](./src/component-patterns/README.md).
1716

18-
## JSX Helpers
17+
## Type Helpers
1918

20-
- TODO: Both classic and the new jsx-runtime helpers will eventually come out of this package and be shared between old and new frameworks. This will be necessary to improve typing across the board.
19+
- TODO: There are a number of issues with the way types are handled in the larger fluentui-react-native project, helpers and core types will be added here to help solve inference issues, avoid hard typecasts, and help the project eventually move to typescript 5.x.

packages/framework-base/package.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,11 @@
1616
"import": "./lib/index.js",
1717
"require": "./lib-commonjs/index.js",
1818
"types": "./lib/index.d.ts"
19+
},
20+
"./jsx-runtime": {
21+
"import": "./lib/jsx-runtime.js",
22+
"require": "./lib-commonjs/jsx-runtime.js",
23+
"types": "./lib/jsx-runtime.d.ts"
1924
}
2025
},
2126
"scripts": {
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
# `fluentui-react-native` - Common component patterns
2+
3+
These are the base component patterns shared across the deprecated or v0 framework (found under packages/deprecated), and the newer framework (found under packages/framework). This also includes the custom JSX handlers required to render them properly.
4+
5+
There are two main patterns exposed here: direct rendering and staged rendering.
6+
7+
## Direct Rendering
8+
9+
The direct rendering pattern allows a component to be called directly, rather than creating a new entry in the DOM.
10+
11+
As an example, if you want to create a wrapper around a component called `MyText` that has `italicize` as one of its props, that always wants to set that value to true. You could define:
12+
13+
```ts
14+
const MyNewText: React.FunctionComponent<MyTextProps> = (props) => {
15+
return <MyText {...props, italicize: true} />;
16+
}
17+
```
18+
19+
When this is rendered, there is an entry for `MyNewText` which contains a `MyText` (another entry), which might contains `Text` (for react-native usage). The direct rendering pattern is one where a component can denote that it is safe to be called directly as a function, instead operating as a prop transform that gets applied to the underlying component.
20+
21+
- For the above to be safe, `MyNewText` should NOT use hooks. In the case of any conditional rendering logic this will break the rule of hooks.
22+
23+
There are two types of implementations in this folder:
24+
25+
- `DirectComponent` - a functional component that marks itself as direct with a `_callDirect: true` attached property. This will then be called as a normal function component, with children included as part of props.
26+
- `LegacyDirectComponent` - the pattern currently used in this library that should be moved away from. In this case `_canCompose: true` is set as an attached property, and the function component will be called with children split from props.
27+
28+
The internal logic of the JSX rendering helpers will handle both patterns. In the case of the newer `DirectComponent` pattern, the component will still work, even without any jsx hooks, whereas the `LegacyDirectComponent` pattern will have a somewhat undefined behavior with regards to children.
29+
30+
## Staged Rendering
31+
32+
The issue with the direct component pattern above, is that hooks are integral to writing functional components. The staged rendering pattern is designed to help with this. In this case a component is implemented in two stages, the prep stage where hooks are called, and the rendering stage where the tree is emitted.
33+
34+
As above there is a newer and older version of the pattern.
35+
36+
- `StagedComponent` - the newer version of the pattern, where the returned component function expects children as part of props.
37+
- `StagedRender` - the older version, where children are split out and JSX hooks are required to render correctly.
38+
39+
Note that while the newer patterns work without any JSX hooks, the hooks will enable the element flattening.
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import React from 'react';
2+
import * as ReactJSX from 'react/jsx-runtime';
3+
import type { RenderType, RenderResult, DirectComponent, LegacyDirectComponent } from './render.types';
4+
5+
export type CustomRender = () => RenderResult;
6+
7+
function asDirectComponent<TProps>(type: RenderType): DirectComponent<TProps> | undefined {
8+
if (typeof type === 'function' && (type as DirectComponent<TProps>)._callDirect) {
9+
return type as DirectComponent<TProps>;
10+
}
11+
return undefined;
12+
}
13+
14+
function asLegacyDirectComponent<TProps>(type: RenderType): LegacyDirectComponent<TProps> | undefined {
15+
if (typeof type === 'function' && (type as LegacyDirectComponent<TProps>)._canCompose) {
16+
return type as LegacyDirectComponent<TProps>;
17+
}
18+
return undefined;
19+
}
20+
21+
export function renderForJsxRuntime<TProps>(
22+
type: React.ElementType,
23+
props: React.PropsWithChildren<TProps>,
24+
key?: React.Key,
25+
jsxFn: typeof ReactJSX.jsx = ReactJSX.jsx,
26+
): RenderResult {
27+
const legacyDirect = asLegacyDirectComponent(type);
28+
if (legacyDirect) {
29+
const { children, ...rest } = props;
30+
const newProps = { ...rest, key };
31+
return legacyDirect(newProps, ...React.Children.toArray(children)) as RenderResult;
32+
}
33+
const directComponent = asDirectComponent<TProps>(type);
34+
if (directComponent) {
35+
const newProps = { ...props, key };
36+
return directComponent(newProps);
37+
}
38+
return jsxFn(type, props, key);
39+
}
40+
41+
export function renderForClassicRuntime<TProps>(type: RenderType, props: TProps, ...children: React.ReactNode[]): RenderResult {
42+
const legacyDirect = asLegacyDirectComponent(type);
43+
if (legacyDirect) {
44+
return legacyDirect(props, ...children) as RenderResult;
45+
}
46+
const directComponent = asDirectComponent(type);
47+
if (directComponent) {
48+
const newProps = { ...props, children };
49+
return directComponent(newProps);
50+
}
51+
return React.createElement(type, props, ...children);
52+
}
53+
54+
export const renderSlot = renderForClassicRuntime;

0 commit comments

Comments
 (0)