|
| 1 | +export const metadata = { |
| 2 | + title: "Customize Blocks Rendering", |
| 3 | + description: |
| 4 | + "Learn to add custom implementations of WordPress Blocks in your Faust.js project.", |
| 5 | +}; |
| 6 | + |
| 7 | +Whether you want to customize the look and feel of a [core WordPress block](https://wordpress.org/documentation/article/wordpress-block-editor/), implement a [custom block](https://developer.wordpress.org/block-editor/getting-started/tutorial/), or add a block from a third-party plugin, Faust.js makes it easy to render WP blocks to your liking. This guide will show you how to render a block in your Faust.js project. |
| 8 | + |
| 9 | +## 0. Prerequisites |
| 10 | + |
| 11 | +Ensure that you have completed the steps in our [Rendering blocks](/docs/how-to/rendering-blocks/) guide and have WordPress blocks rendering in your app successfully before proceeding with the steps below. |
| 12 | + |
| 13 | +## 1. Choose a block to override |
| 14 | + |
| 15 | +For this guide, we are going to override the core WordPress Code Block that renders a formatted block of code.. |
| 16 | + |
| 17 | + |
| 18 | + |
| 19 | +## 2. Review block features and settings |
| 20 | + |
| 21 | +Try using the block yourself in the WordPress block editor and get familiar with the features it has. Review the settings it has in the Settings Panel and change a few of them to see what they do. |
| 22 | + |
| 23 | +## 3. Inspect the block data via the GraphiQL IDE |
| 24 | + |
| 25 | +In the WordPress admin sidebar, go to `GraphQL` > `GraphiQL IDE` to open the GraphiQL IDE. If you're not familiar, this is a tool we can use for composing and testing GraphQL queries. This can help you understand what data your frontend app will receive when it executes a particular query. |
| 26 | + |
| 27 | +Paste the following query into the GraphiQL IDE and hit the `▶️` button to execute the query. Replace `/posts/testing` with the path to the blog post that contains the block you want to override. |
| 28 | + |
| 29 | +```graphql |
| 30 | +{ |
| 31 | + post(id: "/posts/testing", idType: URI) { |
| 32 | + editorBlocks { |
| 33 | + renderedHtml |
| 34 | + ... on CoreCode { |
| 35 | + attributes { |
| 36 | + borderColor |
| 37 | + backgroundColor |
| 38 | + content |
| 39 | + style |
| 40 | + textColor |
| 41 | + fontSize |
| 42 | + } |
| 43 | + } |
| 44 | + } |
| 45 | + } |
| 46 | +} |
| 47 | +``` |
| 48 | + |
| 49 | +> [!IMPORTANT] Block Type |
| 50 | +> Each block is given a unique GraphQL type. In this case, the Core Code block is a type of `CoreCode`. You can find the block type by looking at the `__typename` field. If you need help figuring this out, the following query will list all the blocks on a post with their types: |
| 51 | +> |
| 52 | +> ```graphql |
| 53 | +> { |
| 54 | +> post(id: "/posts/testing", idType: URI) { |
| 55 | +> editorBlocks { |
| 56 | +> __typename |
| 57 | +> type |
| 58 | +> name |
| 59 | +> } |
| 60 | +> } |
| 61 | +> } |
| 62 | +> ``` |
| 63 | +> |
| 64 | +> The `__typename` or `type` fields will show you the block type for each block on a post. You may find it useful to know this is generated from the `$block_type` argument in the block's [`registerBlockType`](https://developer.wordpress.org/reference/functions/register_block_type/) function; shown here in the `name` field. |
| 65 | +
|
| 66 | +The GraphiQL IDE will then display the data that was returned in the response to that query. It should look something like the example response below. |
| 67 | +
|
| 68 | +```json |
| 69 | +{ |
| 70 | + "data": { |
| 71 | + "post": { |
| 72 | + "editorBlocks": [ |
| 73 | + { |
| 74 | + "renderedHtml": "\n<pre class=\"wp-block-code has-border-color has-tertiary-background-color has-text-color has-background has-small-font-size\" style=\"border-color:#333333;border-width:2px;color:#333333\"><code>// Computes the nth Fibonacci number\nfunction fib(n) {\n var a = 0, b = 1, c;\n if (n < 3) {\n if (n < 0) return fib(-n);\n if (n === 0) return 0;\n return 1;\n }\n while (--n)\n c = a + b, a = b, b = c;\n return c;\n}</code></pre>\n", |
| 75 | + "attributes": { |
| 76 | + "borderColor": null, |
| 77 | + "backgroundColor": "tertiary", |
| 78 | + "content": "// Computes the nth Fibonacci number\nfunction fib(n) {\n var a = 0, b = 1, c;\n if (n < 3) {\n if (n < 0) return fib(-n);\n if (n === 0) return 0;\n return 1;\n }\n while (--n)\n c = a + b, a = b, b = c;\n return c;\n}", |
| 79 | + "style": "{\"color\":{\"text\":\"#333333\"},\"border\":{\"color\":\"#333333\",\"width\":\"2px\"}}", |
| 80 | + "textColor": null, |
| 81 | + "fontSize": "small" |
| 82 | + } |
| 83 | + } |
| 84 | + ] |
| 85 | + } |
| 86 | + } |
| 87 | +} |
| 88 | +``` |
| 89 | +
|
| 90 | +You can try modifying the query and running it again to see how the response changes. To learn about the fields that are available for that block, click the `Docs` link in GraphiQL, search for `CoreCode`, and click on `CoreCodeAttributes`. You will see the following: |
| 91 | + |
| 92 | + |
| 93 | + |
| 94 | +> [!INFO]- Styles |
| 95 | +> Notice that with some of the attributes like `backgroundColor`, the editor used the [`theme.json`](https://developer.wordpress.org/themes/global-settings-and-styles/introduction-to-theme-json/) palette slug name instead of the actual value, i.e., `tertiary`. It also stored the custom hardcoded styles in the `style` fields as a JSON string. |
| 96 | +
|
| 97 | +## 4. Define the block in your frontend app |
| 98 | + |
| 99 | +### A. Create a new block component |
| 100 | + |
| 101 | +Create a new `CoreCode.js` file inside of your `wp-blocks` folder that contains the following code. |
| 102 | + |
| 103 | +```js title="wp-blocks/CoreCode.js" |
| 104 | +export default function CoreCode(props) { |
| 105 | + console.log(props.attributes); |
| 106 | + return <div>CoreCode</div>; |
| 107 | +} |
| 108 | + |
| 109 | +CoreCode.displayName = "CoreCode"; |
| 110 | +``` |
| 111 | + |
| 112 | +For now, we're just rendering a placeholder `div` and logging the data to the console. This will be replaced with actual block code in subsequent steps. |
| 113 | + |
| 114 | +> [!INFO]- Context |
| 115 | +> The `displayName` property is defined here so that when we run the `editorBlocks` query in subsequent steps, the `__typename` field in the query will match this string. This is required so that the `WordPressBlocksViewer` component can resolve and render the block properly. |
| 116 | +
|
| 117 | +### B. Register the new block component |
| 118 | + |
| 119 | +Open the `index.js` file inside the `wp-blocks` folder and add your new block like this: |
| 120 | + |
| 121 | +```js title="wp-blocks/index.js" |
| 122 | +import { CoreBlocks } from "@faustwp/blocks"; |
| 123 | +import CoreCode from "./CoreCode"; |
| 124 | + |
| 125 | +export default { |
| 126 | + ...CoreBlocks, |
| 127 | + CoreCode: CoreCode, // [++code] |
| 128 | +}; |
| 129 | +``` |
| 130 | + |
| 131 | +> [!NOTE]- Context |
| 132 | +> This file will be used to import all of the blocks you're overriding and make them available as the default export, which will be passed into `WordPressBlocksProvider` in a subsequent step. |
| 133 | +> |
| 134 | +> We also define the name of the block as `CoreCode` (the property name to the left of the ":" character). This key must match the `__typename` value that the blocks has in the GraphQL schema. |
| 135 | +
|
| 136 | +### C. Create the block GraphQL fragment |
| 137 | + |
| 138 | +Create a fragment that describes the request block fields and attributes. First, `import { gql } from @apollo/client`. Then add the `CoreCode.fragments` code block, as shown below. Include all relevant fields for your implementation. |
| 139 | + |
| 140 | +```js {1, 08-24} title="wp-blocks/CoreCode.js " |
| 141 | +import { gql } from "@apollo/client"; |
| 142 | + |
| 143 | +export default function CoreCode({ attributes }) { |
| 144 | + console.log(attributes); |
| 145 | + return <div>CoreCode</div>; |
| 146 | +} |
| 147 | + |
| 148 | +CoreCode.fragments = { |
| 149 | + key: `CoreCodeBlockFragment`, |
| 150 | + entry: gql` |
| 151 | + fragment CoreCodeBlockFragment on CoreCode { |
| 152 | + attributes { |
| 153 | + borderColor |
| 154 | + backgroundColor |
| 155 | + content |
| 156 | + style |
| 157 | + textColor |
| 158 | + fontSize |
| 159 | + fontFamily |
| 160 | + cssClassName |
| 161 | + } |
| 162 | + } |
| 163 | + `, |
| 164 | +}; |
| 165 | +``` |
| 166 | + |
| 167 | +> [!INFO]- Context |
| 168 | +> Attaching the fragment as a property of the function component (`CoreCode.fragments = ...`) is a convention that Faust.js uses to [colocate fragments](https://www.apollographql.com/docs/react/data/fragments#colocating-fragments) with the components that use them. |
| 169 | +
|
| 170 | +### D. Include the block fragment in your page template queries |
| 171 | + |
| 172 | +Now, you can include the block fragment inside of the GraphQL query for your posts and pages. |
| 173 | + |
| 174 | +The example below shows a `/wp-templates/single.js` template. |
| 175 | + |
| 176 | +```js {2, 9, 18} title="wp-templates/single.js" |
| 177 | +import { gql } from "@apollo/client"; |
| 178 | +import blocks from "../wp-blocks"; |
| 179 | + |
| 180 | +export default function Page(props) { |
| 181 | + // ... |
| 182 | +} |
| 183 | + |
| 184 | +Page.query = gql` |
| 185 | + ${blocks.CoreCode.fragments.entry} |
| 186 | + query GetPost { |
| 187 | + post(id: $databaseId, idType: DATABASE_ID, asPreview: $asPreview) { |
| 188 | + editorBlocks { |
| 189 | + name |
| 190 | + __typename |
| 191 | + renderedHtml |
| 192 | + id: clientId |
| 193 | + parentClientId |
| 194 | + ...${blocks.CoreCode.fragments.key} |
| 195 | + } |
| 196 | + } |
| 197 | + } |
| 198 | +`; |
| 199 | +``` |
| 200 | + |
| 201 | +- `import blocks from "../wp-blocks";{:js}` imports the blocks we're overriding. |
| 202 | +- `${blocks.CoreCode.fragments.entry}{:js}` interpolates the `CoreCode` GraphQL fragment into the page query. |
| 203 | +- `...${blocks.CoreCode.fragments.key}{:js}` tells GraphQL to insert the `CoreCode` GraphQL fragment inside of the `editorBlocks { ... }{:gql}` block. |
| 204 | + |
| 205 | +With this code in place, when the GraphQL query for the single page template is executed, the response will now include data for the `CoreCode` block. You can follow this pattern in your other page templates to query for block data within them, as well. |
| 206 | + |
| 207 | +### E. Verify the block data in the frontend app |
| 208 | + |
| 209 | +If you navigate to the page that contains this block, you should be able to inspect the properties in the console and see your block attributes that were logged by `console.log(props.attributes)` in `CoreCode.js`. |
| 210 | + |
| 211 | + |
| 212 | + |
| 213 | +## 5. Implement the block |
| 214 | + |
| 215 | +You now have the block data available in your frontend app and are ready to implement the block's structure, styling, and functionality. |
| 216 | + |
| 217 | +> [!IMPORTANT] Options |
| 218 | +> You've done the hard work of getting the data into your app and integrating with the `@faustwp/blocks` package. How you implement this block is completely up to you. You can use any React component library, CSS-in-JS solution, or custom styles you like. |
| 219 | +> |
| 220 | +> Keep in mind, If your implementation deviates from the WordPress implementation, your content editors might find this confusing. For this guide, we'll show you a basic implementation of the `CoreCode` block. |
| 221 | +
|
| 222 | +Replace the `CoreCode` component with the following code. This code will render the block in the frontend app with a similar structure to the WordPress block editor. |
| 223 | + |
| 224 | +```js title="wp-blocks/CoreCode.js" |
| 225 | +import { gql } from "@apollo/client"; |
| 226 | + |
| 227 | +export default function CoreCode({ attributes }) { |
| 228 | + return ( |
| 229 | + <pre className={attributes?.cssClassName}> |
| 230 | + <code>{`${attributes?.content}`}</code> |
| 231 | + </pre> |
| 232 | + ); |
| 233 | +} |
| 234 | +
|
| 235 | +CoreCode.fragments = { |
| 236 | + key: `CoreCodeBlockFragment`, |
| 237 | + entry: gql` |
| 238 | + fragment CoreCodeBlockFragment on CoreCode { |
| 239 | + attributes { |
| 240 | + content |
| 241 | + cssClassName |
| 242 | + } |
| 243 | + } |
| 244 | + `, |
| 245 | +}; |
| 246 | +
|
| 247 | +CoreCode.displayName = "CoreCode"; |
| 248 | +``` |
| 249 | +
|
| 250 | +## 6. Implement the block styles |
| 251 | +
|
| 252 | +Let's add some code highlighting to this component to really make it pop. |
| 253 | +
|
| 254 | +> [!IMPORTANT] Options |
| 255 | +> We're going to leave the specific styling up you. You can choose to statically style in here or you could leverage the other available `attributes` to style the block dynamically from the WordPress block editor. Faust does [support styling](/docs/reference/get-styles/) blocks using your WordPress Block theme styles. |
| 256 | +
|
| 257 | +### A. Install the `highlight.js` package by running the following command: |
| 258 | +
|
| 259 | +```bash |
| 260 | +npm add highlight.js |
| 261 | +``` |
| 262 | +
|
| 263 | +### B. Import the `highlight.js` package in the `_app.js` file: |
| 264 | +
|
| 265 | +```js title="_app.js" |
| 266 | +// THIS IS VERY IMPORTANT. |
| 267 | +// In Next.js, put this in your _app.js file |
| 268 | +import "highlight.js/styles/github.css"; |
| 269 | +``` |
| 270 | +
|
| 271 | +### C. Update the Component |
| 272 | +
|
| 273 | +Update `CoreCode.js` with the code bellow to add code highlighting: |
| 274 | +
|
| 275 | +```js title="wp-blocks/CoreCode.js" |
| 276 | +import { gql } from "@apollo/client"; |
| 277 | +import { useEffect, useRef } from "react"; |
| 278 | +import hljs from "highlight.js"; |
| 279 | +
|
| 280 | +export default function CoreCode({ attributes }) { |
| 281 | + // Create a ref for the <code> element |
| 282 | + const codeRef = useRef(null); |
| 283 | +
|
| 284 | + useEffect(() => { |
| 285 | + // Once the component has rendered, apply highlight.js to the code element |
| 286 | + if (codeRef.current) { |
| 287 | + hljs.highlightElement(codeRef.current); |
| 288 | + } |
| 289 | + }, [attributes?.content]); |
| 290 | +
|
| 291 | + return ( |
| 292 | + <pre className={attributes?.cssClassName}> |
| 293 | + <code |
| 294 | + ref={codeRef} |
| 295 | + dangerouslySetInnerHTML={{ __html: attributes?.content }} |
| 296 | + /> |
| 297 | + </pre> |
| 298 | + ); |
| 299 | +} |
| 300 | +
|
| 301 | +CoreCode.fragments = { |
| 302 | + key: `CoreCodeBlockFragment`, |
| 303 | + entry: gql` |
| 304 | + fragment CoreCodeBlockFragment on CoreCode { |
| 305 | + attributes { |
| 306 | + content |
| 307 | + cssClassName |
| 308 | + } |
| 309 | + } |
| 310 | + `, |
| 311 | +}; |
| 312 | +
|
| 313 | +CoreCode.displayName = "CoreCode"; |
| 314 | +``` |
| 315 | +
|
| 316 | +## 7. Wrapping up... |
| 317 | +
|
| 318 | +Navigate to a page that contains the `CoreCode` block and you should see the code block with syntax highlighting applied. |
| 319 | +
|
| 320 | + |
0 commit comments