Skip to content

Add css.Build (using ESBuild to transform CSS resources)#14610

Draft
bep wants to merge 1 commit intogohugoio:masterfrom
bep:feat/cssbuild-14609
Draft

Add css.Build (using ESBuild to transform CSS resources)#14610
bep wants to merge 1 commit intogohugoio:masterfrom
bep:feat/cssbuild-14609

Conversation

@bep
Copy link
Member

@bep bep commented Mar 9, 2026

  • Add engines option: Slice, or engine/version combined, e.g. chrome58 ... EsBuild has 1 target and multiple engines put into one flag: https://esbuild.github.io/api/#target I think it makes most sense for us to split it, which matches the Go API. I need to check what the sensible default here is, but for CSS the relevance is syntax transforms and browser prefixes.

Fixes #14609

Notes for docs

All relevant options:

  • minify
  • loaders
  • externals
  • engines
  • targetPath
  • sourceMap (need to test)
  • sourcesContent (need to test)

TODOs

  • Sensible default loaders for CSS, ref Unexpected "\x89"
  • Make the file loader do a sensible thing. Note that this fix also would be relevant to JS.
  • Collapse engines into target.
  • Test source maps.
  • Add sensible browser defaults for CSS. I found this list in another project using ESBuild: "chrome80" "firefox73" "safari13" "edge80". A little on the conservative side, but it's important that this has some values to get any browser prefixing.
  • Test the jsconfig generation and see if that works/has relevance for this in VS code vs import resolution.
  • Add default none to sourcemap opts.

@bep bep force-pushed the feat/cssbuild-14609 branch 3 times, most recently from 379cb16 to 881d5fd Compare March 9, 2026 17:34
@bep bep changed the title Add support for CSS files as entry files in js.Build Add css.Build (using ESBuild to transform CSS resources) Mar 9, 2026
@bep bep force-pushed the feat/cssbuild-14609 branch 5 times, most recently from 55717df to 8b2ecf5 Compare March 9, 2026 21:09
@jmooring
Copy link
Member

jmooring commented Mar 10, 2026

Request:

According to the esbuild docs, sourceMap is one of linked, external, inline, or both. Can you please add none to the list of allowable values? In our docs it's easier to say "Default is none" instead of something like "Default is an empty string, which means don't create a source map."

Question:

For the engines slice/list, is the default behavior (an empty slice) to skip transformations? Also, I'd be inclined to call the option "target" to match the esbuild docs. Every option in our documentation will have a "see details" link to the corresponding section in the esbuild docs; it would be nice if our option name matched theirs. Whether we use singular or plural forms is irrelevant.

EDIT: It would be great to be able to use browserslist instead of having to update targets over time.

@jmooring
Copy link
Member

jmooring commented Mar 10, 2026

I wanted to create a simple "loaders" example for the docs:

Without any loaders, with this CSS:

body {
  background: url('../images/circle.png') no-repeat center center fixed;
}

I get this:

CSSBUILD: failed to transform "/css/main.css" (text/css): "/home/jmooring/code/hugo-testing/assets/images/circle.png:1:0": Unexpected "\x89"

I think \x89 is the first part of the file signature.

As a guess, I defined a loader for .png files:

{{ $opts := dict "loaders" (dict ".png" "dataurl") }}

That works great. But for a larger file I may not want to embed the data. So I tried this:

{{ $opts := dict "loaders" (dict ".png" "file") }}

But then the published CSS file (public/css/main.css) is the PNG file with a .css extension.

Admittedly I have zero experience with this, so there may be a better way to handle it, but I'm wondering if we want a default map of loaders for common file formats. This assumes that we could somehow make the file loader do what we want. It would be great if users didn't run into errors like Unexpected "\x89".

@bep
Copy link
Member Author

bep commented Mar 10, 2026

Also, I'd be inclined to call the option "target".

I'm not totally sure. It makes the option parsing/documentation fuzzy. These are 2 very distinct things in the ESBuild Go API and target (one value, e.g. ES2020) is a JavaScript only option. engines is used for both (I think).

EDIT in: Yes, I will rename it to target, and make it into a string or a slice.

It would be great if users didn't run into errors like Unexpected "\x89".

I agree, I will do more testing on this today.

Thanks for your detailed feedback.

@bep bep force-pushed the feat/cssbuild-14609 branch from 96519f2 to f918bac Compare March 10, 2026 11:50
@bep
Copy link
Member Author

bep commented Mar 10, 2026

@jmooring have added a list of checkboxes in the first comment; if it's checked it means that I have force pushed a fix.

@bep
Copy link
Member Author

bep commented Mar 10, 2026

For the engines slice/list, is the default behavior (an empty slice) to skip transformations?

It's not documented, but that seems to be the behavior.

EDIT: It would be great to be able to use browserslist instead of having to update targets over time.

Yea, (big) maybe. The great thing about this particular new feature is that it does not (or: it does not have to) depend on any extra mumbo jumbo (e.g. npm install this and that).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add css.Build

2 participants