Parcel: 🐛CSS Importing effectively broken

Created on 10 Apr 2018  ·  46Comments  ·  Source: parcel-bundler/parcel

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

Bug Confirmed Bug CSS Preprocessing ✨ Parcel 2

Most helpful comment

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

All 46 comments

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?

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

My hack of commenting out collectDependencies hasn’t introduced any side effects for me (yet).

I did have to set up a separate gulp task to watch for changes in imported css files and touch the css entry file to force it to recompile, but that appears to be a separate issue with Parcel not watching imports.

On Apr 10, 2018, at 11:06 AM, Jesse Hoyos notifications@github.com wrote:

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


You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub, or mute the thread.

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

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?

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

@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

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.

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

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

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

@devongovett any progress on this?

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.

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

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)

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

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!

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.

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

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

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

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

@cmnstmntmn @dreerr

the problem is with imports;
if you split your code example into:

Please take a look at this (from https://github.com/parcel-bundler/parcel/issues/1165#issue-312767971), it works: (or do you have some postcss configuration)
https://github.com/mischnic/css-import-var

Imports should be handled by postcss directly and not by Parcel!!
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!

Yes, I think disabling Parcel's import handling if postcss-import is used would work (and make it use parcels resolver)?

@mischnic the example at https://github.com/mischnic/css-import-var is missing a postcss config with postcss-custom-properties and should correctly output:

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

(See https://github.com/postcss/postcss-custom-properties)

IMHO the Parcel import should be replaced by the postcss-import or be executed after the postcss files have been generated.

IMHO the Parcel import should be replaced by the postcss-import or be executed after the postcss files have been generated.

But postcss doesn't handle SASS or LESS imports.

@mischnic 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)

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.

I'm hitting this issue extensively trying to build a static html page using basscss. But any project which has a number of css files, some defining variables and custom media queries, and others consuming those definitions, will have this issue. It seems like maybe there need to be two postcss passes — one over the individual asset, and one over the packaged bundle.

Would parcel consider adding a bundle transform phase?

Or perhaps the individual assets could be parsed and partially transformed in parallel, then the asts could be merged by the packager and additional transforms run? This might require differentiating asset plugins from bundle plugins somehow.

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 published a little parcel plugin which swaps the CSSAsset transform function to be pretransform and it fixes the issue in the website I'm working on, in combination with postcss-imports and friends:

https://github.com/sj26/parcel-plugin-css-pretransform

So perhaps renaming will just work?

@sj26 would you like to send a PR?

Should postcss-import use Parcel's resolver?

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

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

@cmnstmntmn Should be "postcss-import": true

hmm, this seems to be the problem

parcelServer running at http://localhost:1234
⚠️  Cannot find module 'parcel-bundler/src/assets/CSSAsset'
    at Function.Module._resolveFilename (module.js:547:15)
    at Function.Module._load (module.js:474:25)
    at Module.require (module.js:596:17)
    at require (/Users/constantin/.config/yarn/global/node_modules/v8-compile-cache/v8-compile-cache.js:159:20)
    at module.exports (/Users/constantin/apps/cora-ui/node_modules/parcel-plugin-css-pretransform/index.js:2:19)
    at Bundler.loadPlugins (/Users/constantin/.config/yarn/global/node_modules/parcel-bundler/src/Bundler.js:209:17)
    at <anonymous>

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

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

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

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 😄

any update on this issue with tailwindcss and @import ?

This is already fixed in Parcel 2 (apart from a cache invalidation bug: https://github.com/parcel-bundler/parcel/issues/3708)

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

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

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

Experiencing this with Parcel too.

Was this page helpful?
0 / 5 - 0 ratings