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

Incorrect css order with @import #1391

Closed
dirslashls opened this issue Jun 23, 2021 · 3 comments
Closed

Incorrect css order with @import #1391

dirslashls opened this issue Jun 23, 2021 · 3 comments

Comments

@dirslashls
Copy link

I have a .tsx file that looks like

// some-third-party imports
import './some.css'
// some of my js code that itself imports css

The 'some.css' file is something like

@import url("internet-url-1")
@import url('internet-url-2")

@import url('relative-path-1')
@import url('relative-path-2')

I was expecting the final bundled css to be

@import url("internet-url-1")
@import url('internet-url-2")

// inline css for relative-path-1
...
// inline css for relative-path-2
...

// all other css from the js code

However, I am noticing the following order

@import url("internet-url-1")
@import url('internet-url-2")

// all other css from the js code

// inline css for relative-path-1
...
// inline css for relative-path-2
...

This is problematic for me as the css included from the js code should supersede the other css in case of a conflict.

@dirslashls
Copy link
Author

Note that I get the correct order when bundled with rollup using rollup-plugin-postcss

@evanw
Copy link
Owner

evanw commented Jun 23, 2021

Here's my attempt to reproduce this:

$ cat index.tsx
import './some.css'
import './js-code'

$ cat some.css
@import url('http://internet-url-1');
@import url('http://internet-url-2');
@import url('./relative-path-1');
@import url('./relative-path-2');

$ cat relative-path-1.css
.relative-path-1 { color: green }

$ cat relative-path-2.css
.relative-path-2 { color: blue }

$ cat js-code.js
import './other.css'

$ cat other.css
.other.css { color: red }

$ esbuild --bundle index.tsx --outdir=out

However, I can't reproduce the problem. Here's what I get:

@import "http://internet-url-1";
@import "http://internet-url-2";

/* relative-path-1.css */
.relative-path-1 {
  color: green;
}

/* relative-path-2.css */
.relative-path-2 {
  color: blue;
}

/* some.css */

/* other.css */
.other.css {
  color: red;
}

In this case all other css from the js code comes after inline css for relative-path-1 and inline css for relative-path-2. Is there a change to this example case that will reproduce the issue? Or another full test case you can provide that demonstrates the issue?

This is problematic for me as the css included from the js code should supersede the other css in case of a conflict.

That's not necessarily how CSS imports work. CSS import order is inherently different than JS import order, which could be why you're seeing this. In CSS, @import does not import the file only once in the place of the first @import like JS. Instead it imports the file every time an @import is encountered. Because all imports except the last one are irrelevant, this effectively means it imports the file only once in the place of the last @import (not the first @import).

That means if multiple files import the same shared dependency and then override it in various ways, only the last file to do so will override anything. This is inconvenient but is how CSS actually works (i.e. in the browser). The behavior of @import in the browser is the same behavior used by esbuild when bundling.

Note that I get the correct order when bundled with rollup using rollup-plugin-postcss

PostCSS actually gets CSS import order wrong in some cases. So the behavior you're seeing from Rollup isn't necessarily correct. The incorrect behavior was introduced here: postcss/postcss-import#211. That change made CSS import order identical to JS import order. While that's convenient, it's also incorrect.

You can configure PostCSS to use the correct CSS import order by using skipDuplicates: false. But that causes you to get a duplicate copy of each import for every @import that references it, which is unnecessary file size bloat. It also causes PostCSS to hang if there are any circular imports. These problems are both avoided by esbuild's CSS import order without compromising on correctness.

@dirslashls
Copy link
Author

Seems like this is fixed in 0.12.6 via #1342 . I was using 0.12.5.

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

No branches or pull requests

2 participants