UNPKG

@jverneaut/html-to-gutenberg

Version:

Create custom Gutenberg blocks from the HTML templates you already have.

110 lines (95 loc) 4.02 kB
import { visit } from "unist-util-visit"; import ProcessorBase from "#processors/ProcessorBase.js"; import PrinterEditJS from "#printers/PrinterEditJS.js"; import { parseRawValue } from "#utils-string/index.js"; /** * @class CustomElementJSXTransformer * * Transforms specific custom HTML elements into JSX-compatible elements * by updating tag names and mapping attributes. * * This processor operates only on the AST corresponding to `PrinterEditJS.filename`. */ export default class CustomElementJSXTransformer extends ProcessorBase { /** * @static {string} HTMLTagName * The tag name to match in the HTML AST (e.g., 'custom-element'). */ static HTMLTagName = ""; /** * @static {string} JSXTagName * The JSX tag name to replace the matched HTML tag with (e.g., 'CustomElement'). */ static JSXTagName = ""; /** * @static {Array<{ html: string|true, jsx: string|false }>} attributesMapping * Defines how to map HTML attributes to JSX props. * * If `html` is `true`, all unmatched attributes will be collected into a single * JSX prop defined by `jsx`. * Otherwise, specific attribute names will be renamed from `html` to `jsx`. */ static attributesMapping = []; /** * Transforms all matching elements in the `PrinterEditJS` AST: * * - Replaces elements with tag name matching `HTMLTagName` by `JSXTagName`. * - Converts or groups attributes based on `attributesMapping`: * - If `html: true` is present in a mapping, collects all *unmapped* attributes * and serializes them as a JSON string into the JSX prop specified by `jsx`. * - If `html` is a string, renames that specific attribute to the corresponding `jsx` key. * - Invokes `onMatch(node)` on every transformed element. * * This method only runs for the AST tied to `PrinterEditJS.filename`. * * @param {string} filename - The filename of the AST to process. */ processAstByFilename(filename) { if (filename === PrinterEditJS.filename) { visit(this.asts[filename], "element", (node) => { // Only transform nodes with matching HTML tag name if (node.tagName === this.constructor.HTMLTagName) { // Rename the tag to the target JSX tag name node.tagName = this.constructor.JSXTagName; // First pass: group unmapped attributes under a JSX prop (html: true case) this.constructor.attributesMapping.forEach((attributeMapping) => { if (attributeMapping.html === true) { const attributeValue = {}; Object.keys(node.properties).forEach((propertyKey) => { if ( !this.constructor.attributesMapping .map((attributeMapping) => attributeMapping.html) .includes(propertyKey) ) { attributeValue[propertyKey] = parseRawValue( node.properties[propertyKey], ); delete node.properties[propertyKey]; } }); if (attributeMapping.jsx) { // Assign grouped properties to the JSX attribute with internal templating syntax node.properties[attributeMapping.jsx] = `$\${${JSON.stringify(attributeValue)}}$$`; } } }); // Second pass: rename specific attributes as defined this.constructor.attributesMapping.forEach((attributeMapping) => { if (attributeMapping.html !== true) { Object.keys(node.properties).forEach((propertyKey) => { if (attributeMapping.html === propertyKey) { node.properties[attributeMapping.jsx] = node.properties[attributeMapping.html]; delete node.properties[attributeMapping.html]; } }); } }); this.onMatch(node); } }); } } onMatch(node) {} }