Skip to content

Commit ea4b31d

Browse files
authored
Merge pull request #254 from wpengine/docs/custom-blocks
docs: custom blocks
2 parents 79b6f49 + d31666a commit ea4b31d

File tree

10 files changed

+385
-3
lines changed

10 files changed

+385
-3
lines changed

next.config.mjs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,16 @@ const nextConfig = {
2525
},
2626
redirects() {
2727
return [
28+
{
29+
source: "/guide/how-to-create-a-block-from-a-third-party-plugin",
30+
destination: "/docs/how-to/custom-blocks/",
31+
permanent: true,
32+
},
33+
{
34+
source: "/guide/how-to-create-a-block-from-the-wordpress-blocks-list",
35+
destination: "/docs/how-to/custom-blocks/",
36+
permanent: true,
37+
},
2838
{
2939
source: "/guide/how-to-migrate-from-legacy-faust",
3040
destination: "/docs/how-to/migrate-from-legacy-faust/",
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
export const metadata = {
2+
title: "Blocks FAQ",
3+
description: "Frequently asked questions about Blocks.",
4+
};
5+
6+
## _"What if the block is missing some attributes?"_
7+
8+
If the block does not have any attributes or has only a few of them declared in the `block.json` for that block (you can view the `block.json` file for the block at https://github.com/WordPress/gutenberg/blob/trunk/packages/block-library/), you can still try to extend the block API by declaring additional attributes for that block.
9+
10+
Follow the [filters reference guide](reference/plugin-filters) to create a block that uses the `additional_block_attributes` property. The attributes will then be available to query from that block.
11+
12+
```php
13+
class CoreCode extends WPGraphQL\ContentBlocks\Blocks\Block
14+
{
15+
protected ?array $additional_block_attributes = array(
16+
// Write your attributes here
17+
);
18+
}
19+
```
20+
21+
> [!NOTE]
22+
> If you include those extensions in a custom plugin, your Headless Website application is dependent on the inclusion of this plugin. You need to make sure you bundle them together; otherwise, the queries you perform in the application will fail.
23+
24+
## _"Can I style the block differently?"_"
25+
26+
Yes, you can style the block in many ways, choosing to ignore some of the attributes altogether. You can also use an external React Library to style the block, such as Material UI or ChakraUI.
27+
28+
Bear in mind that this will almost always result in a degraded user editing experience, as the styles in the WordPress block editor view won't match the styles of the rendered headless page.
29+
30+
## _"What if the block contains custom JavaScript assets?"_
31+
32+
Some Core blocks include JavaScript assets that are injected into the WordPress page so they can run in the front view. Many [dynamic blocks](https://developer.wordpress.org/block-editor/how-to-guides/block-tutorial/creating-dynamic-blocks/) use this functionality to include user interactivity. Since React is not bundled as a dependency in the browser, the client-side code that WordPress ships to the frontend in traditional WordPress sites typically consists of plain JavaScript or jQuery.
33+
34+
To review and handle bundled JavaScript assets, consider these options:
35+
36+
- **Include them in your code**: This is not recommended, as React does not play well with plain JavaScript and jQuery, which may lead to compatibility issues.
37+
- **Rewrite them as React components**: You can attempt to rewrite the code in React. If the bundled code can be understood and rewritten with low effort, then this could be a viable approach.
38+
- **Use an equivalent React Component from a library**: A simpler alternative is to find a compatible React package and use it instead of replicating the block's interactivity. This can often free the developer from implementing the functionality from scratch.
39+
40+
Inevitably, this is a common challenge when using Blocks in a Headless Website setup, so it's up to you to weigh the pros and cons of each approach.
47.1 KB
Loading
64.7 KB
Loading
101 KB
Loading
48.3 KB
Loading
Lines changed: 320 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,320 @@
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+
![WordPress Code block in the Block editor](./images/core-code-block.png)
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 &lt; 3) {\n if (n &lt; 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 &lt; 3) {\n if (n &lt; 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+
![Core code block attributes](./images/core-code-block-ide-attributes.png)
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+
![Browser Console showing logged object](./images/block-console.png)
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+
![](./images/core-code-block-decoupled-preview.png)

0 commit comments

Comments
 (0)