React: Inline CSS modules in JSX with Webpack - css

I have a minimal React component which consists of two files: button.jsx and button.less. The styles are imported and the class names are appended with a hash to make all styles local to the component.
This is great, but i'd like to have all component code in one file. Is it possible to inline the styles in jsx file without losing css modularity?
Current Code
button.jsx
import React from 'react';
import styles from './button.less'
export default class Button extends React.Component {
render() {
return <button className={styles.primary}>{this.props.text}</button>;
}
}
button.less
#import '~semantic-ui/src/definitions/elements/button.less';
.common {
composes: ui button;
}
.primary {
composes: common primary;
}
webpack.config.js (relevant bits)
module: {
loaders: [
{
test: /\.jsx$/,
loader: 'babel'
},
{
test: /\.less$/,
loader: "style!css?modules&importLoaders=1!less"
}
]
},
What i'd like to write instead
button.jsx
<style lang="less" modules>
#import '~semantic-ui/src/definitions/elements/button.less';
.common {
composes: ui button;
}
.primary {
composes: common primary;
}
</style>
import React from 'react';
export default class Button extends React.Component {
render() {
return <button className={styles.primary}>{this.props.text}</button>;
}
}
Inspired by vue.js and vue-loader.
I believe this is a duplicate of this unanswered question:
Using css-loader inline with Webpack + React

I wrote a Webpack loader for this very purpose:
https://github.com/chrisdavies/stylextract-loader
It allows you to write a single style tag per JSX file and it supports webpack CSS modules, too, if you want.
At build time, it extracts the rules from your style tag and moves them out to an external CSS file.
I should note that because it simply extracts your rules out to an external CSS file, it plays nice with SASS, autoprefixer, etc

You can use callback-loader for this. This is actualy a workaround, but it do the trick. Just implement a callback which will extract your css-code and replace it with appropriate import. For example:
webpack.config.js
var fs = require('fs');
var cssIndex = 0;
// Do not forget to create and clean temporary folder "cssTemp" before
var webpackConfig = {
...
resolve: {
alias: {
cssTemp: path.resolve('./cssTemp')
}
},
module: {
loaders: [
{ test: /\.jsx$/, loader: "callback!babel" }
]
},
callbackLoader: {
cssCallback: function(code) {
var filename = cssIndex + '.less';
cssIndex++;
// Save the css code from the callback argument
fs.writeFileSync('./cssTemp/' + filename, code);
// Return the import statement which will replace the callback statement
return 'import styles from "cssTemp/' + filename + '";';
}
}
...
};
button.jsx
import React from 'react';
cssCallback(`
#import '~semantic-ui/src/definitions/elements/button.less';
.common {
composes: ui button;
}
.primary {
composes: common primary;
}
`);
export default class Button extends React.Component {
render() {
return <button className={styles.primary}>{this.props.text}</button>;
}
}

Related

#aws-amplify/ui-react how to customize UI if my project uses SASS?

I'm extending a Next.js (React) project that was built by someone else, which uses .scss files (SASS) for styling. This is the project in question https://github.com/codebushi/nextjs-starter-dimension
Now I'm adding an authentication flow to the project using #aws-amplify/ui-react. Everything works fine, but I want to customize the UI style. I've found in the documentation that I can do that through :root in globals.css, as so:
:root {
--amplify-primary-color: #ff6347;
--amplify-primary-tint: #ff7359;
--amplify-primary-shade: #e0573e;
}
Documentation here: https://docs.amplify.aws/ui/customization/theming/q/framework/react
I know pretty much nothing about SASS except the basics. How would I do the equivalent of setting those variables in :root?
Edit with more details
This is my next.config.js:
module.exports = {
webpack: (config, { dev }) => {
config.module.rules.push(
{
test: /\.scss$/,
use: ['raw-loader', 'sass-loader']
}
)
return config
}
}
This is my authentication page where the Amplify elements are defined:
import { useState, useEffect } from 'react'
import { AuthState, onAuthUIStateChange } from '#aws-amplify/ui-components';
import { AmplifyAuthenticator, AmplifyAuthContainer, AmplifySignUp, AmplifySignIn, AmplifySignOut } from '#aws-amplify/ui-react';
import Router from 'next/router'
const Auth = (props) => {
const [authState, setAuthState] = useState();
const [user, setUser] = useState();
useEffect(() => {
return onAuthUIStateChange((nextAuthState, authData) => {
setAuthState(nextAuthState);
setUser(authData);
//console.log(`authData: ${JSON.stringify(authData, null, 2)}`);
});
}, []);
if (authState === AuthState.SignedIn && user) {
Router.push('https://mywebsite')
return <p>Redirecting...</p>
}
return (
<AmplifyAuthContainer>
<AmplifyAuthenticator usernameAlias="email">
<AmplifySignUp
slot="sign-up"
usernameAlias="email"
formFields={[
{
type: "email",
label: "Email Address *",
placeholder: "Enter your email address",
inputProps: { required: true },
},
{
type: "password",
label: "Password *",
placeholder: "Enter your password",
inputProps: { required: true },
},
]}
/>
<AmplifySignIn slot="sign-in" usernameAlias="email" />
</AmplifyAuthenticator>
</AmplifyAuthContainer>
);
}
export default Auth;
As per the answer below from Sean W, I've already tried creating an _app.js with:
import '../styles/global.scss'
const App = ({ Component, pageProps }) => {
return <Component {...pageProps} />
}
export default App;
with global.scss:
:root {
--amplify-primary-color: #ff6347;
--amplify-primary-tint: #ff7359;
--amplify-primary-shade: #e0573e;
}
But the CSS variables don't seem to be replaced.
Include the styles in your pages. The easiest way is to create a new SCSS file and include it in a custom _app.js.
By default, Next supports SASS - you only need to install the sass npm package and likely do not need a custom next.config. This could be one of your problems.
file - global.scss -
:root{
--amplify-primary-color: #ff6347;
--amplify-primary-shade: #e0573e;
}
//scoped & redeclared to an element with id #__next
:root #__next {
--amplify-primary-color: #ff6347;
--amplify-primary-shade: #e0573e;
}
Amplify could also be setting setting styles after you have already defined them for the :root - if this is the case you will need to scope your custom styles to take precedence over the default Amplify CSS variables. To do this - redeclare them in a CSS rule that is more specific than :root like the example above - keeping order of precedence in mind
Amplify also lets you directly target components.
amplify-authenticator {
--amplify-primary-color: #ff6347;
background: var(--amplify-primary-color);
padding: 5px;
}
// scoped & redeclared
:root #__next amplify-sign-in{
--amplify-primary-color: #ff6347;
}
The amplify elements that can be targeted are
amplify-authenticator
amplify-sign-in
amplify-confirm-sign-in
amplify-sign-up
amplify-confirm-sign-up
amplify-forgot-password
amplify-require-new-password
amplify-verify-contact
amplify-totp-setup
import your file - pages/_app.js
import 'path/to/global.scss';
const App = ({ Component, pageProps }) => <Component {...pageProps} />;
export default App;
Global variables (the equivalent of :root variables) can be created simply with this line.
$color = #c0ff33 !important;
The content can be anything that is available in css and partially sass.
For large projects, creating a variables.scss file and including in it all these variable declarations can be truly helpful for changing multiple variables at once. To include this file, just do #import "./variables.scss" at the top of your file.
Hope this helps! Good luck in your sassy endeavors!

separate styles for separate module in Angular 5

My project has two main parts. One for public web pages and the other for admin control panel. Each of them has separate CSS and javascript files for their template.
If I define all CSS and js files in index.html, all files load in the first meet of the web page, and also maybe have a conflict between CSS classes.
How can I handle this problem?
app.component:
<router-outlet></router-outlet>
app-routing.module:
import { NgModule } from "#angular/core";
import { Routes, RouterModule } from "#angular/router";
import { FirstComponent } from './first/first.component';
const appRoutes: Routes = [
{ path: 'first', component: FirstComponent },
{
path: 'controlpanel',
loadChildren: 'app/control-panel/control-panel.module#ControlPanelModule'
},
{
path: 'publicpanel',
loadChildren: 'app/public-panel/public-panel.module#PublicPanelModule'
}
];
each module has its submodules. Can I separate their styles?
Use sass and create a class flag for public and admin components
like this
theme/_public.scss
.public{
label {
color:red;
}
}
theme/_admin.scss
.admin {
label {
color:green;
}
}
and this in main style.scc
#import "theme/_public.scss";
#import "theme/_admin.scss";
this is much better for app performance you will have one style file with public and admin pages style
stackblitz example
I found the solution. We can disable or enable css files in component.
document.styleSheets[2].disabled = false;
or
document.styleSheets[2].disabled = true;
that's it.

Extract CSS from SCSS and deferred lazy load in React app

I have a few SCSS theme files I want to extract to CSS files and later load them into the page. I want to be able to use contenthash for long term caching.
Since I'm using Webpack 4, I am also using mini-css-extract-plugin. I started down the path of creating a splitChunks in my webpack config.
// webpack.config.js
module.exports = {
plugins: [
new MiniCssExtractPlugin({
// Options similar to the same options in webpackOptions.output
// both options are optional
filename: "[name].[contenthash].css",
chunkFilename: "[id].[contenthash].css"
})
],
optimization: {
splitChunks: {
cacheGroups: {
'vendor': {
// custom commons chunk for js
},
'theme-a': {
test: /theme-a.\scss/,
},
'theme-b': {
test: /theme-b.\scss/,
},
// more themes
}
}
}
module: {
rules: [
{
test: /\.scss$/,
use: [
MiniCssExtractPlugin.loader,
"css-loader",
"sass-loader"
]
}
]
}
}
I've then tried dynamically importing the css in my app:
// app.js
class App extends React.Component {
// constructor
login(themeName) {
import(/* webpackChunkName: "`${themeName}`" */ `./path/to/${themeName}.scss`).then(theme => {
// do something with `theme`
}
}
// other stuff
}
I need to be able to load that css file dynamically in login() and I'm just not sure how to reference it when it has a generated [contenthash].
TLDR: Is there a good way to both extract css and import the referenced CSS bundle to lazy load later? I'm not tied to mini-css-extract-plugin.
Edit: Created a mini-css-extract-plugin issue here.
My solution ended up using extract-text-webpack-plugin. My config now looks like this:
// webpack.config.js
const ExtractTextPlugin = require('extract-text-webpack-plugin');
const ExtractThemeA = new ExtractTextPlugin({ filename: 'themeA.[hash].css', allChunks: true});
module.exports = {
plugins: [
ExtractThemeA,
// more plugins for other css files
],
optimization: {
splitChunks: {
cacheGroups: {
// Note: No changes to splitChunks
'vendor': {
// custom commons chunk for js
}
}
}
module: {
rules: [
{
test: /theme-a\.scss$/,
use: ExtractThemeA.extract([ 'css-loader', 'sass-loader' ])
},
// more module rules for each css file needed
]
}
}
Then, these chunks are available by file name in my HtmlWebpackPlugin:
<!-- HtmlWebpackPlugin Template -->
<script>
// provides me with an array of file name strings
var themesManifest = <%= htmlWebpackPlugin.files.css %>
</script>
Sorry for my miss understanding,
You could probably just make two different scss-files and import them as needed. theme.scss admin.scss or like so
This is how I am doing scss in React right now
In App.js
import styles from '../../stylesheet/main.scss'
// could be as well
import styles1 from '../../stylesheet/theme.scss' // some file
import styles2 from '../../stylesheet/admin.scss' // some other file
const App = () => {
<div className={styles.action_feed} >{ content }</div>
}
In main.scss
.action_feed {
position: fixed;
width: 350px;
height: auto;
max-height: 100%;
max-width: 100%;
top: 0;
left: 0;
z-index: 9999;
}
I think you could just as well do it like so
const themeName = 'main'
import(`./stylesheet/${themeName}.scss`, (css) => {
// meaby set this to state? not quite sure how should properly handle
// dynamically imported css
this.setState({ css: css.action_feed })
// or possible this way. I have done some non-React dom manipulation
// this way before
document.querySelector('body').classList.add(css.action_feed)
})
<div className={this.state.css}>{ content }</div>
You should probably check out React's new Refs API as well. It might give you some nice flexibility for giving className-attr to required element.
Having set to splitChunks.chunks to all works though i think in this case anyway

How can I use sass class names as it is without using any object webpack sass loader ?

I have to use the classNames as like it is like below
import style from './style.scss'
class MyComponent extends Component {
render() {
return <button className="my-button">OK</button>;
}
}
usually we are using style['my-button'] Is it possible to avoid the object style and use like above ?
You can just import the stylesheet:
import './style.scss';
Just importing file should work fine, but if it doesn't, you have to check your webpack.config - in my case (webpack 2) that's all what I need to deal with sass styles (single rule):
{
test: /\.scss$/,
exclude: /node_modules/,
use: [
'style-loader',
'css-loader',
'sass-loader'
]
}
Other way to import styles, if you need only a few classes, is to import them with es2015 syntax:
import { greenBck, btnShadow } from '../styles/main.scss';
const MyButton = () => {
return (
<button className="btn greenBck btnShadow">GreenButton</button>
);
}
I believe this webpack config could help you. It allows the use of classes as you are suggesting above.
Make sure to rebuild after changing your config. We all forget...
NOTE: The code below for the webpack.config.js are snippets, so make sure you read my comments.
/**
* webpack.config.js
*/
// ADD THE ExtractTextPlugin
var ExtractTextPlugin = require('extract-text-webpack-plugin')
// ADD THIS TO YOUR PLUGINS ARRAY
new ExtractTextPlugin('[your-css].css')
// ADD THIS TO YOUR module.loaders ARRAY
{
// sass -to-> css
test: /\.scss$/,
loader: ExtractTextPlugin.extract(
'style', // The backup style loader
'css?sourceMap!sass?sourceMap'
)
}
/**
* react file
*/
import style from './[path]/[your-css].css.scss'
... use your classes as normal ...

where do you import css in angular2 cli

when we try to import 3rd party libs in angular 2 cli we are using this,
var Angular2App = require('angular-cli/lib/broccoli/angular2-app');
module.exports = function(defaults) {
return new Angular2App(defaults, {
vendorNpmFiles: [
'#angular2-material/**/*.js'
]
});
};
and in system-config.ts we write it like this,
/** Map relative paths to URLs. */
const map: any = {
'#angular2-material': 'vendor/#angular2-material'
};
/** User packages configuration. */
const packages: any = {
'#angular2-material/core': {
format: 'cjs',
defaultExtension: 'js',
main: 'core.js'
},
'#angular2-material/checkbox': {
format: 'cjs',
defaultExtension: 'js',
main: 'checkbox.js'
},
// And so on...
};
and in component
import { Component } from '#angular/core';
import { MdCheckbox } from '#angular2-material/checkbox';
#Component({
selector: 'my-app',
template: `<md-checkbox></md-checkbox>`,
directives: [MdCheckbox]
})
export class AppComponent { }
all library is in .js but what if it's css how do we import it?
like how to use fontawesome , sweetalert or bootstrap ?
If you'd like to pull in a style library (bootstrap/font-awesome) you can place the files in the generated public directory.
Within there I create a style directory and place the files in there for example....
[project-root]/public/style/bootstrap.css
When builds run it will take the contents of public and move it to dist...
[project-root]/dist/style/bootstrap.css
And you can reference these files within index.html like so...
<link rel="stylesheet" href="style/bootstrap.css">

Resources