Skip to main content

Announcing the Beta Foundry Release

ยท 8 min read
Jules Sam. Randolph
Developer of React Native Render HTML v6

After seven months of a long and exciting journey in alpha development and testing with the community, I am proud to announce the first Foundry (v6) beta release of React Native Render HTML.

This process has led the new API to mature, and the introduction of beta stage marks the end of its instability. During the beta stage, I will bring fixes to any issue reported by the community and expect the test coverage to rise above 90%. I also plan to refine the Discovery App and website, including additional sections for best user experience. Now, let's get to the heart of it!

The Discovery App#

Along with the release comes this new website and the Discovery App which features all the docs of the website with embedded RNRH-rendered snippets. For this very reason, the Discovery App is recommended for newcomers, or for those who want a "live" feeling of the new engine capabilities. The website is more suited during development when searching for specific information. In addition, the Discovery App has a collection of "Playgrounds" to play with specific components and observe how they respond to style and props changes.

Overview of New Features#

The Transient Render Engine#

The Transient Render engine (TRE) transforms DOM nodes into a ready-to-render data structure called the Transient Render Tree (TRT), which is made of TNodes. It allows features such as whitespace collapsing, hoisting, CSS inheritance and more.

A legitimate concerns is whether it adds any overhead. The short answer is no, if we compare it to the legacy engine which had its own transient render structure. Moreover, this TRT construction process is benchmarked to safeguard its speed. In addition to the enumerated features, the new transient data structure offers obvious advantages for library consumers:

  • It is totally transparent and predictable; you can create snapshots of a โ€‹TNode thanks to the snaphot() method for an intuitive understanding of the engine internals. This feature is extremely handy for debugging and testing!
  • It's hackable by allowing to define custom content models for tags. Say hi to inline images!
  • It is shipped with a CSS processor which enforces strict translation rules from CSS to React Native styles. Say goodbye to native crashes caused by unsupported CSS properties! See the dedicated article for reference.
  • It paves the way to server side (or build-time) pre-rendering in the future, and, why not, a react fiber and MDX builder.

Below is an example of HTML transformation into a Transient Render Tree:

Translation of HTML markup into a Transient Render Tree
<a href="https://domain.com">
This is
<span>phrasing content</span>
<img src="https://domain.com/logo.jpg" />
and this is <strong>too</strong>.
</a>
โ†“
<TDocument tagName="html">
<TBlock tagName="body">
<TBlock tagName="a" href="https://domain.com">
<TPhrasing anonymous>
<TText anonymous data="This is " />
<TText tagName="span" data="phrasing content" />
</TPhrasing>
<TBlock tagName="img" src="https://domain.com/logo.jpg" />
<TPhrasing anonymous>
<TText anonymous data="and this is " />
<TText tagName="strong" data="too" />
<TText anonymous data="." />
</TPhrasing>
</TBlock>
</TBlock>
</TDocument>
This figure shows how HTML markup translates to a transient render tree structure. The markup for the TRT is JSX and has been produced by the snapshot() method. Notice that whitespace have collapsed!
Learn More

A detailed review of the Transient Render Engine has its dedicated article. You can also learn more about the new data flow in the Architecture article.

Custom Renderers#

The new rendering API is an order of magnitude more powerful than the legacy. Custom renderers are now plain React Component instead of functions, and you have now access to the underlying renderer for rich customization:

Foundry Custom Renderer
import React from 'react';
import { Alert } from 'react-native';
function H1Renderer({
TDefaultRenderer,
...props
}) {
const onPress = () => Alert.alert('pressed!');
return (
<TDefaultRenderer
{...props}
onPress={onPress}
/>
);
}

Default renderers support onPress and many more interesting props! See โ€‹TDefaultRendererProps. Moreover, you can customize the rendering of children thanks to the TChildrenRenderer component!

Foundry Custom Renderer with Children Tampering
import React from 'react';
import { TChildrenRenderer } from 'react-native-render-html';
import AdComponent from './AdComponent';
function ArticleWithAds({
TDefaultRenderer,
tnode,
...defaultRendererProps
}) {
const firstChildrenChunk = tnode.children.slice(0, 2);
const secondChildrenChunk = tnode.children.slice(2, 4);
const thirdChildrenChunk = tnode.children.slice(4);
return (
<TDefaultRenderer tnode={tnode} {...defaultRendererProps}>
<TChildrenRenderer tchildren={firstChildrenChunk} />
{firstChildrenChunk.length === 2 ? <AdComponent /> : null}
<TChildrenRenderer tchildren={secondChildrenChunk} />
{secondChildrenChunk.length === 2 ? <AdComponent /> : null}
<TChildrenRenderer tchildren={thirdChildrenChunk} />
</TDefaultRenderer>
);
}

This custom renderer will insert an AdComponent element after the second and fourth children of this TNode.

Last but not least, custom renderers can reuse internal renderers (those special renderers displaying lists, images and anchors). For this purpose, you can use the InternalRenderer prop or useInternalRenderer hook.

Font Selection#

CSS font-family property allows a comma-separated list of fonts, while react Native fontFamily does not. Moreover, setting a font that is not available in the system will often lead to native crashes. To reconcile this inconsistency, the CSS Processor will try to match each font in a font-family property with a list of supported fonts available in the system by the library consumer. The prop to declare such fonts is systemFonts.

A Revamped List Renderer#

The versatile new list component gives access to 47 markers presets for custom CSS Counter Styles; supports list-style-type CSS property and start attribute. There is also an experimental RTL feature!

Lower Alpha
Lower Greek
Lower Roman
Disc (RTL)
Arabic Indic (RTL)
Russian
Circle
Disc
Square
Thai
Disclosure Open
Disclosure Closed

Note that the thai and arabic indic counters have been rendered via @jsamr/counter-style presets, and the russian counter has been rendered with a custom, 2 lines-of-code counter renderer. Learn more about this fancy feature and examples in the dedicated article.

Extensible Internal Image Renderer#

The new internal image renderer is shipped with object-fit CSS property support. Moreover, its building blocks are completely reusable for custom rendering:

โ€‹IMGElementContainer

To render the container of the โ€‹<img> element.

โ€‹IMGElementContentError

To render the fallback view on error state.

โ€‹IMGElementContentLoading

To render the fallback view on loading state.

โ€‹IMGElementContentSuccess

To render the image on success state..

โ€‹useIMGElementState

To get the state of the image resource fetching.

Composite Rendering Architecture#

The three-layer rendering architecture shows its potential when rendering many HTML snippets at a time. It basically means that you can put configuration in a context to avoid the cost of instantiating the TRE too many times:

<RenderHTML source={{ html }} />

is equivalent to this (explicit 3 layers):

<TRenderEngineProvider>
<RenderHTMLConfigProvider>
<RenderHTMLSource source={{ html }} />
</RenderHTMLConfigProvider>
</TRenderEngineProvider>

It also offers the ability to select the root of the document to render, and to inspect the DOM object asynchronously before rendering.

Massive dumps of squashed bugs#

After merging the Foundry PR to master, 73% of open issues have been closed. Most fixes stem from the efficiency and rigor of the new test-driven CSS processor and Transient Render Engine. Most notable areas of fixes:

  • Unrecognized styles crashing the app. The new CSS processor is strict and deterministic in what is translatable to React Native styles and what is not.
  • Missing inheritable styles. Styles inheritance and specificity are implemented and battle-tested in the TRE.
  • List prefixes styles not inheriting from parent styles (color...). The new list internal renderer takes them all.
  • Missing HTML tags support. The TRE now has explicit support for all the tags specified in the HTML5 WHATWG living standard. However, not all tags will be rendered. Tags with a content model set to none will not be rendered (you can override this model though). Check-out the list of all tags and their models in the defaultHTMLElementModels definition.

Ready for Production?#

Quality and Stability Assessments#

With all the bug fixes, high test coverage and API stability, entering the beta stage means the Foundry release is now ready for production. Test coverage for the CSS processor and TRE is 100% (see native-html/core) repository. The react-native-render-html package has a test coverage above 64%, and will rise to above 90% by the end of the beta stage.

Migrating from v4 and v5#

Check-out our dedicated guide, and please don't hesitate to ask for help in our discord channel.

Final Notes#

Consulting

I am available for consulting, to help you incorporate this library in your project or migrate from legacy versions. Contact me via email or LinkedIn.

I want to thank Maxime Bertonnier, the original first contributor of react-native-render-html for letting me join the team and take over this awesome project. I want to also thank all those amazing alpha-testers from the community who have helped me refine the API over the last 7 months. And finally, kudos to Expensify, Inc. for hiring me to build the first iteration of the engine, which was originally motivated by the lack of whitespace collapsing support.