Issues on loading a js asset from a Theme Component

Hi, first time developing on Discourse, so sorry for the silly questions.
I’m building a theme component to display PGNs (chessboard and matches).
The component is not theoretically difficult, there is a js component for this (PGNViewer.js) and I was able to translate the [wrap] tags into the proper <script>var x='pgn here'; ...</script> and <div class='pgn-blahblah'></div> blocks.
So far so good, but now I need to load the js.
I found that I can use import loadScript from "discourse/lib/load-script"; and await loadScript(settings.theme_uploads_local.pgnviewer_js); to load it at initialization time from the /assets/ directory.
But I have a few problems here.
Despite I’m able to load the component on the dev installation, I can’t on my production server, because it says the asset is too big. Is there a way to fix this?
Second, on the dev installation, I see a warning stating it couldn’t load /theme-javascripts/ I have no idea where it comes from. Is there something I should provide?
Overall, is this approach correct, or would it be better to implement a plugin instead?
kind regards



Welcome to Meta, @happycactus :wave:

Yes, probably. The only reason why you’d need a plugin is if you were trying to change the API (ie back end).

I’ve had a similar issue in the past.

Yes, you need to change app.yml to allow large files to be fed through the reverse proxy (nginx).

Thankfully this is a simple setting, upload_size:, here in context:

  ## Which Git revision should this container use? (default: tests-passed)
  version: tests-passed

  ## Maximum upload size (default: 10m)
  upload_size: 20m

Once you’ve changed this you will need to rebuild for it to take effect:

./launcher rebuild app

Of course, this assumes you have appropriate access to the server …


Thank you, Robert!

I’m in doubt, probably because I’ve zero experience with such platforms.
My doubt is:

my approach is to “embed” the small snippet of javascript needed to fill the div block with the content, but I see that maybe I could execute it from a function “cookPgn”, like it is done in the marmaid component:

async function applyMermaid(element, key = "composer") {
  const mermaids = element.querySelectorAll("pre[data-code-wrap=mermaid]");
  await loadScript(settings.theme_uploads_local.mermaid_js);
  mermaids.forEach((mermaid) => {
    if (mermaid.dataset.processed) {

    const spinner = document.createElement("div");

This way, if I understand correctly, I don’t need to load the script from the page (using loadScript perhaps??) but I can just import it in the theme js? Or likely I’m totally mislead!
I mean, what’s the difference between the loadScript and a simple import?
Can you address me to some web resource where I can better understand this?
Sorry again if the questions are trivial or stupid!

Another thing, the limit is by default to 10M, but my asset is around 300kb, and the whole zip is 337kb, still during upload it says it is mre than 512kb.



Have you tried an import? I don’t think you can import an external script.

Loadscript is the right approach and ensures that the script is fully loaded before your code proceeds and uses it (this is why it returns a promise).

can you share the exact error - is this in the browser console?

Your actual problem is trying to use inline JS, which is prohibited by CSP.

Put this code in a helper function in your theme component code, and do not inject it inside posts.

Uploading your current theme to GitHub and we can take a look too.


Have a look at the Discourse Mermaid component, which loads a library and has stuff happen to posts.


Thanks, @Falco, this was one of my doubts.

Sure, here it is: GitHub - studiofuga/discourse-pgn-component: A theme component for Discourse to display PGN blocks
There’s also a plugin version, but I think I’ll not follow this approach and stay on the theme component.

Thanks @pfaffman , indeed I took inspiration on it, and I noticed that it does exactly what I need and in the correct way, without injecting the code as suggested above.

Thanks all, I’ll try with your suggestions and get back with results. Of course my code is open sourced, so any contribution or critic is very welcome.

1 Like

My fault, I forgot I limited the attachment size to 512kB, raised to default (4096) and it worked well. Thanks, and sorry for the stupid question.


Ok I did fix everything and the component does work! But we have a problem, an “unsafe eval” in the library I’m using. I guess I should fix the upstream library, I have no idea how to pack it.
For those interested, I pushed all my changes in my repo above, and here are the details of the error.

and the line is this (on the original js module)

let isBrowser=new Function("try {return this===window;}catch(e){ return false;}")


1 Like

Your script is tripping over Content Security Policy in Production.

You need to add 'unsafe-eval' as a line item in the site setting: content security policy script src

Thanks, I’m browsing the documentation to see how to do this. :slight_smile: I’m learning a lot, thanks

1 Like

Ok I did it, just I needed to use the ’ … lol… Anyway, My script almost worked, I pushed everything on main and it somehow worked, it seems i have an issue with rendering but at least the main component is called (I suspect where the problem is).
Thanks all… I’ll be back soon with more questions LOL. Thanks!

1 Like

Keeping in mind that adding unsafe-eval to your CSP is basically removing the whole point of CSP and making your site less secure as a result.

Using eval is a code smell, and it should be fixed upstream as you pointed above.


Yes, defintely. I’m unable to fix it at the moment, but my plan is to first fix my component, then fix the upstream library and remove the CSP override.
Thanks for the advice!

I’m almost done. I was able to display “something” but it is working randomly.
The js library I’m using works this way:

First we need to define a <div id='board'> block with a unique id. then we need to launch a function to populate the div, something like this. PGNV.pgnBoard('board', {});. The script requires the same id to populate it.

If I understand correctly, I cannot do this while decorating the cooked block, because the document is not yet populated and the PGNV object is unable to find the block. So I used first a widget (doesn’t work, because the HTML function must return the block, and I don’t have it), then I switched to a debounced function: debounceFunction(this, renderPgn, attrs, 200);.

But it works “sometimes”.

What am I missing?


debounceFunction delay calling the target method until the debounce period has elapsed with no additional debounce calls, it sometimes won’t work may still because

debounce may not be the solution in your use case

Have a try to use the afterAdopt options

1 Like

Hi Hawn, thank your for your hint!
It seems exactly what I was looking for. I tried two different approach:

  • first use the normal “decorateCooked” to parse and attach the div block and then the afterAdopt to render by calling PGNV.pgnviewer() or
  • use only the afterAdopt option to both attach and render

none worked for yet another issue that I’m, unable to fully grasp, it seems the inner library crashes with an error, unable to find className attribute apparently in a DOM element. I’m a bit lost :slight_smile: . I’ve seen that someone use loadScript, but I just import the library and using the debounce function just worked. What is the difference between the two methods?


Hi @hawm , I’ve found why it doesn’t work and I’m stuck.
Of course I’m totally new on Ember and on recent js in general. So I studied a bit and find out the reason.
PgnViewerJs expect to work on real DOM, not on Virtual DOM. So even after the cooked element has been adopted, it isn’t present in the real DOM, and PGNV search for IDs by using document.getElementById.
I studied everything I could find out: ember components seems to fit my needs, but I’m unsure how to handle it.
Or perhaps API.onAppEvent ?


I used later() from ember and though it’s a dirty hack, it works.
Any better solution?

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.