Using resource.Match with multiple JavaScript files

Hello,
I have multiple JavaScript files, such as lightbox.js, clipboard.js, etc., and one main.js file. The main.js file contains only my custom scripts, like toggling a hamburger menu’s visibility, while the other files are third-party scripts.

When I had just one file, main.js, it was simple. I used this:

 {{ $script := resources.Get "js/main.js" | minify | fingerprint -}}
 <script src="{{ $script.Permalink }}" defer></script>

However, when dealing with multiple JS files, I used the following code from the resource.Match documentation, and it worked well:

{{ range resources.Match "js/*.js" }}
  <script src="{{ .RelPermalink }}" defer>
{{ end }}

The problem with this method is that when I wanted to pass the output of the resource to both minify and fingerprint, things became a bit more complicated. I was able to achieve it, but I had to concatenate first, like so:

{{ $js := resources.Match "js/*.js" }}
{{ $js = $js | resources.Concat "script.js" | minify | fingerprint }}
<script src="{{ $js.RelPermalink }}" integrity="{{ $js.Data.Integrity }}" defer></script>

Is this the correct approach? or should I try importing each script with a separate script tag. However, that would likely involve calling resources.Get for each file, right?

Your approach looks good to me.

1 Like

This looks sensible … but I would recommend that you have a look at js.Build:

JS bundling used to be hard, but js.Build is incredibly easy to use and very fast (I’m not taking credit for that, we use the ESBuild library created by https://madebyevan.com/), and I tend to use it even for simple 1 file JS projects, but it really shines if you start to import things or if you want to pass some config to the JS. It works great for importing files from /assets, no need for any NPM (but you can …).

1 Like

Thank you. I appreciate it. I’m not very up-to-date with JavaScript, especially when it comes to imports. I visited the MDN page to understand how it works, but there’s an overwhelming amount of information. From what I gathered, when using import, I can have a main.js file that imports other files, right?

main.js:

import "./mycustomjs";
import "./lightbox"
import "./clipboard"

then I can do this in the template file:

{{ $js := resources.Get "js/main.js" | js.Build | minify | fingerprint -}}
<script src="{{ $js.Permalink }}" defer></script>

It works, but I noticed (of course, after temporarily disabling minify) that the JavaScript code looks a bit different when viewed in “view source” in the browser. It seems like js.Build might have transpiled it or something. For example, some variables are defined with ‘var’ even though I didn’t use that in my code, I used the newer ‘let’ and ‘const’. But I guess if it works, the final result doesn’t matter. I’m not sure if what I did is correct. So, the idea here is to have a main file that imports the other files, right?

Yes. The import statement in JS can be confusing, but the typical use case is importing functions. And ESBuild does tree shaking, so it will try to be smart about only bundling the functions you use. Some synthetic example here:

I would not worry too much about ESBuild/js.Build transpiling your source. You can control the transpiler target compability using the target option. And if you pass minify=true in the options, it minifies, and in my experience does a better job than Hugo’s minify filter.

1 Like

Thank you. I appreciate your time.

As you suggested, I’m using js.Build for minification. It seems to work fine:

{{ $js := resources.Get "js/main.js" | js.Build (dict "minify" "true") | fingerprint -}}
<script src="{{ $js.Permalink }}" integrity="{{ $js.Data.Integrity }}" defer></script>
2 Likes

I’m a fan of a construct ala:

{{ $js := resources.Get "js/main.js" | js.Build (dict "minify" (not hugo.IsDevelopment) -}}
{{ if hugo.IsDevelopment }}
   <script src="{{ $js.Permalink }}" defer></script>
{{ else }}
{{ $js = $js | fingerprint }}
<script src="{{ $js.Permalink }}" integrity="{{ $js.Data.Integrity }}" defer></script>
{{ end }}
1 Like

I tried deploying my website on Render, and I get this error when using fingerprint, even though everything works fine locally:

Error: error building site: render: failed to render pages: render of “page” failed: “/opt/render/project/src/themes/basic/layouts/_default/baseof.html:3:3”: execute of template failed: template: about/single.html:3:3: executing “about/single.html” at <partial “head.html” .>: error calling partial: “/opt/render/project/src/themes/basic/layouts/partials/head.html:11:16”: execute of template failed: template: partials/head.html:11:16: executing “partials/head.html” at : error calling fingerprint: *js.Namespace can not be transformed

I think this integrity option is not accepted by every hosting provider. It seems that I will have to do without it.

No, you have a typo in your template, look for js and make that $js

A related tip: You can test your production builds locally using hugo -e production.

Oh, stupid me. Thank you. It was indeed a typo, although it’s strange how the local server didn’t catch it.

See my tip above.

1 Like

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