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 Importing effectively broken #1165

Closed
jssee opened this issue Apr 10, 2018 · 48 comments
Closed

🐛CSS Importing effectively broken #1165

jssee opened this issue Apr 10, 2018 · 48 comments
Labels

Comments

@jssee
Copy link

jssee commented Apr 10, 2018

The way Parcel's CSS import is currently being implemented broken. There are several issues related to this problem. Basically, Parcel is applying postcss transforms before concatenating the CSS, this means that any CSS that gets imported is not being transformed properly. This breaks functionality and won't allow you to use basic features like css-variables if they are being imported anywhere.

related to this:
#609
#593
#329

🤔 Expected Behavior

I should be able to declare variables in one file and use them in another via @import without getting undefined errors

😯 Current Behavior

Parcel is not transforming variables that are in imported css files.

💁 Possible Solution

Concatenate the CSS files (i.e. perform import) before applying transforms. Or let users use the postcss-import plugin from their own config to get things working properly.

@irritant came up with a patch that was as simply commenting out the import gathering during the collectDependencies() process, this should perhaps be the default until something is figured out.

💻 Code Sample

other.css

:root {
  --varColor: #639;
}

input.css

@import './other.css';

body {
  color: var(--varColor);
}

output.css

body {
  color: var(--varColor);  /* should be #639 */
}

some more examples can also be found in #329

@DeMoorJasper
Copy link
Member

DeMoorJasper commented Apr 10, 2018

What about gathering variables using postcss and applying them down the tree? Gathering in generate and applying in postGenerate...
This way parcel won't lose it's parallel behaviour, yet support this?

@jssee
Copy link
Author

jssee commented Apr 10, 2018

@DeMoorJasper I'm not sure, another thing i thought about was doing running collectDependencies() inside preTransforms() but i have no idea if that would work.

@irritant
Copy link

irritant commented Apr 10, 2018 via email

@jssee
Copy link
Author

jssee commented Apr 10, 2018

@irritant I admire your commitment to Parcel but I think at that point it might be easier to just use Gulp as the build tool or even plain Webpack. I have to make that decision myself since unfortunately I cant stop work waiting for this issue gets fixed, Parcel was really great for everything else though.

@DeMoorJasper
Copy link
Member

If parcel isn't responsible for collecting and parsing the dependencies it doesn't know about it resulting in the manual force recompiling.
Not sure what even makes this work, are you using sass?

@michael-ciniawsky
Copy link

.postcssrc.js

const variables = {
  '--red': '#12345'
}

module.exports = {
  plugins: [
     require('postcss-custom-properties')({
         variables // <= Available to all modules (@imports)
     })
  ]
}

⚠️ Don't use a postcss-import-* plugin as parcel collects the dependencies (@imports)

@jssee
Copy link
Author

jssee commented Apr 10, 2018

@DeMoorJasper No sass, just a simple postcss.confg.js which is only running postcss-preset-env to "polyfill" the CSS. I've tried switching to postcss-cssnext to do the same thing but the issue persist, and the same thing will happen if you try to use PreCSS and or even postcss-simple-vars

@devongovett
Copy link
Member

devongovett commented Apr 10, 2018

Parcel processes each CSS file independently, which is why your imported variables aren't being replaced. This strategy makes it faster since we can process each CSS file in parallel rather than one huge concatenated at the end.

Seems like CSS always allowed each file to be processed independently until variables were added. I can't think of another case where this is a problem. Perhaps we could detect if postcss variables are being used and enable a post-processing step in the CSSPackager to replace variables. Other postcss plugins would still be applied to each individual file. This would require re-parsing the concatenated css though, which would be sub-optimal.

@devongovett
Copy link
Member

Actually that probably wouldn't work since other postcss plugins may rely on the variable already being replaced earlier. e.g. a plugin that inlines calc() calls might expect calc(var(--my-var) + 2) to have the variable inlined first so it could do the calculation ahead of time.

We definitely don't want to do all postcss processing on the final concatenated source though. hmm...

@jssee
Copy link
Author

jssee commented Apr 10, 2018

@devongovett What is the reason we dont want postcss processing on the final concatenated file? Is that not how every other tool does this type of stuff?

@irritant
Copy link

@jssee For me, the minor inconvenience of maintaining my fork and running a separate gulp task is worth Parcel's other benefits.

My gulp task is:

gulp.task('invalidate-css-index', function() {
  gulp.src('./src/css/index.css').pipe(gulp.dest('./src/css'));
});

gulp.task('watch-css', function() {
  gulp.watch(['./src/css/**/*.css', '!./src/css/index.css'], ['invalidate-css-index']);
});

Piping index.css to itself (basically equivalent to saving the file) is enough to make Parcel recognize that the file has been touched and recompile the CSS. With collectDependencies commented out, postcss-import handles the imports and allows later postcss plugins to work properly.

@jssee
Copy link
Author

jssee commented May 11, 2018

@devongovett any progress on this?

@twoixter
Copy link

I recently discovered Parcel and used it from last couple days (it's great!) and I'm also banging my head around CSS issues. Here's my 2 cents, specially after reading @devongovett comment:

Parcel processes each CSS file independently, which is why your imported variables aren't being replaced. This strategy makes it faster since we can process each CSS file in parallel rather than one huge concatenated at the end.

Although clever, and desirable, this is not how CSS is generally processed. CSS is not JS and although PostCSS could come close (I don't know enough about PostCSS), certainly LESS is not.

AFAIK CSS @import does not work like ES6 import. At least, not semantically. This is illustrated by the following LESS snippet:

@background-color: red;

body {
    background: @background-color;
}

@background-color: blue;

What would the processed body { background: ??? } be in this snippet? Well, the color is blue processed with LESS and red processed with SASS. (OK, SASS uses $ for variables, bear with me).

In other words, although the snippet does not have any @import I think you get the point: variables in LESS are "lazy" processed, so they get the LAST assigned value even if it is assigned at the very end of the last .less file. SASS on the other hand replaces variables with the value they had at that moment while processing.

Both LESS and SASS require processing the whole thing anyway. LESS for obvious reasons: it requires a 2-pass compilation. But as a general rule of thumb CSS processors never had the notion of "scopes" so importing a CSS file does not open a new "scope" or whatever. It is expected for a .less / .sass file to get access to the "global" namespace, so to speak, so you don't have to @import "variables" in every single file.

@mendrik
Copy link

mendrik commented May 24, 2018

not sure if it helps anyone but I have published a small postcss plugin as a temporary work around for my project: npm install postcss-parcel-import
then add it to your .postcssrc or config:

{
    "plugins": {
        "postcss-import": {},
        "postcss-parcel-import": {},
        "postcss-mixins": {},
        "postcss-nested": {},
        "autoprefixer": {},
        "cssnano": {},
    }
}

and in the pcss files use then @parcel-import '.../../mixins.pcss'

basically it hides it from parcel importing and will substitute the rule with the file contents before other plugins are run.

This is just a temporary solution until this bug is solved.

Source code under MIT: https://github.com/mendrik/postcss-parcel-import

@cmnstmntmn
Copy link

cmnstmntmn commented Aug 27, 2018

my solution was to build in two steps.

  "scripts": {
    "build": "yarn run parcel && yarn run postcss",
    "postcss": "postcss -c scripts/postcss.config.js --replace src/css/app.css -o dist/*.css",
    "parcel": "parcel build index.html"
  }

and run yarn run build

@tatsujb
Copy link

tatsujb commented Oct 29, 2018

I'm not sure if this is a stupid question or not but if I'm using scss only and in one file (root.scss), I do :

    $grey: #cacaca;

and then in another file (app.scss) I do :

    @import 'root';

    .my-class{
           background: $grey;
     }

...will this trigger the same undefined error under parcel ? (thinking of switching to parcel)

@andreidmt
Copy link

Going on what @michael-ciniawsky said, this works:

variables.css

:root {
  --distancer: 30px;
}

button.css

.button {
  padding: var(--distancer);
}

.postcssrc

{
  "modules": true,
  "plugins": {
    "postcss-custom-properties": {
      "preserve": false,
      "importFrom": "src/styles/variables.css"
    }
  }
}

@dreerr
Copy link

dreerr commented Dec 27, 2018

Having similar issues with postcss-extend, when referencing from another file:

base.pcss
.class {
color: red;
}

index.pcss
@import “base.pcss“
body {
@extend .class;
}

The example above does not render an error nor populate the body with red color. The idea of rendering every file individually does not make sense to me, as this should all be possible with postcss
+1 for being able to use a native postcss-import!

@devongovett devongovett added CSS Preprocessing All the PostCSS, Less, SASS, etc issues CSS and removed CSS Preprocessing All the PostCSS, Less, SASS, etc issues CSS labels Jan 6, 2019
@aoberoi
Copy link

aoberoi commented Jan 14, 2019

i'm also facing a similar issue. i'd like to use postcss-mixins, where a @define-mixin happens in foo.css, and the @mixin happens in bar.css (where @import './foo.css'; appears earlier). i cannot get this to work with parcel - i get an Undefined mixin error.

@mischnic
Copy link
Member

The example code in the issue description works correctly with the current Parcel:

:root {
  --varColor: #639;
}body {
  background-color: var(--varColor);
}

@lostboy
Copy link

lostboy commented Feb 16, 2019

@cmnstmntmn you can get past that by adding parcel-bundler to your project and running parcel from an npm script or with npx

@mischnic
Copy link
Member

mischnic commented Feb 26, 2019

One issue is that, with this change, Parcel doesn't know anything about the dependencies of a CSS file, and so HMR (and caching) for these doesn't work anymore.
We would first need to recursively search for imports and add these as a dependency before postcss runs (so parsing every CSS file twice).

@joe-askattest
Copy link

joe-askattest commented Apr 2, 2019

This comment is only about a workaround. Otherwise ignore it.

Having tried the solutions above I found that many only worked sometimes. For me in particular what was broken were my @custom-media declarations. They'd sometimes work, and often break, using the approaches above. Here is what I did to get my parcel build and parcel watch stable and reliable:

  • I have a core entrypoint file (i.e. index.css or style.css) which is the root of my css.
  • At the top of my entrypoint, before any @import statements, I have my @custom-media.
  • I then place a copy of my @custom-media declarations into a media.css file. Yes, this means I have them all written out twice. This file only contains the @custom-media declarations.
  • For @import statements one must use postcss-import-synx2. Don't use postcss-import at all.
  • You also need parcel-plugin-css-pretransform from @sj26 above (thanks), and you will need parcel-bundler too for it to run (it's a missing dependency).
  • When you run parcel watch you put two input files. First the media.css from above, and then you're entrypoint (i.e. my index.css file). You can also have something else import index.css first, say an index.js file that in turn imports index.css. That's fine. The crucial thing is for the CSS entrypoint the @custom-media come before all other CSS. Here is an example of what I mean: parcel build ./src/media.css ./src/index.css. Note how I have the media.css file as the first file, and then my entrypoint second.

A note about my double @custom-declaration. It seems to work reliably they need to both ...

  • Be seen before any @import statements. This is why I have them at the start of my style entrypoint.
  • Be in the first file seen. This is why I have the seperated into media.css.

For whatever reason I need to do both of these. And no, they cannot be the same file. At least not in my testing.

For completeness here is my .postcssrc.js

module.exports = {
  plugins: [
    require('postcss-import-sync2')(),
    require('postcss-custom-media')(),
    require('postcss-mixins')(),
    require('postcss-nested')(),
    require('autoprefixer')({
      grid: true,
    }),
  ]
}

Here is some of my package.json

{
  "name": "my project",
  "scripts": {
    "start": "parcel watch src/media.css src/index.js",
    "build": "parcel build src/index.js",
  },
  "dependencies": {
  },
  "devDependencies": {
    "@babel/core": "^7.4.0",
    "@wordpress/babel-preset-default": "^4.1.0",
    "autoprefixer": "^9.5.0",
    "parcel": "1.12.3",
    "parcel-bundler": "1.12.3",
    "parcel-plugin-css-pretransform": "1.0.0",
    "postcss-custom-media": "7.0.8",
    "postcss-import-sync2": "1.1.0",
    "postcss-mixins": "6.2.1",
    "postcss-nested": "4.1.2",
  }
}

@robcalcroft
Copy link

Going on what @michael-ciniawsky said, this works:

variables.css

:root {
  --distancer: 30px;
}

button.css

.button {
  padding: var(--distancer);
}

.postcssrc

{
  "modules": true,
  "plugins": {
    "postcss-custom-properties": {
      "preserve": false,
      "importFrom": "src/styles/variables.css"
    }
  }
}

This answer from @andreidcm worked super well for me! The difference was explicitly passing preserve: false to my postcss config, where originally CSS custom properties were not being replaced, I did ensure I passed the importFrom option too which was a path to my vars.css file where all my custom properties were 😄

@aguilera51284
Copy link

any update on this issue with tailwindcss and @import ?

mcmire added a commit to mcmire/magic-grapher that referenced this issue Nov 12, 2019
For some reason, when using `@import "reset.css"` in a CSS file, Parcel
doesn't throw an error but then when you load the page and inspect the
generated CSS file, none of the reset code appears there. Strangely,
this does work just fine for the normalize.css package, but not
reset.css.

I tried a few different ways to solve this problem. It's a known fact
that [CSS importing is broken in Parcel 1][1], but this bug doesn't seem
to be related. Eventually I gave up and we're just vendoring reset.css
now.

While I was at it I rearranged some of the files. Unfortunately I
couldn't organize Elm files into their own folder, but alas.

[1]: parcel-bundler/parcel#1165
@mischnic
Copy link
Member

This is already fixed in Parcel 2 (apart from a cache invalidation bug: #3708)

@benallfree
Copy link

benallfree commented Mar 20, 2020

@devongovett

Parcel processes each CSS file independently, which is why your imported variables aren't being replaced. This strategy makes it faster since we can process each CSS file in parallel rather than one huge concatenated at the end.

The CSS spec requires @import statements to be hoisted to the top of the file or they won't work. I've confirmed this in Chrome.

By processing files independently, the @import statements get hoisted to the top of each individual chunk, but then they get concatenated and they are not hoisted in the resulting final CSS.

The workaround for now is something like this:

import './parcel-fix.scss' // Manually combine all your @import statements here
import '@fortawesome/fontawesome-free/scss/fontawesome.scss'
import 'semantic-ui-css/semantic.min.css'
import './app.scss'

@ChildishGiant
Copy link

I'm getting this with even the simplest of setups without css vars, imports etc. Tested with stable and next.

> parcel serve ./src/index.html

Server running at http://localhost:1234 
×  E:\code\cassette\src\style.css:undefined:undefined: Cannot find module 'cssnano' from 'E:\code\cassette\src'
    at C:\Users\allie\AppData\Roaming\npm\node_modules\parcel-bundler\node_modules\resolve\lib\async.js:115:35
    at processDirs (C:\Users\allie\AppData\Roaming\npm\node_modules\parcel-bundler\node_modules\resolve\lib\async.js:268:39)
    at isdir (C:\Users\allie\AppData\Roaming\npm\node_modules\parcel-bundler\node_modules\resolve\lib\async.js:275:32)
    at C:\Users\allie\AppData\Roaming\npm\node_modules\parcel-bundler\node_modules\resolve\lib\async.js:25:69
    at FSReqCallback.oncomplete (fs.js:158:21)
npm ERR! code ELIFECYCLE
npm ERR! errno 1
npm ERR! [email protected] start: `parcel serve ./src/index.html`
npm ERR! Exit status 1

@bholtbholt
Copy link

Found a counterintuitive fix for working with Tailwind (CC @aguilera51284)

@jethromayuk
Copy link

Experiencing this with Parcel too.

@AndyOGo
Copy link

AndyOGo commented Dec 21, 2020

I have the same problem, with tailwindcss and postcss-import or postcss-cached 😞

@devongovett
Copy link
Member

Going to close since this is working in Parcel 2.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests