Esbuild imports entire JS file when I request to import only one function

In my current project we need a “stripped down” version of a particular JS which we can execute on a page that isn’t part of the rest of our website. We want things like navbar functionality on that page, but we don’t need or want the rest of our do-everything.js file running since the page would be missing many elements that that file is expecting to exist.

I thought this would be a perfect use-case for esbuild as I could simply export the functions I wanted and then create a new JS file that simply imports the functions I actually want and leaves the rest. (Let’s call that exported.js.) However, while it seemed like this was working initially, I’ve recently started getting errors from exported.js and upon further investigation, it turns out the file contains basically the entirety of the do-everything.js file, including the functions that that file imports from elsewhere.

In order to check that I wasn’t causing this with my own screwup I ran a test by changing the export in do-everything.js to the following:

function test() {
  console.log("Hello world");

export {test};

And then my exports.js file had the following:

import {test} from "./global";


and of course I have a pretty standard usage on the Hugo side:

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

After running hugo --ignoreCache I’d expect to find an exports.js file in my build folder that consisted of basically just one function. Instead, I find that exports.js contains over 600 lines of code, which I assume is basically the entirety of my 900+ line do-everything.js file with the comments and various linebreaks stripped out.

So either I’ve misunderstood how esbuild works with Hugo or this is a pretty gigantic bug that has completely broken treeshaking. (I should note that according to the esbuild docs, treeshaking can’t even be turned off, so this couldn’t simply be an issue of bad settings.)

I wanted to check in here before I went and posted this to issues as a bug, just in case there is something I’m missing here. Thanks for any help y’all may be able to provide.

Notes on my setup:
hugo v0.82.0-9D960784+extended windows/amd64 BuildDate=2021-03-21T17:28:04Z VendorInfo=gohugoio

So, “tree shaking” certainly works (at least on the binary level), I tested it just now, and I don’t think there is a setting to turn it off (the only difference I find between mine and your project is that I set the target to es2016 in the options passed to js.Build, but I don’t see how that should have anything to say{.

I have not looked at your case, though, but I suspect there is a detail in there that prevents ESBuild from “shaking it” (e.g. a window.myFunc = somefunc. Hard to guess without additional context. Note that I’m no ESBuild expert, even though I have spent a fair amount of hours integrating with it.

Perhaps I was wrong to pin this on tree shaking. I do reference window in do-everything.js so I understand that those sorts of references would not be disposed of if I was importing the entire file. However, since I’m only asking to import the function test, shouldn’t the rest of the file that test is from not be included in the import?

I would think so, but this is a question better suited on ESBuild’s issue tracker. I do suspect, thought, that any function/variable reference that is assigned to window that ESBuild finds on its search is kept.

1 Like