Skip to content

feat: add jsonToHtmlAsync for async customElementTypes support#100

Draft
glassdimlygr wants to merge 1 commit intocontentstack:masterfrom
glassdimlygr:feat/async-json-to-html
Draft

feat: add jsonToHtmlAsync for async customElementTypes support#100
glassdimlygr wants to merge 1 commit intocontentstack:masterfrom
glassdimlygr:feat/async-json-to-html

Conversation

@glassdimlygr
Copy link
Copy Markdown

Summary

Adds jsonToHtmlAsync — an async variant of jsonToHtml that allows customElementTypes handlers to return Promise<string> in addition to string.

Motivation

Frameworks like Next.js use dynamic imports (next/dynamic, React.lazy) for code-splitting. When rendering JSON RTE content that contains embedded component references, the component modules may not be available synchronously. Currently, jsonToHtml requires all handlers to return strings synchronously, making it impossible to await import() a component before rendering it.

This is a common issue for any consumer that:

  • Uses dynamic imports / code-splitting for components referenced in RTE content
  • Needs to fetch data or resolve resources before rendering a custom element type
  • Runs in an environment where component resolution is inherently async

Changes

  • toRedactorAsync (exported as jsonToHtmlAsync): Async version of toRedactor. Children are resolved concurrently via Promise.all. Handler results are awaited, so both sync (string) and async (Promise<string>) return values work.
  • IJsonToHtmlAsyncElementTags: Handler signature where return type is string | Promise<string>
  • IJsonToHtmlAsyncOptions: Options interface using the async element tags type
  • No changes to existing code: jsonToHtml / toRedactor are completely untouched. This is purely additive.

Usage

import { jsonToHtmlAsync } from '@contentstack/json-rte-serializer';

const html = await jsonToHtmlAsync(jsonRteContent, {
  customElementTypes: {
    // Sync handlers still work
    'p': (attrs, child) => `<p${attrs}>${child}</p>`,
    
    // Async handlers now supported
    'reference': async (attrs, child, jsonBlock) => {
      const Component = await import(`./components/${jsonBlock.attrs.type}`);
      return renderToStaticMarkup(<Component {...jsonBlock.attrs} />);
    },
  },
});

Tests

  • Full parity tests: toRedactorAsync produces identical output to toRedactor for all existing test cases (headings, tables, formatting, lists, links, custom handlers, text wrappers)
  • Async-specific tests: async handlers, mixed sync/async handlers, concurrent resolution with correct output ordering, nested children resolved before handler, error propagation

@glassdimlygr glassdimlygr force-pushed the feat/async-json-to-html branch from 6cbbd2d to c2200a4 Compare May 7, 2026 15:14
Add toRedactorAsync (exported as jsonToHtmlAsync) that supports
customElementTypes handlers returning string | Promise<string>.
Enables dynamic component resolution (e.g. await import()) before
serialization. Children are resolved via Promise.all concurrently.

Refactors shared logic (text processing, attr building, element
node processing) into toRedactorHelpers.ts so both sync and async
versions are thin recursive shells with no duplicated code.

The existing sync jsonToHtml behavior is unchanged.

New types: IJsonToHtmlAsyncElementTags, IJsonToHtmlAsyncOptions.
@glassdimlygr glassdimlygr force-pushed the feat/async-json-to-html branch from c2200a4 to 44da5fa Compare May 7, 2026 15:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant