Theme component order of precedence

I notice that theme components appear to execute in order of installation. E.g. if I install A and then B, A scripts are included in generated pages before B.

Is this something I can rely on? Is there a view somewhere that shows this order? When viewing installed components, the component appear to be listed alphabetically.

I would prefer a way to explicitly run actions prior to a particular component. In my case, I want to run actions before the TOC component (related to this topic). From the Ember docs it looks like scheduled functions for a given queue are run in the order scheduled. This makes order-of-scheduling essential in my case.

2 Likes

This is not something you can rely on, instead proper APIs should control this. You can use ember initializers to control this to a degree. @eviltrout may have some specific ideas if you can paste the example code you are trying to schedule.

6 Likes

This is the code I’m running:

<script type="text/discourse-plugin" version="0.8">
    const { run } = Ember;
    
    api.decorateCooked($elem => {
        run.scheduleOnce("actions", () => {
          // Must run before the scheduled actions from TOC component.
        });
    })

The TOC component uses the same interface to schedule its actions.

According to the Ember docs, the schedule interface:

Adds the passed target/method and any optional arguments to the named queue to be executed at the end of the RunLoop

This term “add” suggests a FIFO ordering of scheduled actions. Therefore the order that the call to schedule is critical here.

I noticed that in my attempt to reorder JavaScript execution by reinstalling and uninstalling the TOC component (this has the fortuitous side effect, seemingly, of running the TOC JavaScript last) that the CSS rules are now applied using this order :slight_smile:

I feel like the topic of ordering for theme component code-placement is pretty important. If the ordering is officially “undefined” there’s no way components can hope to interact.

Perhaps component interaction is a non-goal. But I think with an ordering heuristic (e.g. declare that a component file occur before or after another component’s file of the the same name - this would presumably be defined in about.json) this problem is not-so-hard.

For my part, I’m starting to think that forking the TOC component is the right course based on what I’m trying to do.

Every time you create / install a theme or component, Discourse assigns an id to it. If you visit that component’s page, you should see that id in the URL (the number at the end)

component id in the URL

When that component is added to your theme, its execution order seems to be based on its id - on a very basic level (doing a console log without any deferring) . So, 233 will run before 234 and so on.

This works for the most part because subsequent changes are usually added in new components so it just works.

Long term we can have the order respect that of the list of components you have added to the theme

but that’s not on any roadmap for now.

What you really need is initializer order. I don’t think you can do that unless you move your code to the new way of creating theme JS. This method allows you to give the initializer a name and execution order. For example, let’s say I have this file

/javascripts/discourse/initializers/initialize-for-foo.js

and it looks like this

import { withPluginApi } from "discourse/lib/plugin-api";

export default {
  name: "foo",
  initialize() {
    withPluginApi("0.8.7", api => {
      console.log("foo")
    });
  }
}

and I have another initializer that looks like this

/javascripts/discourse/initializers/initialize-for-bar.js

import { withPluginApi } from "discourse/lib/plugin-api";

export default {
  name: "bar",
  initialize() {
    withPluginApi("0.8.7", api => {
      console.log("bar")
    });
  }
}

If I want to ensure that bar runs after foo, I can add an after: argument to it and that will ensure that it runs after the initializer name I pass there. So, to make bar run after foo I would do this in

/javascripts/discourse/initializers/initialize-for-bar.js

import { withPluginApi } from "discourse/lib/plugin-api";

export default {
  name: "bar",
+ after: "foo",
  initialize() {
    withPluginApi("0.8.7", api => {
      console.log("bar");
    });
  }
};
5 Likes

Thanks so much for detailed pointers! Given my related issues with the TOC component (which you also replied to - thank you again) I’ve forked TOC and moved the dependent code into that component. This is related to the heading IDs - specifically the need to control the from the post to handle duplicates and collisions with core element IDs.

I think given what I’m doing with docs, some of which is a bit off the reservation, I think that’s the right approach.

1 Like