parcel: šŸ›CSS Importing effectively broken

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

About this issue

  • Original URL
  • State: closed
  • Created 6 years ago
  • Reactions: 39
  • Comments: 48 (19 by maintainers)

Commits related to this issue

Most upvoted comments

any update on this issue with tailwindcss and @import ?

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

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

@mischnic the problem is with imports;

if you split your code example into:

config.css

:root {
  --varColor: #639;
}

and app.css

@import "./config.css"

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

variables won’t be substituted.

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"
    }
  }
}

@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?

I think we could maybe solve this issue by moving postcss to the pretransform phase, which occurs prior to dependencies being parsed. Currently, it is applied in the transform phase, which happens after @imports are already parsed.

https://github.com/parcel-bundler/parcel/blob/bdc044acfad84ba25bdb6631fbd3dd955424a227/packages/core/parcel-bundler/src/assets/CSSAsset.js#L96-L98

If that method is simply renamed to pretransform rather than transform postcss will be applied first, meaning that if a user specifies postcss-import in their .postcssrc, then imports will be compiled away by postcss instead of being handled by parcel. If not, or there is no .postcssrc, then parcel will handle the imports as it does today.

Would someone like to prototype this and create a PR?

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.

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

.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)

Going to close since this is working in Parcel 2.

@mischnic ideally, but I feel like if users are specifying to use postcss-import instead of the default parcel behavior then that is their problem not ours, so if we don’t support using parcel’s resolver with postcss-import then that’s probably ok.

sass and less already uses it’s own importer (so changing to postcss-import wouldn’t change this at all a far as I know)

I was referring to a css file importing sass/less (though that could be done with some configuration).

I think we should only do this if there is some postcss configuration, to retain performance for ā€œvanillaā€ CSS.

@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'

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 šŸ˜„

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).

hmm, does anyone manage to make it work, using @sj26 's parcel-plugin-css-pretransform?

this is my .postcssrc file. the result is as without the plugin

{
    "modules": false,
    "plugins": {
      "postcss-import": false,
      "postcss-custom-media": true, 
      "postcss-custom-properties": true,
      "autoprefixer": {
        "browsers": ["last 2 versions"],
        "grid": true
      }
    }
}

Imports should be handled by postcss directly and not by Parcel!!

@mischnic The problem is that the postcss-import module is not executed in the way it should be, as imports are handled by Parcel, but only after the postcss transformation has been made!

See: https://github.com/parcel-bundler/parcel/issues/609#issuecomment-359428778

My current workaround is also @cmnstmntmn https://github.com/parcel-bundler/parcel/issues/1165#issuecomment-416218024

This is the same issue as CSS files being compiled with PostCSS before they’re imported https://github.com/parcel-bundler/parcel/issues/609

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

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

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.

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?

@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.