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

CSS + code splitting + multiple entry points #608

Closed
iamakulov opened this issue Dec 17, 2020 · 8 comments · Fixed by #1293
Closed

CSS + code splitting + multiple entry points #608

iamakulov opened this issue Dec 17, 2020 · 8 comments · Fixed by #1293

Comments

@iamakulov
Copy link

Background

Hey! Per the changelog, ESBuild doesn’t currently support code splitting with CSS:

0.7.7

Some things to keep in mind:

[...]

  • There is currently no support for code splitting of CSS. I haven't tested multiple entry-point scenarios yet and code splitting will require additional changes to the AST format.

Still, a) enabling splitting: true along with b) importing CSS files and c) having multiple entry points results in the following behavior:

  • ESBuild detects CSS code that’s used in multiple entrypoints
  • ESBuild code-splits that code into a common CSS chunk (chunk.HASH.css)
  • but: ESBuild doesn’t generate any code that makes the browser load these chunks

Which means that when you open the app, the common CSS code ends up simply missing.

Question

I’m curious what’s your plan on supporting this scenario.

  • Do you plan to generate CSS @imports for code-split CSS chunks?
  • Do you plan to disable code-splitting for CSS at all?
  • Or do you have any other plans for this?

I’m asking this primarily because I’m helping Framer to migrate to ESBuild, and if I know the plan, it would be easier to pick the right workaround while the plan is being implemented!

@evanw
Copy link
Owner

evanw commented Dec 18, 2020

My working plan so far is to generate CSS @imports for code-split CSS chunks. But I haven't researched what other tools do yet so I'm not sure what the current state of the art is. Certainly just disabling code-splitting for CSS would also work. I'd be interested to hear more information about your use case and what you're looking for if you have any thoughts.

@techgems
Copy link

Following, I would love to ditch webpack and those long long wait times per build, but we need CSS as a valid entrypoint to do that.

@michaelvillar
Copy link

As a workaround for the time being, one way to use code splitting and CSS is to build twice:

  • build with splitting: true and loader.css: 'file' → will output the js with chunks
  • build with splitting: false → will output the css files (and extra js we can ignore)

You can also output these two builds in two location and do them concurrently which is neat.

@LukeSheard
Copy link
Contributor

@evanw I'd love to help out with this if possible - but a little lost as to how to get started. We're currently using the workaround @michaelvillar suggested currently but isn't scaling as we build more applications at once.

@evanw
Copy link
Owner

evanw commented Feb 11, 2021

I'd love to help out with this if possible - but a little lost as to how to get started.

The best way to help at this point would be to help me figure out the desired behavior. I'm not too familiar with the history and design space here or how other bundlers solve CSS ordering problems.

CSS is order-dependent and I assume CSS order should be determined by a depth-first post-order traversal over the graph, including from JS files into the CSS files they import (ignoring top-level await). I also assume that it's not ok for the bundler to deviate from this order at all since that could result in visual changes, even if deviating from this order would make a smaller bundle.

Some approaches to CSS code splitting that I can imagine:

  1. Just generate a separate CSS file for each entry point without any code splitting or sharing. Common CSS would be duplicated across separate entry points. Mostly simple and straightforward.

    One decision point here would be what to do about dynamic JavaScript imports that reference CSS. They are sort of considered additional entry points, albeit not user-specified ones. I believe having those entry points re-specify any CSS in the importing entry point (at least by appending a <style> to the <head>) could potentially cause visual changes which is something to be avoided. So dynamic entry points can't be treated the same way as normal entry points.

    The simplest way to solve this is just for dynamic entry points to not have any associated CSS, and to bundle the CSS for all dynamic entry points into the CSS for the top-level entry point. Then there would only ever be a single CSS file per page which makes order easy to reason about.

    A less simple way to solve this would be to try to exclude any CSS in the top-level entry point from the dynamic entry point. But this seems kind of crazy because that dynamic entry point could be imported from several top-level entry points each with it's own subset of CSS, in which case you would be unable to share the dynamic entry point's CSS between top-level entry points.

    Perhaps there is another way this is usually solved too. Maybe the answer is for dynamic entry points to prepend their <style> to the <head> so that duplicates don't cause visual changes? I'm not sure if that could cause any issues.

  2. Layout the CSS order for each entry point, then scan for common runs of CSS across these entry point layouts. Each contiguous run of CSS code that appears in more than one entry point layout would be a separate shared chunk that the root chunks reference with @import. This guarantees that there is no duplicate code across entry points and that the CSS order remains exactly the same. But there would potentially be a huge number of CSS chunks if there are a couple dozen entry points due to combinatorics, which I assume would be a non-starter for most people. Unfortunately CSS doesn't have any mechanism for abstraction so you can't have a "CSS library" with lazily-evaluated code that can be triggered later.

  3. Do everything with JavaScript and do not generate any CSS files. CSS would be in JavaScript strings and <style> tags would be generated at run-time. Since JavaScript does have abstraction capabilities, CSS code could then be deferred until it can be assembled in the correct order. This has no duplication or ordering problems but I assume it would be slow because of JavaScript processing and because the browser can't download, parse, and interpret the CSS in parallel with the JavaScript.

I'm currently considering doing option 1 (no code splitting for CSS).

@LukeSheard
Copy link
Contributor

I ended up landing on a workaround for the time being similar to another comment on the MVP for CSS modules. I'll share the plugin I wrote shortly but it's on my work laptop and I can't extract it right now.

Essentially I would go with something like (3) from above, but use the file loader which exists already to seperate files out into standalone chunks which can be loaded at runtime. For us this is working well as when we build ~300 files in one go the CSS files end up being shared across all the applications.

The only issue that I don't have an answer for with this is preventing the everlasting issue of flashes of unstyled content, since the stylesheets are loaded asynchronously to the execution of the app. I'm not sure how other bundles are solving this at the moment though.

@arcanis
Copy link

arcanis commented Mar 25, 2021

I think Webpack goes with option 1 (disregard dynamic chunks as far as css output is concerned), which seems to be the most straightforward approach (and can be incrementally improved if needed). Plus it's probably what everyone would already imagine when reading "There is currently no support for code splitting of CSS" - the current behaviour is quite confusing.

@Valexr
Copy link

Valexr commented May 18, 2021

🥳

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 a pull request may close this issue.

7 participants