Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support ESBuild Code splitting #7499

Open
Vincent-Carrier opened this issue Jul 20, 2020 · 10 comments
Open

Support ESBuild Code splitting #7499

Vincent-Carrier opened this issue Jul 20, 2020 · 10 comments
Assignees
Milestone

Comments

@Vincent-Carrier
Copy link

Vincent-Carrier commented Jul 20, 2020

Currently, these are the only options supported by js.Build https://godoc.org/github.com/gohugoio/hugo/resources/resource_transformers/js#Options

However, ESBuild exposes all of these. There's a lot of good stuff in there, including control over the output format (presently defaulting to IIFE), code-splitting and sourcemap support. Code-splitting is especially useful since it prevents users from re-downloading the same code twice. Sourcemaps are another important one for debugging.

@remko Thanks for the new feature BTW, it's really great!

@Vincent-Carrier Vincent-Carrier changed the title Support all build options exposed by the ESBuild API (output format, code splitting, etc.) Support more build options exposed by the ESBuild API (output format, code splitting, etc.) Jul 20, 2020
@bep bep added Enhancement and removed Proposal labels Jul 20, 2020
@bep bep added this to the v0.75 milestone Jul 20, 2020
@bep
Copy link
Member

bep commented Jul 20, 2020

Just a quick note that the above is not about just adding some more options -- which is the reason they're not there in the first place.

  • For source maps we could probably replicate what we do in ToCSS.
  • For code splitting, If I understand it correctly, it will produce multiple output files where the imports will be stable, so to speak.

@remko
Copy link
Contributor

remko commented Jul 20, 2020

The only thing that could be 'just adding some options' is inline sourcemaps (which would still leave one output file). However, this is only sensible in a conditional in development mode, so I left it out of the initial patch as well.

@bep bep self-assigned this Jul 21, 2020
@bep
Copy link
Member

bep commented Jul 21, 2020

I have been looking at the code spitting "thing" (which I assume will make this really interesting).

The challenge with this one is that it requires multiple entry points to be usable/interesting. But I'm not totally sure how to express this.

Thinking out loud:

{{ $entryPoints := resources.Match "**/index.js" }}
{{ $js := resources.Get "home/index.js" }}

{{ $js = $js | js.Build (dict "entryPoints" $entryPoints) }}

Would the above be clear enough? /cc @regisphilibert

@Vincent-Carrier
Copy link
Author

Vincent-Carrier commented Jul 21, 2020

I think I got a bit confused in regard to code-splitting. From what I now understand, Code-splitting is when you use async imports, e.g.

const Home = lazy(() => import('./routes/Home'));
const About = lazy(() => import('./routes/About'));

This is nice for web apps, but for blogs and documentation the use cases are more limited.

Chunking is the idea of having more than one file in your bundle output, so that you get better caching / less code duplication.

Code-splitting in ESBuild still seems experimental and limited to the ESM output format. Chunking is something you can do manually with re-exporting e.g.

// my-utils.js
export { range, take } from 'lodash-es'

becomes something like

// my-utils.bundle.js
// <big bundle of code>
export const range = ...
export const take = ...

However it's a bit unclear how one would make that work in the present context, when all your JS source files are in your assets folder. You need to tell ESBuild where to split your code, which if I understand correctly is what "entry point" alludes to (if that's the case, then I propose we use a different terminology because something like my-utils.bundle.js is clearly not an entry point into my program).

Since chunks / entry points are gonna be shared across your project, maybe it would make sense to declare them globally in config.toml.

@regisphilibert
Copy link
Member

I'm fairly limited in the JS realm, so my input albeit naming and UX might not be too acute.

@Vincent-Carrier
Copy link
Author

Thought about this some more:

You can mix both patterns together e.g.

// my-utils.js
export { range, take } from 'lodash-es'

and then in your consumer code

const { range, take } = await import('./my-utils')

The advantage here is that you only need to declare your entry points / chunks in your source files. The disadvantage is that you're using async/await for something that doesn't really need to be.

@bep
Copy link
Member

bep commented Jul 21, 2020

@Vincent-Carrier Re. your last post, given that you have more than a few external dependencies (which I I see is not uncommon in the crazy world of JavaScript), I think you quickly will get a "management problem" doing this in Hugo (keeping track and building the chunks ...).

I don't think it's very uncommon, even for a documentation site, to have page or section specific JS code, and to be able to just import directly from the NPM dependencies without having to worry (too much) about the bundle size would be golden (to me).

But I think this problem needs to mature a little bit (both here and on the ESBuild side) before we attack it.

@Vincent-Carrier
Copy link
Author

Vincent-Carrier commented Jul 21, 2020

@bep Completely agree.
My website only has two dependencies at the moment (lit-html and rough-notation), and so far I've been shipping them unbundled without any performance problems. The problem with the JavaScript ecosystem at the moment is that so many packages are either straight up impossible to use without a bundler or they hurt performance by deeply chaining imports. It's been a fun challenge to work around that constraint (kind of like the JavaScript equivalent of going vegan), but eventually there are times when a bundler is the only solution that makes sense.

@remko If ever you'd like to walk me through your implementation, I'd be really excited to try and build a proof of concept myself (I'm completely new to the Hugo codebase).

@bep bep changed the title Support more build options exposed by the ESBuild API (output format, code splitting, etc.) Support ESBuld Code splitting Jul 21, 2020
@bep bep changed the title Support ESBuld Code splitting Support ESBuild Code splitting Jul 21, 2020
bep added a commit to bep/hugo that referenced this issue Jul 21, 2020
Mostly to minify cache hash breakage.

Updates gohugoio#7499
bep added a commit that referenced this issue Jul 22, 2020
Mostly to minify cache hash breakage.

Updates #7499
@bep
Copy link
Member

bep commented Jul 22, 2020

@Vincent-Carrier I have split these "options tasks" into separate issues. The Format part is merged, for source maps, see #7504

@earthboundkid
Copy link
Contributor

FWIW, my news site has run into a "chunking" issue already. I have two JS entrypoints: main.js (which is Babel'd for IE11) and enhancements.js (which requires a modern browser). Both of them end up importing metrics.js, which does some localStorage stuff to figure out when you were last on the site if ever. It's not the end of the world in my case, but in general, it would be better if metrics.js were only executed once, so you're not wasting time doing the same calculations twice. (Downloading once would also be nice, but not if means that two requests become three.)

Anyway, all this is to say that even for a "simple" site, JS dependencies can get hairy pretty quickly.

@bep bep modified the milestones: v0.75, v0.76 Sep 14, 2020
@bep bep modified the milestones: v0.76, v0.77 Oct 6, 2020
@bep bep modified the milestones: v0.77, v0.78 Oct 30, 2020
@bep bep modified the milestones: v0.117.0, v0.118.0 Aug 30, 2023
@bep bep modified the milestones: v0.118.0, v0.119.0 Sep 15, 2023
@bep bep modified the milestones: v0.119.0, v0.120.0 Oct 4, 2023
@bep bep modified the milestones: v0.120.0, v0.121.0 Oct 31, 2023
@bep bep modified the milestones: v0.121.0, v0.122.0 Dec 6, 2023
@bep bep modified the milestones: v0.122.0, v0.123.0, v0.124.0 Jan 27, 2024
@bep bep modified the milestones: v0.124.0, v0.125.0 Mar 4, 2024
@bep bep modified the milestones: v0.125.0, v0.126.0 Apr 23, 2024
@bep bep modified the milestones: v0.126.0, v0.127.0 May 15, 2024
@bep bep modified the milestones: v0.127.0, v0.128.0 Jun 8, 2024
@bep bep modified the milestones: v0.128.0, v0.129.0 Jun 21, 2024
@bep bep modified the milestones: v0.129.0, v0.131.0 Jul 22, 2024
@bep bep modified the milestones: v0.131.0, v0.133.0 Aug 9, 2024
@bep bep modified the milestones: v0.133.0, Unscheduled Aug 29, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

5 participants