Skip to content

Commit

Permalink
feat: add support for overloading html-react-parser options (#62)
Browse files Browse the repository at this point in the history
* feat:  Add doc to overload html-react-parser options

* fix: format

* fix: formating in overloading-wp-behaviour

* fix: add condition to use only one config at a time

* Implement codesplitting and use types from package

* docs: update doc and include file paths

* docs: add changeset

* refactor: change sequnce of blocks

* docs: improve flow between html-react-parser and blocks

* docs: update config-api with `parserOptions` reference

---------

Co-authored-by: Dovid Levine <[email protected]>
  • Loading branch information
BhumikP and justlevine authored Feb 18, 2025
1 parent 42b3069 commit 55bd683
Show file tree
Hide file tree
Showing 8 changed files with 2,129 additions and 1,099 deletions.
6 changes: 6 additions & 0 deletions .changeset/sixty-hotels-stare.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@snapwp/core": minor
"@snapwp/next": minor
---

feat: Add support for overloading html-react-parser options
45 changes: 33 additions & 12 deletions docs/config-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,16 @@ SnapWP uses the following `.env` variables to configure your Next.js app.
> We recommend copying the `.env` variables from the SnapWP Helper plugin settings screen and pasting them into your `.env` file, then modifying them as needed.
> See the [Getting Started](getting-started.md#backend-setup) guide for more information.
| Variable | Required | Default Value | Description | Maps to Config Variable |
| --------------------------------------- | -------- | ---------------------------------------- | --------------------------------------------------------------------------------- | ----------------------- |
| `NEXT_PUBLIC_URL` | Yes | | The URL of the Next.js site. | `nextUrl` |
| `NEXT_PUBLIC_WORDPRESS_URL` | Yes | | The WordPress frontend domain URL. | `homeUrl` |
| `INTROSPECTION_TOKEN` | Yes | | Token used for authenticating GraphQL introspection queries with GraphQL Codegen. | |
| `NEXT_PUBLIC_GRAPHQL_ENDPOINT` | No | `index.php?graphql` | The relative path to the WordPress GraphQL endpoint. | `graphqlEndpoint` |
| `NEXT_PUBLIC_WORDPRESS_UPLOADS_PATH` | No | `/wp-content/uploads` | The relative path to the WordPress uploads directory. | `uploadsDirectory` |
| `NEXT_PUBLIC_WORDPRESS_REST_URL_PREFIX` | No | `/wp-json` | The WordPress REST API URL prefix. | `restUrlPrefix` |
| `NEXT_PUBLIC_USE_CORS_PROXY` | No | `process.env.NODE_ENV === 'development'` | Whether to use a CORS proxy for the WordPress API. | `useCorsProxy` |
| `NEXT_PUBLIC_CORS_PROXY_PREFIX` | No | `/proxy` | The prefix of the CORS proxy. | `corsProxyPrefix` |
| Variable | Required | Default Value | Description | Available via `getConfig() |
| --------------------------------------- | -------- | ---------------------------------------- | --------------------------------------------------------------------------------- | -------------------------- |
| `NEXT_PUBLIC_URL` | Yes | | The URL of the Next.js site. | `nextUrl` |
| `NEXT_PUBLIC_WORDPRESS_URL` | Yes | | The WordPress frontend domain URL. | `homeUrl` |
| `INTROSPECTION_TOKEN` | Yes | | Token used for authenticating GraphQL introspection queries with GraphQL Codegen. | N/A |
| `NEXT_PUBLIC_GRAPHQL_ENDPOINT` | No | `index.php?graphql` | The relative path to the WordPress GraphQL endpoint. | `graphqlEndpoint` |
| `NEXT_PUBLIC_WORDPRESS_UPLOADS_PATH` | No | `/wp-content/uploads` | The relative path to the WordPress uploads directory. | `uploadsDirectory` |
| `NEXT_PUBLIC_WORDPRESS_REST_URL_PREFIX` | No | `/wp-json` | The WordPress REST API URL prefix. | `restUrlPrefix` |
| `NEXT_PUBLIC_USE_CORS_PROXY` | No | `process.env.NODE_ENV === 'development'` | Whether to use a CORS proxy for the WordPress API. | `useCorsProxy` |
| `NEXT_PUBLIC_CORS_PROXY_PREFIX` | No | `/proxy` | The prefix of the CORS proxy. | `corsProxyPrefix` |

Additionally, if you are running a local development environment without a valid SSL certificate, you can set the following environment variable:

Expand All @@ -36,7 +36,28 @@ NODE_TLS_REJECT_UNAUTHORIZED=0

SnapWP allows you to define configurations in a `snapwp.config.ts` (or `.mjs` or `.js`) file at the root of your application (alongside your `package.json` and `next.config.ts`).

@todo: Add example once we specify what can be configured in `snapwp.config.js|mjs|ts`.
Example `snapwp.config.ts`:

```ts
``tsx
// snapwp.config.ts

import type { SnapWPConfig } from '@snapwp/core/config';

const config: SnapWPConfig = {
/* your custom configuration */
};

export default config;
```

Here are the available configuration options:

| Property | Type | Default Value | Description |
| --------------- | ------------------------ | --------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- |
| `parserOptions` | `HTMLReactParserOptions` | [defaultOptions](../packages/next/src/react-parser/options.tsx) | The default options for the `html-react-parser` library.<br />[Learn more](./overloading-wp-behavior.md#2-pass-customparseroptions-to-overload) |

Config values are available via their respective keys in the `getConfig()` function.

## Integration with `next.config.ts`

Expand All @@ -60,5 +81,5 @@ You can access the configuration values in your application code using the `getC
import { getConfig } from '@snapwp/core/config';

// Or any other valid configuration property.
const { nextUrl, homeUrl } = getConfig();
const { nextUrl, homeUrl, parserOptions } = getConfig();
```
138 changes: 112 additions & 26 deletions docs/overloading-wp-behavior.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,116 @@ SnapWP takes an additive approach to headless WordPress development, where WordP

This tutorial explains how to overload default WordPress behavior using SnapWP, focusing on customizing blocks, creating custom frontend routes, and `html-react-parser` component mapping.

## Default HTML Rendering with `Parse` and [`html-react-parser`](https://www.npmjs.com/package/html-react-parser)

By default, SnapWP uses WordPress as the source of truth for both content _and_ markup. The `Parse` component in SnapWP allows you to convert raw HTML strings into React components, replacing specific elements with custom Next.js components.

This component can be used anywhere in your Next.js application to progressively enhance the rendering of HTML content.

Here's a basic example:

```tsx
import React from 'react';
import { Parse } from '@snapwp/next';
import { type BlockData } from '@snapwp/core';

/**
* Renders the default block.
* @param props - The props for the component.
* @param props.renderedHtml - The rendered HTML.
* @return The rendered default block.
*/
export default function Default( { renderedHtml }: BlockData ) {
return <Parse html={ renderedHtml || '' } />;
}
```

### Overloading the HTML-to-React component mapping

SnapWP allows you to extend and overload the default `HTMLReactParserOptions` used by the `Parse` component. This is useful for customizing the rendering of specific HTML elements, such as images, links, or other custom elements.

### 1. Creating a custom `HTMLReactParserOptions` object`

Create a new Custom Component to overload the [default html-react-parser options](../packages/next/src/react-parser/options.tsx).

```tsx
// MyCustomReactParser.tsx

import React from 'react';
import {
Element,
type DOMNode,
type HTMLReactParserOptions,
} from 'html-react-parser';
import { CustomImage } from '@/CustomImage'; // custom image component defined in your project
/**
* Using typescript type guard to check if a DOMNode is an Element.
* Ref : https://github.com/remarkablemark/html-react-parser/issues/221#issuecomment-784073240
* @param domNode The DOM element
* @return parser options
*/
const isElement = ( domNode: DOMNode ): domNode is Element => {
const isTag = domNode.type === 'tag';
const hasAttributes = ( domNode as Element ).attribs !== undefined;

return isTag && hasAttributes;
};

export const customParserOptions: HTMLReactParserOptions = {
replace: ( domNode ) => {
if ( isElement( domNode ) ) {
const { attribs, children, name, type } = domNode;
const { class: className, style, ...attributes } = attribs;
const { href } = attribs;

if ( type === 'tag' && name === 'img' ) {
return (
<CustomImage
{ ...attributes }
src={ attribs.src }
alt={ attribs.alt || '' }
height={ height }
width={ width }
className={ className }
style={ styleObject }
image={ imageAttributes }
//adding custom id
id="overriding id"
/>
);
}

return undefined;
}

return undefined;
},
};
```

### 2. Pass customParserOptions to overload

```tsx
// snapwp.config.ts

import type { SnapWPConfig } from '@snapwp/core/config';
import { customParserOptions } from './src/MyCustomReactParser';

const config: SnapWPConfig = {
/* passing custom options to overload default react-parser options */
parserOptions: customParserOptions,
};

export default config;
```

---

## Overloading Blocks

SnapWP allows you to customize the rendering of individual WordPress blocks. This is useful for modifying the default appearance or behavior of a block to better suit your design.
In addition to overloading the underling rendering of default HTML content, SnapWP allows you to target the rendering of individual WordPress blocks.

The `@snapwp/blocks` package provides a large and growing number of [Block Components](https://github.com/rtCamp/snapwp/blob/develop/packages/blocks/src/blocks/index.ts), which you can in turn overload with custom components or unregister to fallback to rendering with [html-react-parser](#1-creating-a-custom-htmlreactparseroptions-object).

> [!TIP]
> When using WordPress [Block Themes](https://wordpress.org/documentation/article/block-themes/), pretty much _everything_ is a block.
Expand Down Expand Up @@ -65,7 +172,10 @@ export default function Page() {
}
```

Now, whenever a `core/paragraph` block is encountered, your `MyCustomParagraph` component will be used to render it. Any other blocks will use the default rendering unless you provide a custom component in `blockDefinitions`. Setting the value to `null` will use the [default block rendering](#default-block-rendering).
Now, whenever a `core/paragraph` block is encountered, your `MyCustomParagraph` component will be used to render it. Any other blocks will use the default rendering unless you provide a custom component in `blockDefinitions`.

> [!TIP]
> Setting the block definition value to `null` will use the [default block rendering](#default-html-rendering-with-parse-and-html-react-parser).
---

Expand Down Expand Up @@ -178,27 +288,3 @@ export default function Page() {

- **Local Styles**: Use CSS Modules (as shown with `styles.module.css`) or any other CSS-in-JS solution for component-specific styling.
- **Global and Theme Styles**: Classes like `wp-block-heading`, `has-text-align-center`, and `has-x-large-font-size` (likely from your WordPress `theme.json` and/or global CSS) are automatically available.

## Default Block Rendering

This tutorial covers how SnapWP uses the `Parse` component to render HTML content. The `Parse` component converts HTML strings into React components, which is essential for displaying default WordPress blocks.

### Using `Parse`

The `Parse` component takes an HTML string (`renderedHtml`) and converts it into React components. Here's a basic example:

```tsx
import React from 'react';
import { Parse } from '@snapwp/next';
import { type BlockData } from '@snapwp/core';

/**
* Renders the default block.
* @param props - The props for the component.
* @param props.renderedHtml - The rendered HTML.
* @return The rendered default block.
*/
export default function Default( { renderedHtml }: BlockData ) {
return <Parse html={ renderedHtml || '' } />;
}
```
Loading

0 comments on commit 55bd683

Please sign in to comment.