JSX instead of h (createElement) function in widgets and other places

Hi, I’ve been experimenting with JSX in widgets for better developer experience in themes (I’ve came up with my own setup for theme development without header.html etc, more on that in another topic).

I was wondering why doesn’t Discourse utilise babel plugins within the it’s codebase. In Discourse we have available transform-react-jsx babel plugin which can be used to transform JSX to custom createElement function using jsxPragma options. All of that is available in Discousre.

Adding transform-react-jsx here https://github.com/discourse/discourse/blob/master/lib/discourse_js_processor.rb#L143-L149 and somehow exposing the Babel config to change the jsxPragma here https://github.com/discourse/discourse/blob/master/vendor/assets/javascripts/babel.js
could make this happen.

I’ve already implemented the first part, the pragma part still needs some discovery. Unfortunately, there is no .babelrc to setup invidual plugin configuration. I get the React.createElement function instead of custom function.

More on using JSX without React here: Using jsx WITHOUT React | r0b blog

Any thoughts or maybe help how could this be achieved?

UPDATE:
I did manage to make this work by changing the pragma setting in vendor/assets/javascripts/babel.js on line 26586

var id = state.opts.pragma || "React.createElement";
// to
var id = state.opts.pragma || "h";

Have you see this? You can use Templates now if you don’t like Hyperscript:

4 Likes

@merefield yes, I know about hbs, but I’m not “your typical discourse theme developer” :slight_smile: . I would like to use power of JSX which is actual JS instead of hbs which are limited in their functionality.

And did managed to use JSX in discourse theme now. But in my own discourse fork, so not very useful for general theme development.

2 Likes

Ultimately it’s @eviltrout’s call. I can give you my feeling though. Yes JSX can be pleasant to use and we could have it in Discourse, however I don’t think we will, because we would have to support it and we are more leaning towards getting rid of widgets at some point than adding more options.

9 Likes

I completely understand your point. I’m just wonderinb, why not open the configuration for babel (and bunch of other stuff) for theme and plugin developers?

For example .babelrc in the theme folder can be used for some addition options for js transpiler. If the .babelrc is present discourse_js_processor can use it and extend it’s babel config and enable bunch of additional JS features.

Just an idea.

It’s just a matter of “public api” in a broad mean, whatever we allow, we will have to carry it. What seems easy to support today, might be hard tomorrow. We are definitely experimenting with a lot of things in the pipeline and the client side part of the app lately. Let’s see what robin thinks of all of this.

5 Likes

Please feel no pressure, I’m just a curious developer with some questions. I came to discourse from full stack JS (Node/Vue) world where Ember is just not an option, especially when you have React or even better, Vue.

Ember feels so unnatural and handlebars are just out of date with it’s features. Templating power of JSX and especially Vue SFC cannot be compered with something basic like hbs.

2 Likes

I disagree that Ember is not an option for full stack JS. I understand the argument that Ember is not as popular as other frameworks and you might want to choose another framework for that reason, but there are certainly many many sites using node on the back end and Ember in the front end. All of Ember’s tooling is based on Node, after all.

Having said that, JSX is not in Discourse core’s future, but if there are things we could do to enable its usage in plugins I’d be open to it. Please make a proposal for how it would work!

9 Likes

It is an option, but in my development and work surrounding it is not, and I personally would not use it because Vue seems like a natural fit for these kind of applications.

Just a bit of context:

In last couple of months I’ve been working on developing a pretty custom theme for a client, where l learned a bunch of stuff about inner workings of discourse, but that’s probably a small percentage.

Currently, I’m refactoring bunch of beginner mistakes and trying to make things friendly as possible for future colleagues that could end up maintaining my work. That’s way, I did a bit of experimentation regarding JSX.

Today, I tried the approach from the first post in topic. I’ve included transform-react-jsx here: https://github.com/discourse/discourse/blob/master/lib/discourse_js_processor.rb#L143-L149:

      if opts[:module_name] && !@skip_module
        filename = opts[:filename] || 'unknown'
        "Babel.transform(#{js_source}, { moduleId: '#{opts[:module_name]}', filename: '#{filename}', ast: false, presets: ['es2015'], plugins: [['transform-es2015-modules-amd', {noInterop: true}], 'transform-decorators-legacy', 'transform-react-jsx', exports.WidgetHbsCompiler] }).code"
      else
        "Babel.transform(#{js_source}, { ast: false, plugins: ['check-es2015-constants', 'transform-es2015-arrow-functions', 'transform-es2015-block-scoped-functions', 'transform-es2015-block-scoping', 'transform-es2015-classes', 'transform-es2015-computed-properties', 'transform-es2015-destructuring', 'transform-es2015-duplicate-keys', 'transform-es2015-for-of', 'transform-es2015-function-name', 'transform-es2015-literals', 'transform-es2015-object-super', 'transform-es2015-parameters', 'transform-es2015-shorthand-properties', 'transform-es2015-spread', 'transform-es2015-sticky-regex', 'transform-es2015-template-literals', 'transform-es2015-typeof-symbol', 'transform-es2015-unicode-regex', 'transform-regenerator', 'transform-decorators-legacy', 'transform-react-jsx',exports.WidgetHbsCompiler] }).code"
      end

And changed the pragma setting in vendor/assets/javascripts/babel.js on line 26586

var id = state.opts.pragma || "React.createElement";
// to
var id = state.opts.pragma || "h";

That has enabled me to use h function from virtual-dom instead of React.createElement in the babelfied code. h is part of discourse and this felt like a natural fit.

I don’t know the history of discourse development, and you probably had your reasons for adding fullblown babel.js file with bunch of useful plugins in the code. It’s a shame that these plugins are not available to end developer user like me. So my proposal would be to:

  1. use some kind of babel config, .babelrc, .babel.yml, or in about.json define a babel config field like in package.json Configure Babel · Babel. That config could reference all these plugins in babel.js (or maybe some others, check 3.)

  2. In discourse_js_processor.rb#babel_parse `https://github.com/discourse/discourse/blob/master/lib/discourse_js_processor.rb#L138 read this config and extend it the current config.

  3. If a developer wants to have an custom babel plugin, he could add it to it’s vendor/assets/javascripts/babel/plugins folder (or just babel/plugins folder) and discourse would pick it up and us it in transpile process.

I’m not experienced in Rails backend development - so this is my meta proposal.

And another question, while we’re at it, how hard would it be for discourse to read the dependencies from lets say themes package.json and installed them on first run and use the (have them cached or something like that) inside of theme? (If dependencies change, check which changed and install or remove them, etc).

Once again, you guys are doing a great job and your work is amazing. Please, don’t feel any pressure from my side, I’m just here for learning and from some good discussion and sharing opinions and ideas. I do not expect from you to implement my requests nor anything.

3 Likes

What do you mean by that? What is not possible with handlebars and Ember that’s implemented in another framework?

What I meant is the JSX is JS, not a templating language, a being JS by itself makes is more powerful (for example: ternary operator, map function, etc). You can make what ever you want in Ember/Handlebars but JSX is more convenient and developer friendly, IMHO.

1 Like

And did that work? You were able to add jsx and everything was fine?

Now the other questions you had about why babel is configured the way it is - Discourse is built on Rails using the asset pipeline, and webpack support was only added into Rails in version 6. Discourse’s code base is 8 years old! So over time we’ve added stuff to it but we’ve outgrown its limitations.

One of the big projects this year is migrating to Ember CLI - if you look at our commits you’ll see work towards this goal going into Discourse, but it’s a long road.

Once that is in place, you will be able to use a lot more Javascript build features that currently do not work unless you run them from within Ruby, which is cumbersome. In the meantime, I can’t see us devoting time to reading package.json and having that go in the Rails pipeline automatically.

6 Likes

I’ll POC PR to demonstrate how that works.

Thanks for the insight and explanation!

3 Likes

An update - I’ve managed to make a ThemeField babel, which is used to read babel.config.json from theme and inject plugins into Babel.transform in the discourse_js_processor.

I hope go get the PR out soon just to show as a prof of concept. A warning - It will be a huge mess :sweat_smile:.

PS. Ruby is so easy to work with :star_struck:

Here it is:

https://github.com/discourse/discourse/pull/9729

2 Likes