I tried to understand importLoaders from the readme but completely don't get it.
Can anybody please give a better explanation of what importLoaders do?
Thank you
Bernd
I'm new to Webpack as well and as I understand it, It does what is says on the tin:
importLoaders allow to configure which loaders should be applied to @imported resources.
Given the following CSS:
/* File style.css */
@import "body.css";
body {
/*background: yellow;*/
font-size: 20px;
}
div {
display: flex;
}
/* File body.css */
$color: red;
body {
background: $color;
}
div {
color: white;
a {
color: green;
}
}
div {
display: flex;
}
On a basic set up with using PostCSS the following would only apply PostCSS plugins to style.css, NOT the @imported body.css:
module: {
loaders: [
{
test: /\.css$/,
loader: "style-loader!css-loader!postcss-loader"
}
]
},
postcss: function() {
return [autoprefixer, precss];
}
To get autoprefixer (and precss) working on @imported CSS files we use importLoaders instead:
module: {
loaders: [
{
test: /\.css$/,
loader: 'style!css?importLoaders=1!postcss'
}
]
},
postcss: function() {
return [autoprefixer, precss];
}
I assume this has something to do with how Webpack loaders parse assets. You have to explicitly tell the loader to interpret @import directives. Why that isn't done by default or as an option in style-loader or css-loader I don't know - maybe there is a way of doing it that I am missing.
Thanks @mummybot for the explanation. That helped me to get a bit better understanding.
Any idea what the number after importLoaders= means?
As far as I can tell it is simply to make sure that the value of importLoaders is true:
module.exports = function getImportPrefix(loaderContext, query) {
if(query.importLoaders === false)
return "";
var importLoaders = parseInt(query.importLoaders, 10) || 0;
var loadersRequest = loaderContext.loaders.slice(
loaderContext.loaderIndex,
loaderContext.loaderIndex + 1 + importLoaders
).map(function(x) { return x.request; }).join("!");
return "-!" + loadersRequest + "!";
};
https://github.com/webpack/css-loader/blob/master/lib/getImportPrefix.js
On an expanded note to my earlier reply, here is the webpackconfig.js I have currently assembled (based on the Webpack getting started example) which does the following:
var ExtractTextPlugin = require('extract-text-webpack-plugin'),
autoprefixer = require('autoprefixer'),
precss = require('precss');
module.exports = {
entry: [
"./entry.js",
],
output: {
path: __dirname,
filename: "bundle.js"
},
devtool: "source-map",
module: {
loaders: [
{
test: /\.css$/,
loader: ExtractTextPlugin.extract('style-loader', 'css-loader?sourceMap&modules&importLoaders=1!postcss-loader')
},
]
},
postcss: function() {
return [autoprefixer, precss];
},
plugins: [
// Set the name of the single CSS file here.
new ExtractTextPlugin('main.css', { allChunks: true })
]
};
Thanks for that @mummybot .
Not sure that the number really comes down to true/false though. For now I just put 1, but still feel a bit uncomfortable about that.
My understanding is that the number specifies how many loaders after css-loader should be applied to imports. The README says:
importLoaders (int): That many loaders after the css-loader are used to import resources.
If all you have is postcss, then 1 is fine. But if you had more loaders than that, you'd want to bump the number up.
Thanks @randycoulman, your answer help me alot
what if I want my @import files to use a different set of loaders? For example my project's CSS is postcss but I'm using a npm package with .less files
@alisonailea Just have multiple loader sections in your webpack config which include/exclude the folders you want.
{
test: /\.less$/,
loader: ExtractTextPlugin.extract(
'style' ,
'css?sourceMap&importLoaders=1!' +
'postcss-loader!' +
'less?sourceMap'
)
}
I found that if I set importLoaders = 1, less-loader still work for @import file. In this case,
@import file will only use postcss-loader, right ?
But in fact, it does not. and if I remove importLoaders=1, @import files still will bundle.
I can not figure out why..
I have to say that the order in which loaders are executed, and the language used here and in the docs is totally confusing.
'style!css!resolve-url!postcss!sass!./main.scss'
Let's look at the order of chaining.
main.scss => sass-loader => postcss-loader => resolve-url-loader => css-loader => style-loader
Now, tell me - which "after" are we talking about? From my understanding of the docs, and what's been said here, what people mean by "after" is the order when reading from left-to-right.
So, "chain order" is right-to-left. importLoaders is from left-to-right.
Yeah, it's a bit confusing. What the docs are missing is another example, with an additional loader (sass-loader, less-loader, whatever), just so users can realize that they need to bump the number.
I would have thought that importing would apply the same rules as the parent document. Ah well.
@QuantumInformation but maybe you don't want _everything_ applied. Although, it would be a nice default 👍 but I'm not sure if current webpack API supports that.
I read all the comment above. But still confused.
module.exports = {
entry: "./entry.js",
output: {
path: __dirname,
filename: "bundle.js"
},
module: {
rules: [
{
test: /\.less$/,
use: [
'style-loader',
{
loader: 'css-loader',
options: {
importLoaders: 1
}
},
'less-loader'
]
}
]
}
};
I tried with the recommended configuration. No matter what provided to importLoaders, import statements in less file are handled by less loader and make none difference. I am more confused.
@yanghuabei what are you trying to achieve? Don't you want imported Less files to be handled by less-loader?
it seems only have an effect on imported .css resource. If you're importing .scss or .less, they will be handled as normal by webpack.
Fixed by #500
@codeboyim's comment did it for me. This is exactly what is missing in the docs.
importLoaders only has effect on unresolved @imports. So when using postCSS with nextCSS (no @import resolver) you'll want to set importLoaders. This way nextCSS will also be applied to imported .css files. But when using sass, it already handles the @import statements, so no importLoaders is required.
So, this only happens when css-loader finds an unresolved @import. When using sass-loader for example; All imports are resolved (and concatenated) before css-loader even gets the chance to look for an @import.
@guidobouman whoa, I had no idea it works that way… do you mind adding that to the docs?
@silvenon I've updated my previous comment with my most recent learnings. ☝️
It's a tad bit simpler than you'd think.
@guidobouman I think it's better to include sass-loader in the importLoaders counter anyway, the reason is scenarios like .some-rule { composes: foo from './base.scss'; } where you want sass-loader to run over these files (base.scss in my example) as well.
@roymiloh That feels a bit weird to me, mixing css modules with SASS. Then you'd be better off using the full future CSS spec with just postCSS and it's plugins, and completely dropping anything like SASS, LESS or Stylus.
I guess would be great to put a small real-life example of a importLoaders option use case. Because it becomes clear that this option may come in handy more like 'sometimes', rather than 'often', so people won't tend to apply it by default even if they don't need it at all (using sass-loader for example).
@dvakatsiienko PR welcome
The documentation is still strange. They show this:
{
test: /\.css$/,
use: [
'style-loader',
{
loader: 'css-loader',
options: {
importLoaders: 2 // 0 => no loaders (default); 1 => postcss-loader; 2 => postcss-loader, sass-loader
}
},
'postcss-loader',
'sass-loader'
]
}
But when would you ever want to include sass-loader if importLoaders only affects .css (non-sass/less) files? So you wouldn't want to set it to 2 here. You probably do want postcss-loader (autoprefixer/cssnano) to run over @imported files though. So the only number that makes sense here is 1, right?
@mnpenner Sass in this case already handles importing of .sass, .scss and extensionless files. One thing this would allow is using sass in .css. No idea why that would be something you want though.
It does make sense to let PostCSS run on imported .css files. So yes a value of 1 does make some sense.
original issue + PR: https://github.com/webpack-contrib/css-loader/issues/21
@mnpenner Sass in this case already handles importing of
.sass,.scssand extensionless files. One thing this would allow is using sass in.css. No idea why that would be something you want though.It does make sense to let PostCSS run on imported
.cssfiles. So yes a value of 1 does make some sense.
I think sass-loader will resolve @import, there will be no @import. importLoaders only has effect on unresolved @imports, so value shuld be 0?
It still doesn't make any sense. Also pipe syntax is catastrophe and should be deprecated immediately.
The docs totally need clarification. This option is extremely confusing, especially when you use sass-loader, which resolves imports vs. plugins that does not.
What here need clarify?
options: {
importLoaders: 2, // 0 => no loaders (default); 1 => postcss-loader; 2 => postcss-loader, sass-loader
},
@evilebottnawi The value 2 does not make sense. See my previous comments.
TLDR; if you use sass-loader, you don't need to set importLoaders in css-loader
Most helpful comment
My understanding is that the number specifies how many loaders after css-loader should be applied to imports. The README says:
If all you have is
postcss, then1is fine. But if you had more loaders than that, you'd want to bump the number up.