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:
I should be able to declare variables in one file and use them in another via @import
without getting undefined
errors
Parcel is not transforming variables that are in imported css files.
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.
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
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 @import
s are already parsed.
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:
@import
statements, I have my @custom-media
.@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.@import
statements one must use postcss-import-synx2. Don't use postcss-import
at all.parcel-plugin-css-pretransform
from @sj26 above (thanks), and you will need parcel-bundler
too for it to run (it's a missing dependency).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 ...
@import
statements. This is why I have them at the start of my style entrypoint.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.
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:
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