Conditionally adding scripts to JS Bundle

Hi, I’ve been trying various routes here but to no avail so I’m hoping someone can help.

My goal is to only load JS when a feature is toggled on using front matter, so for example

show-summary = false //no code is added to bundle for that page
show-summary = true //code is added to bundle for that page.

My current approach is to have a bundle as a slice which I append files to based on an if statement checking the front matter for true or false to determine if it’s appended.

The front matter test is working correctly, I console to ensure that when toggled it changes.

What doesn’t work however is the appending to the slice for the main bundle.
I get no error and the check is clearly working, it’s just the appending of the target script does not work.
Further, when I move the script append outside of the if block, it works fine - suggesting scope is an issue but I cannot understand why as we’re appending to a higher scoped variable not declaring one.

Below is an example block which has had some bits removed for clarity but still is not working

{{ define "jsFile" }}
    {{ $jsBundle := slice }}
    {{ $jsBundle := $jsBundle | append ( resources.Get "scripts/app.js" ) }}

    {{ $showSummaryFlag :=  eq ( .Param "showSummary" ) true }}
    {{ printf "%#v" $showSummaryFlag }}

    {{ if eq $showSummaryFlag true }}
        {{ $accordionCode := resources.Get "scripts/common/accordion.js" }}
        {{ $jsBundle := $jsBundle | append $accordionCode }}

        <script>console.log("Toggled to true")</script>
    {{ end }}
    
    {{ $js := $jsBundle | resources.Concat "scripts/build.js" | resources.Minify }}

    <script>
        {{ $js.Content | safeJS }}
    </script>
{{ end }}

You’re re-defining your bundle every time with :=. Check out the difference between that and a simple =

Yes but I’m re-defining the bundle by taking the current file and appending the new one and then adding it to $jsBundle.

Notwithstanding that it’s probably better to simply assign as you say, when I change it so that it’s not re-defining it, it is still not working.

Below is the code I’ve tested in case I’m missing something

{{ define "jsFile" }}
    {{ $jsBundle := slice }}
    {{ $jsBundle = $jsBundle | append ( resources.Get "scripts/app.js" ) }}

    {{ $showSummaryFlag :=  eq ( .Param "showSummary" ) true }}
    {{ printf "%#v" $showSummaryFlag }}

    {{ if eq $showSummaryFlag true }}
        {{ $accordionCode := resources.Get "scripts/common/accordion.js" }}
        {{ $jsBundle = $jsBundle | append $accordionCode }}

        <script>console.log("Toggled to true")</script>
    {{ end }}
    
    {{ $js := $jsBundle | resources.Concat "scripts/build.js" | resources.Minify }}

    <script>
        {{ $js.Content | safeJS }}
    </script>
{{ end }}

Why do want a different JS bundle for each page?? Surely the end result is that the user then ends up downloading multiple bundles that will contain largely the same code? This does not seem optimal.

The thinking is the code willl be inlined and will be limited to features which appear on the page.

For example, if a FAQ accordion was toggled off, then the code would not be inlined.

Your first variant redefined jsBundle inside the if, and that variable went out of scope after the if.

Sorry but can you explain what you mean by the first variant?

Are you essentially saying that within the scope of the if $jsBundle is a new variable and so the one outside of the scope is not being updated?

If so, I need help with how to pass back to the outer scope version

This

Yes.

Ok thank you that’s instructive.

Is their a preferred way to update the externally scoped slice?

I’ve so far tried using a .Scratch to assign the $accordionCode value from inside the if (instantiating it outside the IF so it’s not local again) and then to append that output to the jsBundle slice (again once outside of the if) but it kept showing the below complaint.

error calling Concat: slice interface {} not supported in concat

Essentially, what you’re doing in the second variant:

{{ x := slice }}
{{ if some condition }}
{{ x = x | append whatever }}
{{ end }}
   use x here

The error message is perhaps not related to this.

Yeah so I’ve tried what you’ve suggested before posting as it’s what Googling the issue suggested.
The issue, as I understand it, there is that resources.Concat doesn’t support a slice Interface being what’s passed. So when the slice you pass to resources.Concat has a value which is itself a slice it complains that it’s not happy with that.

Where I think it is presently is that a slice doesn’t work as you cannot pass it as a value to a slice to concatenate and Scratch’s are also not working for similar reasons, so I’m going to have do individual blocks for each component. It’s not as elegant as being able to append it onto a bundle but not sure I can get around it

From the documentation, I’d conclude that concat expects a slice as its parameter. Maybe one of your Get calls does not what you expect (typo or so?).

Yeah so it’s fine walking through a slice, it doesn’t seem to be happy about a slice being one of the values from the slice.

Put another way, it’s fine going through an array concatenating, it’s not happy when one of the values in the array is another array… which if all you’re doing is appending does make sense.

I’ve tested the .Get functions outside of the scope of the IF block and it works, the issue there is it’s not conditionally appended.

Simplify, etc…

{{ $jsBundle := slice (resources.Get "scripts/app.js") }}
{{ if .Params.showSummary }}
  {{ $jsBundle = $jsBundle | append (resources.Get "scripts/common/accordion.js") }}
{{ end }}
{{ $jsBundle = $jsBundle | resources.Concat (printf "%d.js" math.Counter) | minify }}
<script>
  {{ $jsBundle.Content | safeJS }}
</script>

Try it:

git clone --single-branch -b hugo-forum-topic-47707 https://github.com/jmooring/hugo-testing hugo-forum-topic-47707
cd hugo-forum-topic-47707
hugo server

The above has a problem. Give me a minute…

1 Like

I’ve corrected the problem with the example above.

In addition to not rendering .Content, there was a caching problem because you were using the same path with resources.Concat regardless of content.

Thank you its now working

This is better; there are only two unique possibilities…

{{ $path := "temp_a.js" }}
{{ $jsBundle := slice (resources.Get "scripts/app.js") }}
{{ if .Params.showSummary }}
  {{ $jsBundle = $jsBundle | append (resources.Get "scripts/common/accordion.js") }}
  {{ $path = "temp_b.js" }}
{{ end }}
{{ $jsBundle = $jsBundle | resources.Concat $path | minify }}
<script>
  {{ $jsBundle.Content | safeJS }}
</script>

Not sure i follow the purpose of the $path variable, is there somewhere i can look to better understand why its necessary.

I just noticed in the docs that both resources.FromString and resources.ExecuteAsTemplate include this note:

The resource is cached, using the target path as the cache key.

That note is missing from the docs for resources.Concat. I will change the docs in a few minutes.

When you use the same path (cache key) with both conditions (showSummary = true/false), the final result depends on which page was rendered first, which is indeterminate due to concurrency.

You’d have the same problem if you were publishing (writing to disk) the final result instead of inlining the JS.