Generator: Support React as a template engine

Created on 5 Jun 2020  路  9Comments  路  Source: asyncapi/generator

Yes, you've read well. And no, I'm not drunk. Yet 馃槃

React has proven it's not just made for HTML generation. You can find multiple projects using it for different purposes, like Ink or React Native. So why not code generation?

Reasons why we should consider it

  1. It鈥檚 markup in JS not JS (or logic) in markup. We鈥檙e always creating a lot of basic filters because we鈥檙e on the markup language by default. Same problem appears when using Angular or Vue (not sure latest versions, I think they changed something wrt this). If we choose something like React, we could easily import any npm package or do basic stuff like typeof myvar === 'object' without having to create a filter.
  2. Composability and reusability. If templates (and its parts) are React components, they can be reused in other templates too. Think, for instance, we have a POJO template and we want to use the POJO template in many Java code generators. Of course, the POJO template can use our future feature for model generation but it can actually improve it with some other stuff that may not be possible or available. Like generating other files that operate on the POJO, for instance. I鈥檝e been thinking a lot how cool would it be to use templates inside templates. Same way you use Node.js packages in your Node.js packages.
  3. We can benefit from tooling for rendering static React markup. In the end, it鈥檚 no different from what Gatsby and Next.js are doing. We just sometimes output HTML and sometimes Markdown, code, etc. which are all strings of text and perfectly doable with React.
  4. We can benefit from React Fast Refresh. While developing a template, you would instantly have the generated result after saving. Way faster than what we have right now and, in the case of developing HTML templates, preserving the state and refreshing without reloading the page.
  5. We get a more precise error context than we do now with Nunjucks. If you're a template developer, you know how difficult could it be to guess where the error is exactly located. With React, you get precise information in the CLI and in the browser (if your template is for the browser). See the example below.
  6. And last but not least, React is a stable project with a huge community while Nunjucks is pretty much stuck in adding new features. I think it鈥檚 due to the fact that they want to be compatible with Python鈥檚 Jinja2.

Example of how I imagine it

Please note this is a draft. Things ~may~ will for sure change. Read it as a way to understand what I'm thinking about.

The following is a translation of the index.js file you can find here: https://github.com/asyncapi/nodejs-template/blob/master/template/src/api/index.js

import { convertToFilename } from '@asyncapi/generator/sdk'
import { File, Line } from '@asyncapi/generator/react-sdk'
import { capitalize, camelCase } from 'lodash'

export default function IndexFile({ asyncapi, params }) {
  const protocol = asyncapi.server(params.server).protocol()
  const configKey = protocol === 'ws' ? 'ws' : `broker.${protocol}`

  return (
    <File>
      const Hermes = require('hermesjs');
      const app = new Hermes();
      const {cyan, gray} = require('colors/safe');
      const buffer2string = require('./middlewares/buffer2string');
      const string2json = require('./middlewares/string2json');
      const json2string = require('./middlewares/json2string');
      const logger = require('./middlewares/logger');
      const errorLogger = require('./middlewares/error-logger');
      const config = require('../lib/config');
      const { capitalize(protocol) }Adapter = require('hermesjs-{protocol}');
      {
        Object.entries(asyncapi.channels()).map(([channelName, channel], index) => (
            <Line key={index}>
              const { camelCase(channelName) } = require('./routes/{ convertToFilename(channelName) }.js');
            </Line>
          )
        )
      }

      app.addAdapter({ capitalize(protocol) }Adapter, config.{configKey});

      app.use(buffer2string);
      app.use(string2json);
      app.use(logger);

      // Channels
      {
        Object.entries(asyncapi.channels()).map(([channelName, channel], index) => (
          <Line key={index}>
            { channel.hasPublish() && `app.use(${ camelCase(channelName) });` }
            { channel.hasSubscribe() && `app.useOutbound(${ camelCase(channelName) });` }
          </Line>
        ))
      }
      app.use(errorLogger);
      app.useOutbound(logger);
      app.useOutbound(json2string);

      app
        .listen()
        .then((adapters) => {
          console.log(cyan.underline(`$\{config.app.name\} $\{config.app.version\}`), gray('is ready!'), '\n');
          adapters.forEach(adapter => {
            console.log('馃敆 ', adapter.name(), gray('is connected!'));
          });
        })
        .catch(console.error);
    </File>
  )
}

Examples of error information

On the markup

Screen Shot 2020-06-05 at 13 04 13

On the JS code

Screen Shot 2020-06-05 at 13 06 07

And of course the Stack trace too

Screen Shot 2020-06-05 at 13 07 29

enhancement keep-open

Most helpful comment

I vote for having it as an experimental additional template engine which we can slowly worm up and see where it can go, or just get rid of it after all if we are not happy

All 9 comments

I'm template developer, and with Nunjucks i could write template on the Java with slight influence of Nunjucks filters. I like this feature, because it's my primary language and if template user would like to have a look on the code, it will be understandable for the user.
Regarding "Fast Fresh" will be work with hooks? Especially with hooks which change file names.

Interesting... I'm sold just because of "We get a more precise error context than we do now with Nunjucks" and then keeping everything in javascript 馃槃 Are you thinking complete replacement or just another thing to support? 馃

@Tenischev I understand the friction. The problem is that, as templates get more and more complex, Nunjucks sucks in readability. Actually, all the React files would start with something like:

import { convertToFilename } from '@asyncapi/generator/sdk'
import { File, Line } from '@asyncapi/generator/react-sdk'
import { capitalize, camelCase } from 'lodash'

export default function MyFile({ asyncapi, params }) {
  return (
    <File>
     ...

So when reading, even if you don't understand much about it, you can pretty much ignore it as it's boilerplate code that's very similar everywhere. In any case, don't be afraid of React, it's JS with HTML inside 馃槃 You'll be fine with it in 20 minutes or less.

@jonaslagoni Initially, I'd do it as a separate engine we support. Depending on how we see this working we might decide to keep both or just go for one. Both approaches have pros and cons. Definitely not something we have to decide now 馃槃

Another thing I didn't mention: since we now would have a top-level <File> tag, we can put meta-information about the file there, like its name, permissions, etc. Something you'd do with hooks now could be easily done here like this:

// File: $$schema$$.java

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

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

This approach cuts down filters and hooks usage by a lot.

I vote for having it as an experimental additional template engine which we can slowly worm up and see where it can go, or just get rid of it after all if we are not happy

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:

I vote to move this feature along. I am starting to get really frustrated with Nunjucks it takes so long time to find the bugs the generator are reporting with --debug flag or without... It takes longer and longer to create more and more complicated templates.

Example I am currently getting the following error:

Something went wrong:
Template render error: (C:\Users\Lagoni\AppData\Roaming\npm\node_modules\@asyncapi\generator\node_modules\@asyncapi\dotnet-nats-template\template\Dotnet.Nats.Client\test\channels\$$channel$$.cs.njk) [Line 66, Column 5]
  unknown block tag: endif
    at Object._prettifyError (C:\Users\Lagoni\AppData\Roaming\npm\node_modules\@asyncapi\generator\node_modules\nunjucks\src\lib.js:36:11)
    at C:\Users\Lagoni\AppData\Roaming\npm\node_modules\@asyncapi\generator\node_modules\nunjucks\src\environment.js:561:19
    at eval (eval at _compile (C:\Users\Lagoni\AppData\Roaming\npm\node_modules\@asyncapi\generator\node_modules\nunjucks\src\environment.js:631:18), <anonymous>:20:11)
    at C:\Users\Lagoni\AppData\Roaming\npm\node_modules\@asyncapi\generator\node_modules\nunjucks\src\environment.js:611:9
    at eval (eval at _compile (C:\Users\Lagoni\AppData\Roaming\npm\node_modules\@asyncapi\generator\node_modules\nunjucks\src\environment.js:631:18), <anonymous>:60:12)
    at Template.getExported (C:\Users\Lagoni\AppData\Roaming\npm\node_modules\@asyncapi\generator\node_modules\nunjucks\src\environment.js:599:16)
    at eval (eval at _compile (C:\Users\Lagoni\AppData\Roaming\npm\node_modules\@asyncapi\generator\node_modules\nunjucks\src\environment.js:631:18), <anonymous>:59:6)
    at createTemplate (C:\Users\Lagoni\AppData\Roaming\npm\node_modules\@asyncapi\generator\node_modules\nunjucks\src\environment.js:313:9)
    at handle (C:\Users\Lagoni\AppData\Roaming\npm\node_modules\@asyncapi\generator\node_modules\nunjucks\src\environment.js:325:11)
    at C:\Users\Lagoni\AppData\Roaming\npm\node_modules\@asyncapi\generator\node_modules\nunjucks\src\environment.js:337:9
    at next (C:\Users\Lagoni\AppData\Roaming\npm\node_modules\@asyncapi\generator\node_modules\nunjucks\src\lib.js:326:7)
    at Object.asyncIter (C:\Users\Lagoni\AppData\Roaming\npm\node_modules\@asyncapi\generator\node_modules\nunjucks\src\lib.js:332:3)
    at Environment.getTemplate (C:\Users\Lagoni\AppData\Roaming\npm\node_modules\@asyncapi\generator\node_modules\nunjucks\src\environment.js:319:9)
    at eval (eval at _compile (C:\Users\Lagoni\AppData\Roaming\npm\node_modules\@asyncapi\generator\node_modules\nunjucks\src\environment.js:631:18), <anonymous>:57:5)
    at C:\Users\Lagoni\AppData\Roaming\npm\node_modules\@asyncapi\generator\node_modules\nunjucks\src\environment.js:613:9
    at Template.root [as rootRenderFunc] (eval at _compile (C:\Users\Lagoni\AppData\Roaming\npm\node_modules\@asyncapi\generator\node_modules\nunjucks\src\environment.js:631:18), <anonymous>:41:1)

Where the file $$channel$$.cs.njk only contains 33 lines of code so the error is neck deep in one of the macros, which might go as deep 3 atm..

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:

This is now fully supported check out the docs https://github.com/asyncapi/generator/blob/master/docs/authoring.md

Was this page helpful?
0 / 5 - 0 ratings

Related issues

alexanderTilg picture alexanderTilg  路  10Comments

ymarillet picture ymarillet  路  9Comments

jonaslagoni picture jonaslagoni  路  5Comments

uraala picture uraala  路  10Comments

derberg picture derberg  路  8Comments