Code reading help. ES6, plugin-outlet `_connectorCache`, custom-html `_customizations`, appEvents

I got a few questions regarding plugin-outlet, custom-html, appEvents in mount-widget.js and ES6.

I’ve included some code snippets, and that’s why this post looks too long. It’s code from Discourse source code, not mine. So, it’s guaranteed that you won’t be reading my (hard-to-read) code.


While trying to read Discourse code, I wanted to try ‘something’ and created my own Rails project and created javascripts/something.js.es6 and javascripts/something-else.js. And imported them in application.html.erb with:

    <%= javascript_include_tag 'something-else' %> # loads
    <%= javascript_include_tag 'something' %>      # Throws error

And apparently, .es6 throws error. After some googling, I’ve discovered that I could use the babel-transpiler gem, but in the Discourse Gemfile, I don’t see babel-tranpiler gem being used. Googling “discourse es6” gives me:

which links to this commit. The Gemfile says it uses 6to5 but I don’t see that either in my Discourse folder.

How do you use ES6 in Discourse?


plugin-outlet.

It seems like the plugin-outlet uses data stored in Ember.TEMPLATES to ‘findOutlets’:

// app/assets/javascripts/discourse/lib/plugin-connectors.js.es6
function buildConnectorCache() {
  _connectorCache = {};

  findOutlets(Ember.TEMPLATES, (outletName, resource, uniqueName) => {
    _connectorCache[outletName] = _connectorCache[outletName] || [];

    _connectorCache[outletName].push({
      templateName: resource.replace("javascripts/", ""),
      template: Ember.TEMPLATES[resource],
      classNames: `${outletName}-outlet ${uniqueName}`,
      connectorClass: findClass(outletName, uniqueName)
    });
  });
}

But I can’t find where and how Ember.TEMPLATES is defined. TEMPLATES doesn’t seem like a Ember thing.

I did notice that I could prompt Ember.TEMPLATES on my Discourse instance, and it contains many entries, more than 450 on my instance. All entries seem like “template” files.

The only occurrence where I see it being defined is:

# lib/theme_javascript_compiler.rb
  # TODO Error handling for handlebars templates
  def append_ember_template(name, hbs_template)
    name = name.inspect
    compiled = EmberTemplatePrecompiler.new(@theme_id).compile(hbs_template)
    content << <<~JS
      (function() {
        if ('Ember' in window) {
          Ember.TEMPLATES[#{name}] = Ember.HTMLBars.template(#{compiled});
        }
      })();
    JS
  rescue Barber::PrecompilerError => e
    raise CompileError.new e.instance_variable_get(:@error) # e.message contains the entire template, which could be very long
  end

but this function was used in:

# app/models/theme_field.rb
  def process_html(html)
...
    doc.css('script[type="text/x-handlebars"]').each do |node|
      name = node["name"] || node["data-template-name"] || "broken"
      is_raw = name =~ /\.raw$/
      hbs_template = node.inner_html

      begin
        if is_raw
          js_compiler.append_raw_template(name, hbs_template)
        else
          js_compiler.append_ember_template(name, hbs_template)
        end

Here, I didn’t bother to read more because the file was under app/models/ that I thought this had something to do with the database.

Is there a function that searches for the template files and assigns to Ember.TEMPLATES? And in which file?


Regarding custom-html … I’m getting these questions from trying to read the application.hbs by the way.

// app/assets/javascripts/discourse/components/custom-html.js.es6
import { getCustomHTML } from "discourse/helpers/custom-html";
...

export default Ember.Component.extend({
  ...

  init() {
    ...
    const html = getCustomHTML(name);

// app/assets/javascripts/discourse/helpers/custom-html.js.es6
let _customizations = {};

export function getCustomHTML(key) {
  const c = _customizations[key];
  if (c) {
    return new Handlebars.SafeString(c);
  }
...
// Set a fragment of HTML by key. It can then be looked up with `getCustomHTML(key)`.
export function setCustomHTML(key, html) {
  _customizations[key] = html;
}

In this case VSCode search gives me that setCustomHTML isn’t used elsewhere besides the test file.

How is the _customizations filled? Or is this because I’m not using any “custom-html”?


Came up with the question while trying to read site-header component.

appEvents. I’ve seen:

// app/assets/javascripts/discourse/components/mount-widget.js.es6
  dispatch(eventName, key) {
    this._childEvents.push(eventName);

    const caller = refreshArg =>
      this.eventDispatched(eventName, key, refreshArg);
    this._dispatched.push([eventName, caller]);
    this.appEvents.on(eventName, caller);
  },

And many appEvents.on and appEvents.off lines in other files as well. But how is this.appEvents ‘injected’ into mount-widget?

I’ve only discovered it defined:

// app/assets/javascripts/discourse/widgets/widget.js.es6
export default class Widget {
  constructor(attrs, register, opts) {
    ...
    this.appEvents = register.lookup("app-events:main");

and what I’ve discovered, mount-widget has connection to the Widget class in widget.js only through:

  rerenderWidget() {
    ...
      const newTree = new this._widgetClass(args, this.register, opts);

So, in summary "how does app/assets/javascripts/discourse/components/site-header.js.es6 use this.appEvents? I don’t see it defined as a property for site-header"


I’m sorry if the question looks too big, but I thought this would help you understand my confusion.

Have you read Beginner's Guide to Creating Discourse Plugins - Part 1? That’s where I’d start.

Was it updated? Last time I read:

  • it was a beginners guide on how to create and use my plugin
  • I don’t think that post talks about custom-html

Also I’m curious about how ES6 is loaded.

You could just point me to the file where _customizations and Ember.TEMPLATES are loaded. Or … just more than one guide page on Discourse.

I’m interested in the source code design, not how to get started on adding my plugins, if you’ve read my question.

Thanks to anyone who could help in advance.

To be clear, you are trying to copy how Discourse’s asset pipeline works in another project?

What Discourse does is very complicated and specific to our use case. Just as an extreme example, we have our own hand-rolled template compiler for our virtual dom library. You don’t want to get lost in that unless you are Discourse.

I would not recommend it for another project. I recommend you use something like rails-webpacker instead.

6 Likes

I see. Just curious where about (like the file names) the “hand-rolled template compiler” and where its output is plugged into. I hope it’s not a hard task to tell me about those file names. Or, I should’ve easily figured it out if I knew how to read code properly :sob: