I am trying CSS Modules for the first time with React and Webpack and I came across at least three ways to achieve it:
css-loader
react-css-modules
babel-plugin-react-css-modules
I went with babel-plugin-react-css-modules in order to balance code simplicity and performance and everything seems to be working fine except for one thing: my 3rd party libraries (Bootstrap and Font Awesome) are also included in CSS Modules transformation.
<NavLink to="/about" styleName="navigation-button"></NavLink>
The above assigns a properly transformed className to the NavLink. However, a span inside needs to refer to global styles in order to render an icon.
<span className="fa fa-info" />
The above span is not assigned a transformed className which is expected, but my bundled stylesheet does not have these CSS classes as they are being transformed into something else, to simulate local scope.
Below is the content in my .babelrc file to activate babel-plugin-react-css-modules:
{
"presets": ["env", "react"],
"plugins": [
["react-css-modules", {
"generateScopedName": "[name]__[local]___[hash:base64:5]",
"filetypes": {
".less": {
"syntax": "postcss-less"
}
}
}]
]
}
In my Webpack configuration, below is the section to configure css-loader for transforms:
{
test: /\.(less|css)$/,
exclude: /node_modules/,
use: extractCSS.extract({
fallback: 'style-loader',
use: [
{
loader: 'css-loader',
options: {
minimize: true,
modules: true,
sourceMap: true,
localIdentName: '[name]__[local]___[hash:base64:5]'
}
},
{
loader: 'less-loader'
}
]
})
}
As far as I have read, the above rule should exclude the library stylesheets and I also tried adding another rule specifically for the excluded stylesheets, however that did not seem to work, as I guess as those stylesheets were still transformed with the original rule.
In order to import CSS from the two libraries, I have the below two lines in my parent stylesheet that declares some global styles:
#import '../../../node_modules/bootstrap/dist/css/bootstrap.min.css';
#import '../../../node_modules/font-awesome/css/font-awesome.min.css';
I find these two approaches below might be helpful:
https://stackoverflow.com/a/52294675
https://github.com/css-modules/css-modules/pull/65#issuecomment-412050034
In short, there seems to be no options to ignore/exclude certain paths from being modularized by the css-modules webpack plugin so far. Ideally it should be supported by the plugin, but here're some approaches you can try out:
use two webpack rules to utilise the webpack rule exclusion/inclusion:
module.exports = {
rules: [
{
test: /\.css$/,
exclude: /node_modules/,
use: [
'style-loader',
{
loader: 'css-loader',
options: {
modules: true,
localIdentName: '[path]__[local]___[hash:base64:5]',
},
},
],
},
{
test: /\.css$/,
include: /node_modules/,
use: ['style-loader', 'css-loader']
}
]
}
...or, inject into webpack's getLocalIdent from the second answer above to manually exclude certain paths.
const getLocalIdent = require('css-loader/lib/getLocalIdent');
{
loader: 'css-loader',
options: {
modules: true,
localIdentName: '[path][name]__[local]--[hash:base64:5]',
getLocalIdent: (loaderContext, localIdentName, localName, options) => {
return loaderContext.resourcePath.includes('semantic-ui-css') ?
localName :
getLocalIdent(loaderContext, localIdentName, localName, options);
}
}
}
For me using :global worked :
.my-component {
:global {
.external-ui-component {
padding: 16px;
// Some other styling adjustments here
}
...
}
}
Ps: for doing it with webpack config, please see another answer.
source
Updated solution from playing771
{
loader: 'css-loader',
options: {
modules: {
auto: (resourcePath) => !resourcePath.includes('node_modules'),
localIdentName: '[name]__[local]__[hash:base64:5]',
},
},
},
Related
My .css or .scss files won't load at all.
What I'm trying to achieve is to hash my class names using css-loader option localIdentName.
I'm working on ReactJS with custom webpack config. This is one of my rules inside webpack.config.js:
{
test: /(\.css|\.scss|\.sass)$/,
use: [
{
loader: 'style-loader',
},
{
loader: 'css-loader',
options: {
modules: {
localIdentName: "[path][name]__[local]--[hash:base64:5]",
}
},
},
{
loader: 'sass-loader',
options: {
},
},
],
},
I am trying to include another library's css for their component in my own application. For reference, I am trying to use this data table library: https://github.com/filipdanic/spicy-datatable.
In the docs, it states Out of the box, spicy-datatable is bare-bones. Include this CSS starter file in your project to get the look from the demo. Edit it to suit your needs.
I tried to import the style sheet at the top of the component that I am building like this: import * as spicy from 'spicy-datatable/src/sample-styles.css'; in my own component file. It was not styled. I tried putting the raw code into my index.scss file in my assets/styles folder - did not work. I tried putting it in my own styles file ./component.scss - did not work.
I have them currently set up like:
import * as styles from './component.scss';
import * as spicy from 'spicy-datatable/src/sample-styles.css';
and am getting an error:
Module parse failed: Unexpected token (4:0)
You may need an appropriate loader to handle this file type.
webpack.config.js
const dirNode = 'node_modules';
const dirApp = path.join(__dirname, 'client');
const dirAssets = path.join(__dirname, 'assets');
/**
* Webpack Configuration
*/
module.exports = {
entry: {
vendor: ['lodash'],
bundle: path.join(dirApp, 'index')
},
resolve: {
modules: [dirNode, dirApp, dirAssets]
},
plugins: [],
module: {
rules: [
// BABEL
{
test: /\.js$/,
loader: 'babel-loader',
exclude: /(node_modules)/,
options: {
compact: true
}
},
// CSS / SASS
{
test: /\.(scss)$/,
use: [
'style-loader',
{
loader: 'css-loader',
options: {
importLoaders: 1,
modules: true,
localIdentName: '[path]___[name]__[local]___[hash:base64:5]'
}
},
'sass-loader'
]
},
// IMAGES
{
test: /\.(jpe?g|png|gif)$/,
loader: 'file-loader',
options: {
name: '[path][name].[ext]'
}
}
]
}
};
.babelrc
"plugins": [
[
"react-css-modules",
{
"filetypes": {
".scss": {
"syntax": "postcss-scss"
}
},
"webpackHotModuleReloading": true
}
]
I'm not sure if I need to add something to specifically handle .css files, this is my first time working with CSS Modules. I thought react-css-modules did that so I'm not quite sure why the CSS file isn't loading correctly.
Edit:
Edited my webpack around to include CSS:
{
test: /\.(css)$/,
use: [
'style-loader',
{
loader: 'css-loader',
options: {
modules: true,
localIdentName: '[path]___[name]__[local]___[hash:base64:5]'
}
}
]
},
Error is gone, but styles still do not appear.
Could you try changing below:
import * as spicy from 'spicy-datatable/src/sample-styles.css';
to
import from 'spicy-datatable/src/sample-styles.css';
If you are using CSS-Modules, try below:
import spicy from 'spicy-datatable/src/sample-styles.css';
and then use the style on JSX element like below:
<h1 className={classes.<className in CSS here>}>
I setup a codesandbox with the spicy-datatable library and imported the styles and looks like it applied. The styles are in "Hello.css" file and it is imported in "index.js".
https://codesandbox.io/s/4j31xj3689
If library doesn't use css-modules (uses className attribute instead of styleName) we need to disable modules for imported css, so the class names will remain unchanged. This can be done in 2 ways:
Modify your Webpack config
module: {
rules: [
{
test: /\.(css)$/,
use: [
'style-loader',
{
loader: 'css-loader',
options: {
modules: false
}
}
]
},
...
]
}
Import library css directly into your scss stylesheet (thanks to this answer pointing out how to perform proper .css import). Make sure to exclude .css file extension from import line. :global directive will prevent css-modules to modify class names for all styles within this directive.
:global {
#import "~library-module-name/.../CssFileWithoutExtension";
}
I am trying to setup my react project so I can use SASS in the SCSS format.
This is in my webpack.config.dev.js:
{
test: /\.scss$/,
exclude: /node_modules/,
use: [
require.resolve('style-loader'),
{
loader: require.resolve('css-loader'),
options: {
importLoaders: 1,
},
},
{
loader: require.resolve('sass-loader'),
}
]
}
I import the scss files into my jsx in two different ways:
import './index.scss';
import css from './ModalWrapper.scss';
When I run the app I am currently getting the error:
./src/index.scss
Module build failed:
body {
^
Invalid CSS after "m": expected 1 selector or at-rule, was "module.exports = __"
in /pathtoapp/web/src/index.scss (line 1, column 1)
It appears me, that one, react is trying to interpret the SCSS as CSS which should work. In addition, react believes that body is not valid CSS. There, I would believe that neither CSS or SCSS are being loaded correctly.
Any help would be appreciated. There are quite a few unanswered questions to this problem.
If you are on Webpack 3, add this to module.rules
{
test: /\.scss$/,
loader: [
require.resolve('style-loader'),
require.resolve('css-loader'),
require.resolve('sass-loader'),
]
},
And then add the file loader and make sure to add .scss to the array of the key exclude like this
{
loader: require.resolve('file-loader'),
// Exclude `js` files to keep "css" loader working as it injects
// it's runtime that would otherwise processed through "file" loader.
// Also exclude `html` and `json` extensions so they get processed
// by webpacks internal loaders.
exclude: [/\.js$/, /\.html$/, /\.json$/, /\.scss$/,],
options: {
name: 'static/media/[name].[hash:8].[ext]',
},
}
And of course, make sure you have style-loader, sass-loader, css-loader and file-loader in you package.json. This code snippet worked for me when using the latest version of create-react-app.
This is what ended up working for me:
{
test: /\.scss$/,
use: [{
loader: 'style-loader'
}, {
loader: 'css-loader',
options: {
modules: true,
sourceMap: true,
localIdentName: "[local]___[hash:base64:5]",
},
}, {
loader: 'sass-loader',
options: {
outputStyle: "expanded",
sourceMap: true,
},
}]
},
I'm sure the other answers are just as good, but for maximum brevity, this works for me (I erased some of the internal webpack.config.dev.js comments presumably made by the create react folks):
module: {
strictExportPresence: true,
rules: [
{
test: /\.scss/,
use: ['style-loader','css-loader', 'sass-loader']
},...
I don't know if it matters, but I put this on the top. Also, yes, make sure to add the scss file to the excluded array as mentioned above.
Init
Im trying to use webpack with the sass-loader and the postcss-loader. I already tried different solutions but nothing worked like I want it to.
I tried the solution from Angular 2 Starter Pack with the raw-loader and the sass-loader, but than the postcss-loader didnt work.
Code
Angular 2 Component
#Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss']
// styles: [
// require('./app.component.scss')
// ]
})
Webpack module loader
{
test: /\.scss$/,
loaders: ['to-string-loader', 'css-loader', 'postcss-loader', 'resolve-url-loader', 'sass-loader']
}
Problem
With these code lines everything works but the styles are added in the <head> tag within the <style> tag. At some point I would have hundreds of style lines which I want to avoid.
If I change my loader code to this:
loader: ExtractTextPlugin.extract('to-string-loader', 'css-loader', 'postcss-loader', 'resolve-url-loader', 'sass-loader?sourceMap')
and add this to the webpack config
plugins: [
new ExtractTextPlugin('style.css')
]
it results in an error
Uncaught Error: Expected 'styles' to be an array of strings.
The style.css is actually linked in the html code.
Im searching for a solution which allows me to use sass, postcss and a single .css file.
I just ran into this issue and figured out a solution. I'm pretty sure its the "to-string-loader". The dev config below is working for me using Webpack 4 and Angular 7. It allows a global stylesheet (Tailwind CSS in my case) alongside component styles. Hot module replacement is also working for editing both entries.
entry: {
app: './src/main',
styles: './src/assets/styles/styles'
},
resolve: {
extensions: ['.ts', '.tsx', '.mjs', '.js', '.scss'],
},
module: {
rules: [
{
// Process the component styles
exclude: path.resolve(__dirname, 'src/assets/styles/styles'),
test: /\.(scss)$/,
use: [
{ loader: 'raw-loader' }, // Load component css as raw strings
{
loader: 'postcss-loader', // Process Tailwind CSS
options: {
sourceMap: 'inline',
}
},
{
loader: 'sass-loader', // Compiles Sass to CSS
},
]
},
{
// Process the global tailwind styles
include: path.resolve(__dirname, 'src/assets/styles/styles'),
test: /\.(scss)$/,
use: [
{
loader: 'style-loader', // Allow for HMR
},
{
loader: 'postcss-loader', // Process Tailwind CSS
options: {
sourceMap: 'inline',
}
},
{
loader: 'sass-loader', // Compiles Sass to CSS
},
]
},
]
},
The style-loader will extract the styles into the head at runtime, and allow for HMR. In your prod config, you can use css-loader alongside MiniCssExtractPlugin to extract and inject the global styles as a .css file into the head:
{
// Process the global tailwind styles
include: path.resolve(__dirname, 'src/assets/styles/styles'),
test: /\.(scss)$/,
use: [
{ loader: MiniCssExtractPlugin.loader },
{ loader: 'css-loader' },
{
loader: 'postcss-loader', // Process Tailwind CSS
options: {
sourceMap: false,
}
},
{
loader: 'sass-loader', // Compiles Sass to CSS
},
]
},
I'm using webpack-dev-server to hot load all of my assets including CSS. Currently though, my CSS loads after the JavaScript which causes my application issues in some places that depend on layout existing.
How can I ensure that the CSS loads before the JavaScript executes?
I'm thinking there must be a way to do this from module, perhaps a a callback I could hook in to? Or maybe by configuring the style-loader to have priority?
This is my index.js:
import './styles/main.scss';
import './scripts/main.js';
if (module.hot) {
module.hot.accept();
}
and this is my styles loader:
{
test: /\.(scss)$/,
loader: 'style!css?&sourceMap!postcss!resolve-url!sass?sourceMap'
},
A similar question has been asked at this style-loader Github Issue: https://github.com/webpack/style-loader/issues/121
I was having the exacly same issue, in production the css are extracted so it always work, however in development because of the style-loader "sometimes executing after the main bundle" i had issues where the main bundle would calculate the size of some nodes in the DOM which was set by the css... so it could result to wrong sizes as the main css still havent loaded... i fixed this issue by having the option singleton:true.
example:
{
test: /\.s?css$/,
loader: ExtractTextPlugin.extract({
fallback: [
{
loader: 'style-loader',
options: { singleton: true }
}
],
use: [
{
loader: 'css-loader',
options: {
importLoaders: 1,
minimize: !isProduction,
sourceMap: !isProduction
}
},
{ loader: 'postcss-loader', options: { sourceMap: !isProduction, ...postcss } },
{ loader: 'resolve-url-loader', options: { sourceMap: !isProduction } },
{ loader: 'sass-loader', options: { sourceMap: true } }
]
}
)
}
Looks like there's no event, callback or any way to detect that the style has been loaded. After long hours of searching in vain, I had to do something really dirty:
function checkCSS() {
const repeat = requestAnimationFrame(checkCSS);
// CSS loaded?
if(getComputedStyle(document.body).boxSizing === 'border-box') {
routes.loadEvents() // Init JS
cancelAnimationFrame(repeat) // Cancel next frame
}
}
if (process.env.NODE_ENV !== 'production') {
checkCSS()
} else {
$(document).ready(() => {
routes.loadEvents()
})
}
Because I have * { box-sizing: border-box; } in my styles and that I'm pretty sure native CSS styles won't never look like this, I can be ~99% sure that my own CSS is loaded.
I died a little writing this. Hopefully we'll find a better way!