Upcoming core changes that may break some themes/components (April 12)

Next week I’m going to merge this PR that allows themes and components to have QUnit tests, but it also changes how themes JavaScript is processed/transpiled by Discourse. Making those changes in a backward-compatible manner is very difficult without reworking lots of code in core (which may very well introduce other backward-incompatible changes), so the changes may break the JavaScript of your themes/components when you upgrade your site.

In this post I’m going to explain what the changes are that may affect your themes/components and what you need to do to fix them.

1. JavaScript inside <script type="text/discourse-plugin"> tags will be invoked with strict mode enabled

This change doesn’t affect JS code that’s not inside <script type="text/discourse-plugin"> tags. You’re completely safe from this change if your code is in regular <script> tags or in standalone .js files.

The easiest way to tell if your theme/component is affected by this change is to wrap your JS code inside an Immediately-Invoked Function Expression (IIFE) and stick a "use strict"; at the top of your code. For example, let’s say this is how your theme code looks like right now:

<script type="text/discourse-plugin" version="0.8.11">
  a = 5;
  console.log(a);
</script>

After you wrap it in an IIFE it should look like this ("use strict"; is important because it enables strict mode and we want to test how our code behaves in strict mode):

<script type="text/discourse-plugin" version="0.8.11">
  (function() {
    "use strict";
    a = 5;
    console.log(a);
  })();
</script>

If your component stops working after you do this, then it’s going to break when you upgrade your site. To fix your code, I highly recommend that you first read MDN documentation for JavaScript Strict mode and then see if your theme/component does anything prohibited under strict mode. If it does, then you need to refactor your code so it doesn’t do anything prohibited.

The most likely error that you’ll see is ReferenceError when declaring a variable without the var (or let/const) keyword. In the example above, the line a = 5; will throw a ReferenceError exception under strict mode because we forgot to add var. The code looks like this after we fix it:

<script type="text/discourse-plugin" version="0.8.11">
  (function() {
    "use strict";
    var a = 5;
    console.log(a);
  })();
</script>

Once you’re done testing/fixing your code, feel free to remove the IIFE and the "use strict"; line.

2. Themes JavaScript modules paths will be prefixed with theme IDs.

A while back we added a new feature that allowed themes JavaScript to be split into multiple files, and I need to give a little bit of context on how this feature to explain the upcoming change.

When a theme/component that ships with standalone JavaScript files is installed on a Discourse instance, Discourse loops through all the JavaScript files and creates a JavaScript module for each one. Each module needs to have a unique identifier (a.k.a path), so Discourse uses the file path (with minor changes) as the module’s path.

For example, if your theme/component has a file at javascripts/discourse/helpers/my-helper.js, Discourse will create a module for that file and assign discourse/helpers/my-helper as its path, and the module will contain a transpiled version of the JavaScript inside the original file.

The nice thing about modules is that you can import classes/functions/objects etc. from one module into another. For example, you could import a function called xyz from my-helper into other modules by using an import statement like so:

// javascripts/discourse/controllers/my-theme-controller.js

import { xyz } from "discourse/helpers/my-helper";

The PR that I’m going to merge next week will add a prefix to themes modules paths. So in our example my-helper will change from discourse/helpers/my-helper to discourse/theme-<theme_id>/helpers/my-helper. This means our import statement will stop working because the module path will have changed. To fix it we need to simply change the path in the import statement from absolute to relative like so:

// javascripts/discourse/controllers/my-theme-controller.js

import { xyz } from "../helpers/my-helper";

And now our import statement should work again. See these PRs 1 and 2 for real examples of components that are affected by (2) and how they’re fixed.

Again, this only affects your theme/component if it imports from one of its own modules; importing core modules is not affected by this change.

Hope this helps. If you have any questions, please let me know!

31 Likes

Thanks for the detailed write-up :slight_smile:

Will this affect “absolute” paths to files in a plugin used in a theme component? For example theme components that work with the layouts plugin all require helpers in the layouts plugin itself like this

requirejs('discourse/plugins/discourse-layouts/discourse/lib/layouts')

See for example, the layouts category list widget.

It seems the path change here brings it in line with the namespacing of assets in the plugin asset pipeline (using theme ids instead of plugin names), and that plugin asset paths used in a theme component will remain the same. And that a require like the above will still work. Is that right?

7 Likes

Yes, that’s right :+1:

6 Likes

@loginerror, I think you’ll find this topic useful :wink:

7 Likes

@Terrapop
I think you had concerns regarding this.

5 Likes

We already fixed our code when this was pushed for a limited time.

5 Likes