How "difficult" is modifying/extending Discourse for a backend dev

A few thoughts about this.

Developing paid Discourse courses

The idea of courses related to Discourse development on a platform like Udemy or Coursera is interesting. I suspect the main issues are that there aren’t that many people outside of the Discourse team who would be qualified to create a course in Discourse development and that the potential number of students for a course is quite small. That said, I’m sure that a good course related to developing Discourse themes or plugins could get enough students over time to make it worth the effort of creating a course.

There’s also a possible issue that charging for Discourse courses might go against the spirit of this forum. I’m just speculating about that though.

Discourse development tutorials

I think the observation that the actual implementation of many Discourse plugins, themes, and theme components is quite simple is correct. As with developing for other platforms, the problem is getting past the initial barrier of not understanding how the parts fit together. The Discourse team (or some other qualified developers) could help with this by writing a few tutorials that walk you through the entire process of creating functional themes, theme component, and plugins. For example, a step by step guide to creating something like the Discourse Placeholder theme component.

I realize that this would require a large investment of time, so it would need to align with Discourse’s business goals.

Currently available resources

With a bit of searching, there’s lots of good information about Discourse development on this forum. Looking at code examples from the Discourse GitHub repo is also a great way to learn.

A recent tool that shouldn’t be ignored is GitHub Copilot. If you structure your development environment in the right way, it can be helpful for getting a sense of how to develop Discourse themes and plugins. For developing Discourse themes, the best approach I’ve found is to develop the theme (using the Discourse Theme CLI) in the root directory of my local Discourse installation. Structuring things this way gives Copilot easy access to the Discourse code base. (Note that for plugin development, the plugins folder is already in the root /discourse directory.) (Also note, I could be wrong about Copilot actually accessing the Discourse code base that I’ve loaded in my IDE. In any case, it’s helpful to have the core Discourse code available for my own reference.)

To avoid distraction and also to focus on using Copilot as a tool for learning, I turn off Copilot Completions in my IDE (VS Code) and just interact with Copilot via chat. Here’s an example of the kind of assistance it can give. (shopify.js is the file that was created by the Theme CLI in my theme’s /javascripts/discourse/api-initializers/ directory.)

Conversation with Copilot

scossar: Can you explain what the default export in shopify.js is doing? It’s essentially the boiler plate code that is supplied by Discourse when a theme is created with the discourse_theme gem. I’ve altered the boiler plate code so that it logs each page change. I understand the call to api.onPageChange, but I don’t have a clear understanding of how the api object is being injected into my code.

GitHub Copilot: The api object is injected into your code through the withPluginApi function that is called in the initialize method of the object returned by apiInitializer.

Here’s a simplified version of what’s happening:

  1. When Discourse starts, it runs all the initializers.

  2. The apiInitializer function creates an initializer that runs after the "inject-objects" initializer.

  3. When this initializer runs, it calls the withPluginApi function, passing in the version, cb, and opts parameters.

  4. The withPluginApi function checks if the current version of Discourse is compatible with the version specified. If it is, it calls the callback function (cb) and passes the PluginApi instance as an argument. This PluginApi instance is the api object you see in your code.

So, the api object is an instance of PluginApi, and it’s being passed to your code by the withPluginApi function. This object provides a number of methods that allow you to interact with Discourse, such as onPageChange, which you’re already using.

scossar: So the function that’s the second argument to the default export in shopify.js is the callback function that’s accepted by apiInitializer?

GitHub Copilot: Yes, that’s correct. The second argument to apiInitializer is a callback function. This function is called with the PluginApi instance as an argument when the initializer runs (assuming the current Discourse version is compatible with the version specified). This is how the api object is made available in your shopify.js file.

Once I’d sorted that out…

scossar: …One other thought, could api.decorateCooked be used to inject a ratings iframe that was hosted on an external application (for example, hosted by the Shopify app that I’m developing)?

GitHub Copilot: Yes, you can use api.decorateCooked to inject an iframe into a post. Here’s a rough example of how you might do this:

api.decorateCooked($elem => {
  const postId = $elem.data('post-id');
  const iframe = document.createElement('iframe');
  iframe.src = `https://your-app.com/ratings?post_id=${postId}`;
  iframe.width = '100%';
  iframe.height = '200'; // adjust as needed
  $elem.append(iframe);
}, { id: 'rating-iframe' });

In this example, api.decorateCooked is used to append an iframe to each post. The iframe’s source is set to a URL on your external application, with the post ID included as a query parameter. You would replace 'https://your-app.com/ratings' with the actual URL of your ratings page.

Please note that this is a simplified example. You might need to adjust it to fit your needs, and you should definitely add error handling and other necessary precautions to ensure it works correctly and securely.

It’s not reasonable at this point to expect Copilot to actually write all of your code, but it’s a great tool for learning how to write Discourse code.