I'm creating a react component library. Styling needs to be isolated, apart from some global variables. My styling is not applying everywhere, only on some pages. Why is this and how can I fix it?
My webpack.config.js:
const webpack = require('webpack');
const path = require('path');
module.exports = async ({ config, mode }) => {
config.module.rules.push({
test: /\.scss$/,
use: [
'style-loader',
{
loader: 'css-loader',
options: {
modules: {
mode: 'local',
localIdentName:
'[name]-[local]-[hash:base64:3]',
},
import: true,
importLoaders: true,
}
},
{
loader: 'sass-loader',
},
{
loader: 'sass-resources-loader',
options: {
resources: [
path.resolve(__dirname, '../src/styles/variables/_variables.scss'),
path.resolve(__dirname, '../src/styles/variables/_header.scss'),
path.resolve(__dirname, '../src/styles/variables/_footer.scss')
]
}
}
],
});
return config;
};
For example: I have a module "Checkbox". The story for the module is below. The first story is styled, the second story is not.
stories.add('Empty', () => (
<Checkbox />
));
stories.add('With Label', () => (
<Checkbox classes={propsClasses} field={'Field'} label={'Label'} />
));
EDIT:
With the webpack.config.js below, all the styles load. But then ALL the styles load on EVERY page. Which makes for duplicate style and no longer an isolated component.
const webpack = require('webpack');
const path = require('path');
module.exports = async ({ config, mode }) => {
config.module.rules.push({
test: /\.scss$/,
use: [
'style-loader',
{
loader: 'css-loader',
options: {
modules: {
mode: 'local',
localIdentName: '[local]',
},
import: true,
importLoaders: true,
}
},
{
loader: 'sass-loader',
},
{
loader: 'sass-resources-loader',
options: {
resources: [
path.resolve(__dirname, '../src/styles/variables/_variables.scss'),
path.resolve(__dirname, '../src/styles/variables/_header.scss'),
path.resolve(__dirname, '../src/styles/variables/_footer.scss')
]
}
}
],
});
return config;
};
Webpack bundles CSS files when they are required, the same way it bundles JS files when they are required. When using sass-loader, css-loader, and style-loader, the require will pass the Sass source code through those loaders and the result is JS code that will insert the transpiled CSS into a <style> element on the page at runtime. That JS code is what gets added to the bundle webpack creates.
I'm not familiar with sass-resources-loader, but per its README, This loader will #import your SASS resources into every required SASS module -- this is likely why you're seeing all of your styles imported for each module...?
To isolate your styles on a per-component basis, you probably want a .scss file for each component that contains styles for that component, and to require() that .scss file in its JS module. Because webpack processes each Sass file independently, as it's required, each component's Sass file will need to import any variables/mixins it needs. (It sounds like sass-resources-loader is trying to handle that for you, but not giving the desired results?)
How are you loading your Sass files currently?
Related
I need to add a UI toolkit dependency in my project. The dependency injects two libraries in node-modules
—toolkit
—toolkit-core
—toolkit-ui
I need to use files from toolkit-ui. But the toolkit-ui is importing relative dependency as below:
#import "toolkit-core/tools"
When the project compiles it gives me below error :
ERROR in ./css/app.scss (./node_modules/css-loader!./node_modules/postcss-loader/src??ref--5-2!./node_modules/resolve-url-loader!./node_modules/sass-loader/dist/cjs.js!./css/app.scss)
Module build failed (from ./node_modules/sass-loader/dist/cjs.js):
#import "toolkit-core/tools";
^
File to import not found or unreadable: toolkit-core/tools.
My webpack set up as below
const { resolve, join} = require('path');
const autoprefixer = require('autoprefixer');
const postCssLoader = {
loader: 'postcss-loader',
options: {
plugins: function() {
return [autoprefixer({ browsers: ['last 2 versions', 'ie 10'] })];
}
}
};
module.exports = (projectDir) => [
…
{
test: /\.(scss|css)$/,
use: [
'style-loader', 'css-loader', postCssLoader, 'resolve-url-loader',
{
loader: 'sass-loader',
options: {
sourceMap: true,
minimize: true,
includePaths: [path.join(__dirname, 'node_modules')],
}
}
]
},
…
]
Can someone please suggest how can I resolve this?
Thanks
I resolved above.
I was importing webpack loaders from a common build directory. I was trying to override it by adding a new rule for scss|css. But that didn't seem to work. Webpack used the first rule for scss|css loaders.
However if I replace that rule by my in app rule. It works.
Recently we found out that we have to use SSR for Our React Project.
I have checked with every method that I know and almost tested all methods that I've found on medium and other sites. And after a lot of work, I decided that we have to migrate to Next JS.
While the process of migrating everything is fine, but for the style sheets.
In the old version of our app, we used webpack to bundle our styles with the project and everything was fine.
This is the webpack.config.js
const HtmlWebpackPlugin = require('html-webpack-plugin');
const ExtractTextPlugin = require("extract-text-webpack-plugin");
const port = process.env.PORT || 3000;
const extractSCSS = new ExtractTextPlugin('./[name].css');
// const UglifyJS = require('uglifyjs-webpack-plugin');
module.exports = {
mode: 'development',
output: {
filename: 'bundle.[hash].js',
publicPath: '/'
},
devtool: 'source-map',
module: {
rules: [
// First Rule
{
test: /\.(js)$/,
exclude: /node_modules/,
use: ['babel-loader'],
},
// Second Rule
{
test: /\.scss$/,
use: ['css-hot-loader'].concat(extractSCSS.extract({
fallback: 'style-loader',
use: [
{
loader: 'css-loader?sourceMap',
options: { alias: { '../img': '../public/img' }, sourceMap: true }
},
{
loader: 'sass-loader',
options: {
sourceMap: true
}
}
]
}))
},
{
test: /\.css$/,
use: ['style-loader', 'css-loader']
},
{
test: /\.(png|svg|jpg|gif)$/,
use: [
'file-loader'
]
},
{
test: /\.(woff|woff2|eot|ttf|otf)$/,
use: [
'file-loader'
]
}
]
},
plugins: [
new HtmlWebpackPlugin({
template: 'public/index.html',
favicon: 'public/favicon.ico'
}),
extractSCSS,
],
devServer: {
host: 'localhost',
port: port,
historyApiFallback: true,
open: true
}
};
and after I migrated the app, my next.config.js looks like this:
// next.config.js
const withSass = require('#zeit/next-sass')
const withCSS = require('#zeit/next-css')
module.exports = withCSS( withSass(
{
webpack(config, options) {
config.module.rules.push({
test: /\.(png|jpg|gif|svg|eot|ttf|woff|woff2)$/,
use: {
loader: 'url-loader',
options: {
limit: 100000
}
}
},
)
return config
},
}
))
The problem is that everything renders correctly but there are no stylesheets in it and it doesn't contain any style. Is there anybody who could help me to solve this problem?
For just using CSS from node_modules you don't need this fancy config.
3 simple steps:
Install next-css plugin:
npm install --save #zeit/next-css
Create in your root directory next.config.js with the following content:
// next.config.js
const withCSS = require('#zeit/next-css')
module.exports = withCSS({
cssLoaderOptions: {
url: false
}
})
Now you should be able to import styleshets from node_modules like this:
import 'bootstrap-css-only/css/bootstrap.min.css';
Note: Using Next v 8+
Background:
I spent a few hours trying to simply import a CSS installed as a node_module and the various solutions are mostly hacky workarounds, but as shown above, there is a simple solution.
It was provided by one of the core team members: https://spectrum.chat/next-js/general/ignoring-folders-files-specifically-fonts~4f68cfd5-d576-46b8-adc8-86e9d7ea0b1f
This is not a real answer but CSS in Next.js is just SUCKS! I find myself constantly struggle to make it work so what I decided is to follow their docs and simply use:
const App = () => {
return (
{style}
<div/>
);
}
let style = (<style jsx>
{`
.someClass {}
`}
</style> )
export default App;
This way you can have CSS as you might have in regular HTML without any external imports
source
You don't need both withCSS & withSass plugins.
If you are using Sass the withSass plugin will compile it to CSS.
Just make sure you add the path to the CSS file in your _document.js file inside the Head component like this:
<Head>
<link rel="stylesheet" href="/_next/static/style.css" />
</Head>
For importing css you can use Head component of the nextJS.
import Head from 'next/head';
<Head>
<link rel="stylesheet" href="path of the css" />
</Head>
CLI Tool: Storybook
Framework: Vue/ Nuxt
Issue: I'm trying to pull in global SCSS variables to Storybook Stories so they run the components the same way as they do in Nuxt, I've tried the custom webpack config with sass-resources-loader but had no luck, just wanted to check if anyone else has already solved this problem
It seems to be an issue with Storybook handling multiple rules.
I solved it by a work around.
You can read the blog i wrote for detailed explaination here.
Below is my webpack config - main.js :
webpackFinal: async (config, { configType }) => {
config.module.rules.map((rule) => {
if (rule.oneOf) {
rule.oneOf = rule.oneOf.slice().map((subRule) => {
if (subRule.test instanceof RegExp && subRule.test.test('.scss')) {
return {
...subRule,
use: [
...subRule.use,
{
loader: require.resolve('sass-resources-loader'),
options: {
resources: [
path.resolve(__dirname, '../src/styles/_common.scss')
]
}
}
],
}
}
return subRule;
});
}
return rule;
});
return config;
},
Hope this helps someone!
I encountered the issue where global SASS variables were causing Storybook for Vue to fail.
For me, creating a webpack.config.js file in the .storybook folder with the below configuration solved my problem:
module.exports = (storybookBaseConfig, configType, defaultConfig) => {
defaultConfig.module.rules.push(
{
resourceQuery: /module/,
use: [
{
loader: 'vue-style-loader',
options: {
sourceMap: false,
shadowMode: false
}
},
{
loader: 'css-loader',
options: {
sourceMap: false,
importLoaders: 2,
modules: true,
localIdentName: '[name]_[local]_[hash:base64:5]'
}
},
{
loader: 'postcss-loader',
options: {
sourceMap: false
}
},
{
loader: 'sass-loader',
options: {
sourceMap: false,
indentedSyntax: true,
data: '#import "#/sass/_variables.scss";'
}
}
]
}
);
return defaultConfig;
};
Note the line data: '#import "#/sass/_variables.scss";' needs to match the file path for the SASS file with variables in your project.
This section of config was retrieved from Vue CLI 3 by running vue inspect > output.js and then copying the config for the rule test: /\.sass$/.
You need to add the scss rule in your .storybook/webpack.config.js for storybook to parse scss.
const path = require('path');
const scss = {
test: /\.scss$/,
use: [
'vue-style-loader',
'css-loader',
'sass-loader'
],
};
module.exports = (storybookBaseConfig, configType, defaultConfig) => {
defaultConfig.module.rules.push(scss);
return defaultConfig;
};
You may also need to install the appropriate loaders:
yarn add -D vue-style-loader sass-loader css-loader
For anybody who can actually get Storybook to read SCSS files but can't get it to read the global variables file, do this in your custom webpack config:
module: {
rules: [
// Apply loader
{
test: /\.scss$/,
loaders: [
'style-loader',
'css-loader',
{
loader: 'sass-loader',
options: {
prependData: '#import "path/to/global.scss";',
},
},
],
},
],
}
If your components do not get styles applied when run in the Storybook component explorer UI, just import SASS styles in your main Storybook config/storybook/config.js (in previous versions was by default at storybook/config.js) like so:
// Import Styles
import '../../src/assets/styles/index.scss';
Usually you'd have your styles and plugins imported in your src/main.js / src/main.ts but you also need to do this in Storybook config, as when running Storybook it's not running the whole Vue app but just those individual components.
I just wondering why my CSS name became hash after I build and run my React + Webpack application. Is there advance configuration that I may have missed to set the CSS name as normal?
This is my Webpack configuration:
var webpack = require('webpack');
var path = require('path');
module.exports = {
entry: './app/app.jsx',
output: {
path: __dirname,
filename: './public/bundle.js'
},
resolve: {
alias: {
applicationStyles: path.resolve(__dirname,'app/styles/app.css'),
Clock: path.resolve(__dirname,'app/components/Clock.jsx'),
Countdown: path.resolve(__dirname,'app/components/Countdown.jsx'),
CountdownForm: path.resolve(__dirname,'app/components/CountdownForm.jsx')
},
extensions: ['.js', '.jsx']
},
module: {
rules: [
{
test: /\.jsx?$/,
loader: 'babel-loader',
exclude: /node_modules/
},
{
test: /\.css$/,
use: [
{ loader: 'style-loader' },
{
loader: 'css-loader',
options: {
modules: true
}
}
]
}
]
},
devtool: 'cheap-module-eval-source-map'
};
This is the CSS name that becomes hash:
To be more clear, I add the source code of how I import and use the CSS on React:
import React from 'react';
import ReactDOM from 'react-dom';
import Countdown from 'Countdown';
/* Import the CSS file */
import Styles from 'applicationStyles';
ReactDOM.render(
/* Use CSS */
<div className={Styles.box}>
<Countdown/>
</div>,
document.getElementById('app')
);
This is what Webpack does by default to avoid identical CSS classes (from different CSS modules) to collide.
Here are three things you can do:
1: At the app level, you can add the following configuration to your Webpack to disable CSS modules. It is not recommended as it could lead to collisions and hard-to-find bugs.
options: {
modules: false
}
2: At the file level, you can import it like this to prevent Webpack from obfuscating the class names. This is useful when importing third-party configuration libraries CSS files.
import '!style!css!golden-layout-css-base';
3: At the CSS class level, you can use :global(.your-class-name) to avoid obfuscating a specific class
:global(.container) {
padding: 10px;
}
In your Webpack configuration, the CSS loader needs a configuration for prefixed names. Basically localIdentName:'[local]' sets the pre-fixer as the local class name only.
For detailed info, you can look at the documentation for CSS Loader
module: {
rules: [
{
test: /\.css$/,
use: [
{ loader: 'style-loader' },
{
loader: 'css-loader',
options: {
modules: true,
localIdentName:'[local]'
}
}
]
}
]
}
The class name can be combined with an auto-generated hash using the localIdentName option of CSS Modules by setting it to [local]_[hase:base64:5].
[local] here refers to the class name.
[hash:base64:5] means generate a Base64 hash string of length 5.
{
test: /\.css$/,
use: [
'style-loader',
{
loader: 'css-loader',
options: {
modules: {
localIdentName: '[local]_[hash:base64:5]'
}
}
]
}
By setting the css-loader modules options to an object, you're essentially setting modules to true, but with specific options.
Setting the localIdentName to [local] completely defeats the purpose of using CSS Modules.
I am trying to use react-datetime on my react-on-rails app. To make the datetime work out of the box, I need to import the CSS mentioned on their GH page.
On my app, I copy/paste the CSS into a file I named DateTime.css:
...
import DateTime from 'react-datetime';
import '../../schedules/stylesheets/DateTime.css';
...
export default class AddDate extends React.Component {
But it gives me this error:
VM45976:1 Uncaught Error: Module parse failed: /Users/some/path/to/my/project/App/components/schedules/stylesheets/DateTime.css Unexpected token (1:0)
You may need an appropriate loader to handle this file type.
| .rdt {
| position: relative;
| }
It seems like the CSS loader is not working. I tried this on pure react app (create-react-app) and it worked. It broke when I did it inside react_on_rails.
This is my webpack config atm (standard out-of-the-box react_on_rails):
const webpack = require('webpack');
const { resolve } = require('path');
const ManifestPlugin = require('webpack-manifest-plugin');
const webpackConfigLoader = require('react-on-rails/webpackConfigLoader');
const configPath = resolve('..', 'config');
const { devBuild, manifest, webpackOutputPath, webpackPublicOutputDir } =
webpackConfigLoader(configPath);
const config = {
context: resolve(__dirname),
entry: {
'webpack-bundle': [
'es5-shim/es5-shim',
'es5-shim/es5-sham',
'babel-polyfill',
'./app/bundles/App/startup/registration',
],
},
output: {
// Name comes from the entry section.
filename: '[name]-[hash].js',
// Leading slash is necessary
publicPath: `/${webpackPublicOutputDir}`,
path: webpackOutputPath,
},
resolve: {
extensions: ['.js', '.jsx'],
},
plugins: [
new webpack.EnvironmentPlugin({
NODE_ENV: 'development', // use 'development' unless process.env.NODE_ENV is defined
DEBUG: false,
}),
new ManifestPlugin({ fileName: manifest, writeToFileEmit: true }),
],
module: {
rules: [
{
test: require.resolve('react'),
use: {
loader: 'imports-loader',
options: {
shim: 'es5-shim/es5-shim',
sham: 'es5-shim/es5-sham',
},
},
},
{
test: /\.jsx?$/,
use: 'babel-loader',
exclude: /node_modules/,
},
{
test: /\.css$/,
use: 'css-loader',
exclude: /node_modules/,
},
],
],
},
};
module.exports = config;
if (devBuild) {
console.log('Webpack dev build for Rails'); // eslint-disable-line no-console
module.exports.devtool = 'eval-source-map';
} else {
console.log('Webpack production build for Rails'); // eslint-disable-line no-console
}
I am very new in webpack, and not sure how to I can add loaders to make it work, how can I apply the DateTime.css file that I have to be applied to react-datetime?
EDIT: added css-loader (also updated the webpack above). It is no longer complaining that I don't have the correct loader, but the CSS does not work.
{
test: /\.jsx?$/,
use: 'babel-loader',
exclude: /node_modules/,
},
{
test: /\.css$/,
use: 'css-loader',
exclude: /node_modules/,
},
],
There are 2 conceptually different ways to approach this.
1. Using CSS modules.
This way your CSS will end up bundled with your JS and as soon as webpack loads that JS module/bundle it will automatically append CSS style element into the head.
In my project I have this rule to do exactly that (note that we use both css-loader and style-loader):
{
test: /\.css$/,
use: ['style-loader', {
loader: 'css-loader',
options: {
modules: true,
localIdentName: '[path][name]__[local]--[hash:base64:5]'
}
}]
}
More on css-loader modules at this link.
2. Using ExtractTextPlugin. This way all your CSS will be extracted into a separate file. The configuration requires 2 things: a plugin and loaders configuration created by the plugin:
const ExtractTextPlugin = require('extract-text-webpack-plugin');
// Add this to your webpack plugins array
new ExtractTextPlugin('styles.css')
And add this to your rules:
{
test: /\.css$/,
exclude: /node_modules/,
use: ExtractTextPlugin.extract({
fallback: 'style-loader',
use: ['css-loader']
})
}
This will create one single styles.css file and put all CSS you import from JS into that file.