Skip to content

Commit

Permalink
perf: use explicit composite architecture for RenderHTML component
Browse files Browse the repository at this point in the history
This allows to avoid the cost of instantiating a new engine for each
HTML snippet, see https://git.io/JRcZb

fix #4123
  • Loading branch information
jsamr committed Aug 11, 2021
1 parent 3ec2137 commit 1b68a5f
Show file tree
Hide file tree
Showing 9 changed files with 100 additions and 71 deletions.
2 changes: 2 additions & 0 deletions src/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import ErrorBoundary from './components/ErrorBoundary';
import Expensify from './Expensify';
import {LocaleContextProvider} from './components/withLocalize';
import OnyxProvider from './components/OnyxProvider';
import HTMLEngineProvider from './components/HTMLEngineProvider';
import ComposeProviders from './components/ComposeProviders';

LogBox.ignoreLogs([
Expand All @@ -25,6 +26,7 @@ const App = () => (
OnyxProvider,
SafeAreaProvider,
LocaleContextProvider,
HTMLEngineProvider,
]}
>
<CustomStatusBar />
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
/* eslint-disable react/prop-types */
import _ from 'underscore';
import React from 'react';
import {useWindowDimensions, TouchableOpacity} from 'react-native';
import HTML, {
import React, {useMemo} from 'react';
import {TouchableOpacity} from 'react-native';
import {
TRenderEngineProvider,
RenderHTMLConfigProvider,
defaultHTMLElementModels,
TNodeChildrenRenderer,
splitBoxModelStyle,
Expand All @@ -18,20 +20,16 @@ import ThumbnailImage from '../ThumbnailImage';
import variables from '../../styles/variables';
import themeColors from '../../styles/themes/default';
import Text from '../Text';
import {
propTypes as renderHTMLPropTypes,
defaultProps as renderHTMLDefaultProps,
} from './renderHTMLPropTypes';

const propTypes = {
/** Whether text elements should be selectable */
textSelectable: PropTypes.bool,
...renderHTMLPropTypes,
children: PropTypes.node,
};

const defaultProps = {
textSelectable: false,
...renderHTMLDefaultProps,
children: null,
};

const MAX_IMG_DIMENSIONS = 512;
Expand Down Expand Up @@ -221,31 +219,40 @@ const renderersProps = {
},
};

const BaseRenderHTML = ({html, debug, textSelectable}) => {
const {width} = useWindowDimensions();
const containerWidth = width * 0.8;
const defaultViewProps = {style: {alignItems: 'flex-start'}};

// We are using the explicit composite architecture for performance gains.
// Configuration for RenderHTML is handled in a top-level component providing
// context to RenderHTMLSource components. See https://git.io/JRcZb
// Beware that each prop should be referentialy stable between renders to avoid
// costly invalidations and commits.
const BaseHTMLEngineProvider = ({children, textSelectable}) => {
// We need to memoize this prop to make it referentially stable.
const defaultTextProps = useMemo(() => ({selectable: textSelectable}), [textSelectable]);
return (
<HTML
defaultTextProps={{selectable: textSelectable}}
defaultViewProps={{style: {alignItems: 'flex-start'}}}
<TRenderEngineProvider
customHTMLElementModels={customHTMLElementModels}
renderers={renderers}
renderersProps={renderersProps}
baseStyle={webViewStyles.baseFontStyle}
tagsStyles={webViewStyles.tagStyles}
enableCSSInlineProcessing={false}
dangerouslyDisableWhitespaceCollapsing={false}
contentWidth={containerWidth}
computeEmbeddedMaxWidth={computeEmbeddedMaxWidth}
systemFonts={EXTRA_FONTS}
source={{html}}
debug={debug}
/>
>
<RenderHTMLConfigProvider
defaultTextProps={defaultTextProps}
defaultViewProps={defaultViewProps}
renderers={renderers}
renderersProps={renderersProps}
computeEmbeddedMaxWidth={computeEmbeddedMaxWidth}
>
{children}
</RenderHTMLConfigProvider>
</TRenderEngineProvider>
);
};

BaseRenderHTML.displayName = 'BaseRenderHTML';
BaseRenderHTML.propTypes = propTypes;
BaseRenderHTML.defaultProps = defaultProps;
BaseHTMLEngineProvider.displayName = 'BaseHTMLEngineProvider';
BaseHTMLEngineProvider.propTypes = propTypes;
BaseHTMLEngineProvider.defaultProps = defaultProps;

export default BaseRenderHTML;
export default BaseHTMLEngineProvider;
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import PropTypes from 'prop-types';

const propTypes = {
/** HTML string to render */
html: PropTypes.string.isRequired,
children: PropTypes.node,

/** Optional debug flag */
/** Optional debug flag. Prints the TRT in the console when true. */
debug: PropTypes.bool,
};

const defaultProps = {
children: null,
debug: false,
};

Expand Down
20 changes: 20 additions & 0 deletions src/components/HTMLEngineProvider/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import React from 'react';
import BaseHTMLEngineProvider from './BaseHTMLEngineProvider';
import {defaultProps, propTypes} from './htmlEnginePropTypes';
import withWindowDimensions from '../withWindowDimensions';
import canUseTouchScreen from '../../libs/canUseTouchscreen';

const HTMLEngineProvider = ({isSmallScreenWidth, debug, children}) => (
<BaseHTMLEngineProvider
debug={debug}
textSelectable={!canUseTouchScreen() || !isSmallScreenWidth}
>
{children}
</BaseHTMLEngineProvider>
);

HTMLEngineProvider.displayName = 'HTMLEngineProvider';
HTMLEngineProvider.propTypes = propTypes;
HTMLEngineProvider.defaultProps = defaultProps;

export default withWindowDimensions(HTMLEngineProvider);
15 changes: 15 additions & 0 deletions src/components/HTMLEngineProvider/index.native.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import React from 'react';
import BaseHTMLEngineProvider from './BaseHTMLEngineProvider';
import {propTypes, defaultProps} from './htmlEnginePropTypes';

const HTMLEngineProvider = ({debug, children}) => (
<BaseHTMLEngineProvider debug={debug}>
{children}
</BaseHTMLEngineProvider>
);

HTMLEngineProvider.displayName = 'HTMLEngineProvider';
HTMLEngineProvider.propTypes = propTypes;
HTMLEngineProvider.defaultProps = defaultProps;

export default HTMLEngineProvider;
27 changes: 27 additions & 0 deletions src/components/RenderHTML.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import React from 'react';
import {useWindowDimensions} from 'react-native';
import PropTypes from 'prop-types';
import {RenderHTMLSource} from 'react-native-render-html';

// We are using the explicit composite architecture for performance gains.
// Configuration for RenderHTML is handled in a top-level component providing
// context to RenderHTMLSource components. See https://git.io/JRcZb
// The provider is available at src/components/HTMLEngineProvider/
const RenderHTML = ({html}) => {
const {width} = useWindowDimensions();
return (
<RenderHTMLSource
contentWidth={width * 0.8}
source={{html}}
/>
);
};

RenderHTML.displayName = 'RenderHTML';
RenderHTML.propTypes = {
/** HTML string to render */
html: PropTypes.string.isRequired,
};
RenderHTML.defaultProps = {};

export default RenderHTML;
22 changes: 0 additions & 22 deletions src/components/RenderHTML/index.js

This file was deleted.

19 changes: 0 additions & 19 deletions src/components/RenderHTML/index.native.js

This file was deleted.

1 change: 0 additions & 1 deletion src/pages/home/report/ReportActionItemFragment.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,6 @@ class ReportActionItemFragment extends React.PureComponent {
? (
<RenderHTML
html={fragment.html + (fragment.isEdited ? '<edited/>' : '')}
debug={false}
/>
) : (
<Text
Expand Down

0 comments on commit 1b68a5f

Please sign in to comment.