evanw / esbuild

An extremely fast bundler for the web
https://esbuild.github.io/
MIT License
38.16k stars 1.15k forks source link

A way to force a file to appear in the inputs section of the metafile #2058

Open bschlenk opened 2 years ago

bschlenk commented 2 years ago

Hi Evan! My team uses a custom plugin to handle css modules. How it works from a high level:

This works pretty well. But recently, I found that if I change the js to export each css class individually, it reduces our bundle size by about 5%.

return { contents: `export const a = 'file--a--hash'; export const b = 'file--b--hash'` }

The problem here is that sometimes we import css only to include it, not to use any of the exported classes. This would be for global styles and such, where we just import the file:

import 'styles.css'

In this case, the styles.css file doesn't appear in the metafile (because esbuild considers it unused?). I think this works with the module.exports = {...} style because common js modules don't get tree shaken at all. With this assumption, I thought I could force esbuild to include css files that don't contain classes by returning module.exports = {}. This only works if the css file doesn't contain any classes. If it has classes, then we're back to using export const, and esbuild tree shakes the css file out of the metafile.

That brings us to the title of this issue. I'm looking for a way to force a file to appear in the metafile, even if it appears to be unsed from esbuild's perspective. Alternatively, if I knew which entryFile a file belonged to inside of onLoad, then I could build up that mapping in the onLoad calls, instead of relying on the metafile at all. Maybe a workaround already exists for this?

hyrious commented 2 years ago

Support for CSS module is still on the road in esbuild. One hack I could imagine is to distinguish css "module" and "style", e.g. change the import path to "style.css?module", then use a plugin to load that file into this result:

import "style.css"
export const a = "class--a"

This way, we rely on the esbuild default behavior to concat css files.

As of the alternative way you said, it's maybe not obvious but a css file can be imported by many entry points, in which case esbuild will generate many copies of one css file.

bschlenk commented 2 years ago

Interesting, it's possible the the existing concat css behavior would work for us here. In that case I think we'd need to do some post processing on the merged css file to handle some of the remaining work we do, which can probably be done in the onEnd callback.

Css aside, I think it'd be useful to know at the end of a build what entrypoints an extra emitted file belongs to. It kinda reminds me of webpack's emitFile api that lets webpack track how a file was added to the build.