asyncapi / generator

Use your AsyncAPI definition to generate literally anything. Markdown documentation, Node.js code, HTML documentation, anything!
https://asyncapi.com/docs/tools/generator
Apache License 2.0
766 stars 229 forks source link

React as a Generator engine #407

Closed fmvilas closed 3 years ago

fmvilas commented 4 years ago

React as a Generator engine

Make Generator support React as a template engine, along with Nunjucks.

Problem

The current templating system —based on Nunjucks— has some really important drawbacks:

  1. It's hard (or impossible) to create reusable "partials" across templates. Ultimately, this leads to repetition, especially those of the same kind. For instance, POJO (Plain Old Java Objects) definitions are common across different templates. And this is something that's going to happen more and more as we create different templates for the same programming language.
  2. It gets really painful to debug a template when an error happens inside a macro. Even with the --debug flag turned on. That's a problem of Nunjucks itself and has been there for quite some time now.
  3. Even though Nunjucks provides lots of built-in filters, it's very common to find ourselves having to create a custom filter for basic stuff like checking the type of a variable, e.g., typeof variable === 'object' or running basic JS logic.
  4. It can get really hard to read a complex template. See this example.
  5. All the points above become super important if we want to increase the number of templates. Template authoring needs to be a smooth process, not a painful one.

Solution

In general, the solution goes through adding React as a template engine to the Generator. When the template declares that it's using React as its template engine, the Generator will render files using React instead of Nunjucks.

There a slight change in the "rendering paradigm" though. In the case of React, we don't "render the file" but instead we "import" it, "process" it, and get the result as a string that has to be written to the destination file. This will allow us to have top-level metadata on each file, like in the following example:

// File: $$schema$$.java

...
import { generateCamelCaseSchemaName } from '@asyncapi/generator/sdk'

export default function SchemaFile({ schema }) {
  return (
    <File name={generateCamelCaseSchemaName(schema)}>
      ... (your Java code here)
    </File>
  )
}

In the example above, we have a top-level <File> component that allows us to add meta information about the file itself, like its name, permissions, and more. Even more, we can tell Generator not to render this file by simply returning null in the SchemaFile function:

// File: $$schema$$.java

...
import { generateCamelCaseSchemaName } from '@asyncapi/generator/sdk'

export default function SchemaFile({ schema }) {
  if (schema.type !== 'object') return null // <== NOTICE THIS LINE

  return (
    <File name={generateCamelCaseSchemaName(schema)}>
      ... (your Java code here)
    </File>
  )
}

In the example above, this file will only be rendered if the type of the schema is object. This will allow us to eventually get rid of file name template syntax like $$schema$$, $$everySchema$$, and $$objectSchema$$. Also, the usage of <File name="..."> will eventually allow us to get rid of all the file name templates.

Solution elements

Rabbit holes & challenges

  1. Compiling React JSX and ES7 import syntax may be challenging. Watch out. We may have to introduce a step to precompile ES7 and JSX syntax before the renderer requires the files.
  2. We may have to create our own React renderer with its own DOM model and reconciler. Here's a great source of inspiration: https://github.com/chentsulin/awesome-react-renderer. This tool can also be helpful: https://www.npmjs.com/package/esinstall.

Out of bounds

  1. Removing the usage of file name templates.
  2. Creating an SDK for the Generator.
derberg commented 4 years ago

The most important is to have it as mentioned in the first line, along with Nunjucks, where Nunjucks is default, and with some alpha flag one can enable React.

It can get really hard to read a complex template I just would like to argue here that it is a good reason for a change. Simply because you can also write an unreadable react component, while you also can write a perfectly structured Nunjucks template like the html-template. Unless you mean that there is a great plugin/tooling support for displaying the content of the react component

fmvilas commented 4 years ago

with some alpha flag one can enable React

We can instead rely on the template configuration file. If there's a renderer field with value equal to react, we proceed as a React template. Otherwise, it would use Nunjucks.

Unless you mean that there is a great plugin/tooling support for displaying the content of the react component

The difference is that anyone can easily create reusable components (partials) or add handy functions right inside the React file. The latter can be done with Nunjucks filters too but requires more effort because functions are not colocated with the code using them, which is great for reusability but a pain for simple one-time stuff. The former is simply not possible. We can't have reusable partials anyone can use in their templates (without having to copy/paste from somewhere).

In general, the more elaborated/handy components we can offer to the template authors, the easier is going to be to create a template and also read it. One common example is the variable indentation. You have to prepend something like {{ indent1 }} as in this example, making the whole thing hard to read. That's the reason I'm including the <Text indentation={indentSize}> component as a mandatory thing in this initial version.

derberg commented 4 years ago

We can instead rely on the template configuration file. If there's a renderer field with value equal to react, we proceed as a React template. Otherwise, it would use Nunjucks.

❤️

github-actions[bot] commented 3 years ago

This issue has been automatically marked as stale because it has not had recent activity :sleeping: It will be closed in 30 days if no further activity occurs. To unstale this issue, add a comment with detailed explanation. Thank you for your contributions :heart: