UNPKG

@jverneaut/html-to-gutenberg

Version:

A Webpack plugin and CLI that generates dynamic Gutenberg blocks built with React and either PHP or Twig, from a single HTML file.

430 lines (336 loc) 14.8 kB
![Tests Status](https://github.com/jverneaut/html-to-gutenberg/actions/workflows/test.yml/badge.svg) ![GitHub Release](https://img.shields.io/github/v/release/jverneaut/html-to-gutenberg) # HTML To Gutenberg A Webpack plugin and CLI that generates dynamic Gutenberg blocks built with React and either PHP or Twig, from a single HTML file. Instead of manually coding both React and PHP/Twig components, simply write an HTML file with some special attributes, and this plugin will automatically generate all necessary files for you: - **React-based** `edit.js` for the WordPress editor - **Frontend rendering with** `render.php` by default - Or use **Twig-based** `render.twig` if you prefer (recommended!) - **block.json** with automatically defined attributes - **index.js** to register the block type https://github.com/user-attachments/assets/d9ee9410-9529-4664-a7a4-82b0eb1ad306 This plugin now **defaults to PHP rendering**, making it more compatible with typical WordPress projects. However, if you're working with **Timber, Bedrock**, or just want a more **frontend-friendly templating experience**, you can enable Twig rendering by setting: ```js new HTMLToGutenbergPlugin({ ... engine: "twig", // Enables render.twig instead of render.php }); ``` 👉 Personally, **I highly recommend Twig** for rendering blocks. It feels closer to HTML, is easier to read and write, and is much nicer to maintain—especially if you're a front-end developer. > For a full Twig-based Gutenberg-ready setup, check out my other project [gutenberg-tailwindcss-bedrock-timber-twig](https://github.com/jverneaut/gutenberg-tailwindcss-bedrock-timber-twig/) ## Features - **Automatic Gutenberg block generation** from simple HTML - **Use attributes** (`data-attribute="title"`, etc.) to define editable fields - **Supports RichText and MediaUpload**: - Non-`<img>` elements with `data-attribute="something"` **Editable RichText** - `<img>` elements with `data-attribute="something"` **Image selection via MediaUpload** - **Fully automates block.json attributes creation** - **Add additional styles** via the `data-styles="primary secondary"` attribute on the root block element - **InnerBlocks handling** with `<blocks>` and `<block>` elements - **Automatic `style` strings to JS objects conversion** for `edit.js` - **Supports both PHP and Twig** for frontend rendering ## 📦 Installation ```sh npm install --save-dev @jverneaut/html-to-gutenberg npm install --save-dev @10up/block-components # Required for the <Image /> edit.js component ``` ## ⚙️ Webpack Configuration This plugin is designed to work with Webpack. Here's how to integrate it: ```js // webpack.config.js import HTMLToGutenbergPlugin from "@jverneaut/html-to-gutenberg"; export default { plugins: [ new HTMLToGutenbergPlugin({ inputDirectory: "./blocks", // Your source HTML files outputDirectory: "./generated-blocks", // Where generated Gutenberg blocks will be placed blocksPrefix: "custom", // Blocks namespace // Optional: switch to Twig-based rendering (recommended) engine: "twig", // either 'php' (default) or 'twig' removeDeletedBlocks: true, // Deletes blocks in outputDirectory that no longer have a corresponding source HTML file (default: false) }), ], }; ``` 📌 This setup will: - Scan `blocks/` for `.html` files - Generate Gutenberg blocks inside `generated-blocks/` > **Note: These blocks still need to be bundled and registered with WordPress before use.** ### Minimal full setup example using Webpack and PHP #### Webpack configuration ```js // webpack.config.js import HTMLToGutenbergPlugin from "@jverneaut/html-to-gutenberg"; import GutenbergWebpackPlugin from "@jverneaut/gutenberg-webpack-plugin"; export default { mode: "development", entry: "./index.js", // Your main entry point for non-Gutenberg scripts plugins: [ new HTMLToGutenbergPlugin({ inputDirectory: "./blocks", // Source folder for your custom blocks HTML outputDirectory: "./generated-blocks", // Where transformed blocks will be output }), new GutenbergWebpackPlugin("./generated-blocks"), // Registers the generated blocks ], module: { rules: [ { test: /\.(js|jsx)$/, exclude: /node_modules/, use: { loader: "babel-loader", options: { presets: ["@babel/preset-env", "@babel/preset-react"], }, }, }, ], }, }; ``` #### Registering Blocks in PHP Add the following to your theme’s `functions.php` file or a custom plugin to automatically register the generated blocks with WordPress: ```php add_action('init', function () { $blocks_path = get_stylesheet_directory() . '/dist/blocks'; $blocks = array_filter(glob($blocks_path . '/**/*'), 'is_dir'); foreach ($blocks as $block) { register_block_type($block); } }); ``` This is a lightweight, automatic setup. Feel free to adapt it to your specific workflow — other approaches might suit your project better. ## CLI ```sh Usage: npx @jverneaut/html-to-gutenberg [options] A Webpack plugin and CLI that generates dynamic Gutenberg blocks built with React and either PHP or Twig, from a single HTML file. Options: -V, --version output the version number -i, --input <path> HTML blocks input path (default: ".") -o, --output <path> Gutenberg blocks output path -p, --prefix <type> Blocks namespace (default: "custom") -e, --engine <type> Engine (either "php", "twig" or "all") (default: "php") -w, --watch Watch the input directory for changes and regenerate blocks -h, --help display help for command ``` ## 🚀 Quick Start (Example Project) **An example** is available in the `example/` folder. You can test it by running: ```sh cd example npm install npm run dev ``` You can then edit `demo-block.html` and see the generated block inside `example/generated/demo-block`. It is setup to output both `render.php` as well as `render.twig` for demonstration purposes. ## Usage > _Documentation writing in progress..._ > > In the meantime, you can explore a variety of examples in the [\_\_tests\_\_/fixtures/processable](https://github.com/jverneaut/html-to-gutenberg/tree/main/__tests__/fixtures/processable) directory. These include both Twig and PHP rendering examples to help you understand how to generate blocks using HTML with this plugin. > > As long as the input HTML is valid, the plugin should correctly parse it and generate the corresponding translated JS/Twig/PHP files. If you come across any edge cases or manage to break the plugin in unexpected ways, feel free to open an issue. ## Example ### 📝 Input HTML ```html <section class="container" data-styles="primary secondary" data-parent="custom/parent-block" > <div class="grid grid-cols-12 px-8 gap-x-6"> <div class="col-span-6 flex flex-col justify-center"> <h1 data-attribute="title">Hello, <strong>Gutenberg!</strong></h1> <p data-attribute="content"> Lorem ipsum dolor sit amet consectetur, adipisicing elit. Veritatis facere deleniti nam magni. Aspernatur, obcaecati fuga. </p> </div> <div class="col-span-6"> <img data-attribute="image" src="w-full aspect-video rounded-lg" /> </div> <div class="col-span-12 flex gap-x-6"> <blocks templateLock> <block name="custom/child-block" title="Title 1" number="42"></block> <block name="custom/child-block"> <attribute name="title"><strong>Title 2</strong></attribute> <attribute name="number">42</attribute> </block> <block name="custom/other-child-block"> <attribute name="title" value="Title 3"></attribute> <attribute name="number" value="42"></attribute> </block> </blocks> </div> </div> </section> ``` ### 🔄 Generated files `edit.js` **(for Gutenberg editor)** ```jsx import { useBlockProps, RichText, MediaUpload, InnerBlocks, } from "@wordpress/block-editor"; import { Image } from "@10up/block-components"; export default ({ attributes, setAttributes }) => { return ( <section {...useBlockProps({ className: "container" })}> <div className="grid grid-cols-12 px-8 gap-x-6"> <div className="col-span-6 flex flex-col justify-center"> <RichText tagName="h1" value={attributes.title} onChange={(title) => setAttributes({ title })} ></RichText> <RichText tagName="p" value={attributes.content} onChange={(content) => setAttributes({ content })} ></RichText> </div> <div className="col-span-6"> <MediaUpload src="w-full aspect-video rounded-lg" value={attributes.image} onSelect={(image) => setAttributes({ image: image.id })} render={({ open }) => ( <Image style={{ cursor: "pointer" }} onClick={open} id={attributes.image} onSelect={(image) => setAttributes({ image: image.id })} /> )} ></MediaUpload> </div> <div className="col-span-12 flex gap-x-6"> <InnerBlocks allowedBlocks={["custom/child-block", "custom/other-child-block"]} template={[ ["custom/child-block", { title: "Title 1", number: 42 }], [ "custom/child-block", { title: "<strong>Title 2</strong>", number: 42 }, ], ["custom/other-child-block", { title: "Title 3", number: 42 }], ]} templateLock ></InnerBlocks> </div> </div> </section> ); }; ``` `render.php` **(for frontend rendering)** ```php <?php $image = wp_get_attachment_image_src($attributes['image'], 'full'); $image_alt = get_post_meta($attributes['image'], '_wp_attachment_image_alt', true); ?> <section <?php echo get_block_wrapper_attributes(['class' => 'container']); ?>> <div class="grid grid-cols-12 px-8 gap-x-6"> <div class="col-span-6 flex flex-col justify-center"> <h1><?php echo wp_kses_post($attributes['title'] ?? ''); ?></h1> <p><?php echo wp_kses_post($attributes['content'] ?? ''); ?></p> </div> <div class="col-span-6"> <img src="<?php echo esc_url($image[0]); ?>" alt="<?php echo esc_attr($image_alt); ?>" /> </div> <div class="col-span-12 flex gap-x-6"> <?php echo $content; ?> </div> </div> </section> ``` `render.twig` **(for frontend rendering)** ```twig <section {{ wrapper_attributes({ class: 'container' }) }} > <div class="grid grid-cols-12 px-8 gap-x-6"> <div class="col-span-6 flex flex-col justify-center"> <h1>{{ attributes.title }}</h1> <p> {{ attributes.content }} </p> </div> <div class="col-span-6"> <img src="{{ get_image(attributes.image).src }}" alt="{{ get_image(attributes.image).alt }}" /> </div> <div class="col-span-12 flex gap-x-6"> {{ content }} </div> </div> </section> ``` `block.json` **(auto-generated block metadata)** ```json { "name": "custom/demo-block", "title": "Demo Block", "textdomain": "demo-block", "$schema": "https://schemas.wp.org/trunk/block.json", "apiVersion": 3, "version": "0.1.0", "category": "theme", "example": {}, "styles": [ { "name": "primary", "label": "Primary", "isDefault": true }, { "name": "secondary", "label": "Secondary" } ], "parent": ["custom/parent-block"], "attributes": { "align": { "type": "string", "default": "full" }, "title": { "type": "string", "default": "Hello, <strong>Gutenberg!</strong>" }, "content": { "type": "string", "default": "Lorem ipsum dolor sit amet consectetur, adipisicing elit. Veritatis facere deleniti nam magni. Aspernatur, obcaecati fuga." }, "image": { "type": "integer" } }, "supports": { "html": false, "align": ["full"] }, "editorScript": "file:./index.js", "render": "file:./render.twig", // if using Twig engine "render": "file:./render.php" // if using PHP engine } ``` `index.js` **(register the block type)** ```js import { registerBlockType } from "@wordpress/blocks"; import { InnerBlocks } from "@wordpress/block-editor"; import Edit from "./edit.js"; import metadata from "./block.json"; registerBlockType(metadata.name, { edit: Edit, save: () => <InnerBlocks.Content />, }); ``` ## FAQ ### Can I add more fields beyond RichText and MediaUpload? Right now, the plugin auto-generates fields for text and images as well as InnerBlocks. Support for additional fields may come later based on my experience building production sites with this tool. ### Should generated blocks be versioned, or should the source HTML file be? That depends on your strategy: - **Versioning the source HTML files only:** You treat the `.html` files as **the single source of truth**, and let this plugin regenerate the entire block every time. This is ideal when using this plugin as a **build tool**, fully automating block creation and updates. You don’t version the generated files—just the `.html`. - **Versioning the generated files only:** You use the HTML input files as a **block scaffolding tool**, generate the files once, delete or ignore the `.html` files, and then **manually edit the generated React/Twig/PHP code**. This approach gives you more control over customization at the cost of automation. 👉 Choose the one that fits your workflow best—**automated generation** vs **manual control**. ### Why would I choose Twig instead of PHP for rendering blocks? Personally, I find **Twig much friendlier** for templating. It’s closer to HTML, which makes it easier to read, write, and maintain—especially for front-end developers. On top of that, **writing code generation for Twig is simpler** than for PHP. Since the syntax is less verbose and more structured, it’s a better fit for the kind of programmatic output this plugin produces. ### How do I use the Twig-generated blocks inside my project? Check out [gutenberg-tailwindcss-bedrock-timber-twig](https://github.com/jverneaut/gutenberg-tailwindcss-bedrock-timber-twig/) — a companion project that enables you to use **Twig as the rendering engine for Gutenberg blocks**. This setup uses Timber and integrates tightly with TailwindCSS and Bedrock, giving you full control over the front-end and a seamless Twig-based authoring experience. > I plan to release this integration as a standalone package in the future to make it easier to use in other projects.