Customizing antd Less or Css for create-react-app - reactjs

I am using create-react-app as starter kit and was having a go at antd component library.
I can't seem to find a way to customize their styles (for branding purpose). Documentation doesn't seem to give clear directions regarding customization.
Docs link: https://ant.design/docs/react/customize-theme
They suggest two ways to do it though :
1.Using theme property . But this only works for antd-init or dva-cli boilerplates and not for create-react-app
2.Overriding Less variables .
Now to make the either of these option work for create-react-app without eject , what are the steps I need to take?

You question is not very well researched I am afraid. At a minimum, read the obvious resurces like the instructions at https://ant.design/docs/react/use-with-create-react-app and older Stackoverflow questions (like How do I use .babelrc to get babel-plugin-import working for antd?) before posting a question.
As it stands, CRA does not support changing the Babel configuration. This is needed to include the antd babel-plugin, which is needed to support importing only the necessary antd modules.
As a result you can only import the whole monolithic antd in a non-ejected CRA app.

if you don't intend to use eject you should use config-overrides.js to specify how you want to modify the default webpack.config.js that is part of react-scripts. Then once you have a config-overrides you can add a less Rule that has an option for modifyVars. Here you can specify any changes to the less variables.
function addLessRule(config, env)
{
const { getLoader, loaderNameMatches } = require("react-app-rewired");
const fileLoader = getLoader(
config.module.rules,
rule => loaderNameMatches(rule, 'file-loader')
);
const lessExtension = /\.less$/;
fileLoader.exclude.push(lessExtension);
const cssRules = getLoader(
config.module.rules,
rule => String(rule.test) === String(/\.css$/)
);
var lessModifyVars = {
'primary-color': '#990000',
'link-color': '#1DA57A',
'border-radius-base': '2px',
'layout-header-background' : '#f0f2f5'
};
var lessLoaders = [
{ loader: 'style-loader'} ,
{ loader: 'css-loader' },
{ loader: 'less-loader', options: {
modifyVars: lessModifyVars
} }
];
lessRule = {
test : lessExtension,
use: lessLoaders,
}
config.module["rules"].push( lessRule );
return config
}

If you want to customize antd without using react eject and don't want to change use forked versions of create-react-app or react scripts then
you can simply create a less file (let's say main.less ) , import antd.less and replace the default variables of ant design in this main.less file.
Now compile this less file using lessc (npm i -g less).
lessc "main.less antd.css" --js
--js is for inline javascript in less
Now simply include these complied css in your app.
take a look at https://medium.com/#aksteps/782c53cbc03b

Related

React component library can not load its own static asset

I have a react app (using create react app) and then a component library (using webpack directly). One component in the component library needs to load a png file in one of the components that it exports. In the webpack config for the component library I have a section such as:
{
test: /\.(png|jpg|jpeg|gif)$/i,
type: 'asset/resource',
},
This successfully results in a file like: 29b6b66cf1a691be2b3f.png in the component libraries output directory. The issue, is that when the application uses that component and the component attempts to load the image, the img element is <img ... src="29b6b66cf1a691be2b3f.png" /> and that fails to load, since that image actually lives in the component library folder (within the react application's node_modules/component-library/ at that point).
I have scoured the internet to the best of my ability, and can not seem to figure out what the best solution would be here. Any help is appreciated. I will quickly offer clarification if needed.
UPDATE: I have discovered CopyWebpackPlugin but it is quite unfortunate that this would require me to eject the "parent" application from create react app. Something I would very much prefer to avoid.
UPDATE2: Current plan is to try following something like what is explained here. The jist of it is to utilize something like rewire to avoid needing to eject and still be able to edit the webpack config via something like:
// in ./build.js
const rewire = require('rewire');
const defaults = rewire('react-scripts/scripts/build.js');
const config = defaults.__get__('config');
// edit webpack config values here
... I will answer my own question here if I find that this approach works.
Alright, well this felt like it was much messier than it needed to be, but I was able to figure out a way to accomplish this. The pieces were as follows.
First, needed to install copy-webpack-plugin. This was not as simple as it might sound, because I needed to find one that would not conflict with the version of webpack required by my react-scripts (create react app). I determined that copy-webpack-plugin#6.4.1 was the last version to support webpack v4, so I installed that and then added the following to my package.json:
"overrides": {
"copy-webpack-plugin": {
"webpack": "4.44.2"
}
},
this ensured that it would use the version of webpack that my react-scripts installation was expecting.
Then, I also needed to install rewire, and add the following to a file called build.js:
const rewire = require('rewire');
const defaults = rewire('react-scripts/scripts/build.js');
const config = defaults.__get__('config');
const CopyPlugin = require("copy-webpack-plugin");
const patterns = [
{
context: "node_modules/component-library/path/",
from: "*.png",
to: "."
},
]
if(config.plugins === undefined){
config.plugins = [new CopyPlugin({patterns})]
}else{
config.plugins.push(new CopyPlugin({patterns}))
}
// below lets it work in dev mode too
if(config.devServer === undefined){
config.devServer = {
writeToDisk: true
}
}else{
config.devServer.writeToDisk = true;
}
Finally, had to update my build script to be:
"scripts": {
...
"build": "node ./build.js",
...
},

React : webpack.config.js modification to import json variables in a sass file?

I am trying to import variables from .json file in a .scss file with node-sass-json-importer package.
I am facing a problem because this package is not automatically integrated in react-scripts/config/webpack.config.js. So, I would like to modify this file as follows below :
Add const jsonImporter = require ('node-sass-json-importer');
Add an optional preProcessorOptions object parameter to getStyleLoaders function. Indeed, this function has no preprocessor options const getStyleLoaders = (cssOptions, preProcessor) and the only option added by default is sourceMap: true. Of course, this function will take in account this new parameter.
Add a third parameter in the getStyleLoaders call for scss file.
{
implementation: require("sass"),
sassOptions: {
importer: jsonImporter(),
}
}
It works on a minimal webpack implementation (without react). But, I suppose it is not so easy to apply changes to react-scripts/config/webpack.config.js and I suspect I will have many problems. Perhaps, there is an another way to do it.
Thanks for answer.

How to do React Native-style multiple version builds for React with Webpack

Motivation
I am maintaining an app that is white-labelled for numerous separate brands, which vary mainly in style but also sometimes in core UX. The current (Backbone) solution involves keeping shared code in a separate repo and then building the separate apps with Grunt, with much of the style code and some view overrides for each project living in its own folder. We simply run all the grunt tasks one after the other using a shell script. We're going to build new versions of this thing in React going forward and want to minimize duplicate code, which has now become a major problem in the legacy version.
Desired outcome
The React Native packager builds two versions of its app at the same time. It looks at an import statement like import ComponentA from './ComponentA.js' and goes looking for either ComponentA.android.js or ComponentA.ios.js first, then falls back to importing ComponentA.js if it doesn't find a platform-specific one. I would like to replicate this behavior in Webpack. So I would like to have a folder that looks like this:
react_clients/src/components
|_ ComponentB.js // import ComponentA from './ComponentA.js';
|_ ComponentA.js
|_ ComponentA.brand1.js
|_ ComponentA.brand2.js
Webpack should build ComponentB.js as follows:
brand1.bundle.js imports from ComponentA.brand1.js
brand2.bundle.js imports from ComponentA.brand2.js
brand3.bundle.js and brand4.bundle.js import from ComponentA.js
This would also apply to styles, ideally with the same naming convention.
If necessary, Webpack could be run separately for each version, either using different webpack.config files or accepting command line arguments. The key thing is to avoid duplicating application code.
Current code
The starting point for Webpack is a freshly-generated and ejected create-react-app project.
PS: Apologies in advance if this turns out to be a duplicate but this has been a very tricky question to research. I suspect the answer will have something to do with an advanced configuration of https://webpack.js.org/configuration/resolve/ but can't figure it out yet.
Alright folks here's what I ended up doing:
Dev
In .env.development I specify a variable for the name of the project I want to do dev on:
REACT_APP_VERSION_NAME=brand1
Then in webpack.config.dev.js I take advantage of module resolution to achieve the behavior described above:
const JS_PROJECT_EXTENSION = `.${process.env.REACT_APP_VERSION_NAME}.js`;
const STYLE_PROJECT_EXTENSION = `.${process.env.REACT_APP_VERSION_NAME}.pcss`;
const extensions = [JS_PROJECT_EXTENSION, '.js', '.json', '.jsx', STYLE_PROJECT_EXTENSION, '.pcss'];
...
module.exports = {
...
extensions,
...
}
Then in the code I can simply do the following:
import ComponentA from './componentA';
import Styles from './styles';
And everything works as expected.
Production
I don't specify REACT_APP_VERSION_NAME in .env.production. Instead the relevant config files export functions, and I iterate over the versions I want to build.
First, I created a separate version of config/paths.js that exports a function instead of a static object:
module.exports = function(projectName) {
return {
...
appBuild: resolveApp('build/' + projectName),
...
};
}
And my webpack.config.prod.js looks like this:
...
const getPaths = require('./paths.prod');
...
module.exports = function(projectName) {
const paths = getPaths(projectName);
const JS_PROJECT_EXTENSION = `.${projectName}.js`;
const STYLE_PROJECT_EXTENSION = `.${projectName}.pcss`;
const extensions = [JS_PROJECT_EXTENSION, '.js', '.json', '.jsx', STYLE_PROJECT_EXTENSION, '.pcss'];
...
return {
...
output: {
...
filename: projectName + '-assets/js/[name].[chunkhash:8].js',
chunkFilename: projectName + '-assets/js/[name].[chunkhash:8].chunk.js',
...
}
... [etc, adding projectName to any output that needs to be built separately]
};
}
Finally, just wrap most of the action in scripts/build.js in a loop:
...
[various imports]
...
process.argv[2].split(' ').forEach(projectName => {
const config = require('../config/webpack.config.prod')(projectName);
const paths = require('../config/paths.prod')(projectName);
...
[rest of build.js as normal]
}
After that it's just a matter of pointing your server at the right files for each version, and running yarn build "brand1 brand2" when you want to build.
Going to accept this answer since it's working for me for now but would love to hear about potential improvements from anyone who comes across it in the future.

How to extract multiple theme stylesheets with webpack?

I am trying to make a React app themeable. For now themes only consist of different sets of Sass variables which define different header colors, etc.
From my current understanding the ExtractTextPlugin seems to be my best bet as I don't want my styles to be inlined and rather have separate files per theme.
So I created two themes:
src/scss/themes/theme-a.scss
src/scss/themes/theme-b.scss
The themes import the basic layout and common styles and override the relevant variables.
But the only way I managed to make webpack create separate css files
for both themes was to create distinct entry points for each theme in
my webpack.prod.config:
entry: {
app: './src/js/init.js',
theme-a: './src/scss/themes/theme-a.scss',
theme-b: './src/scss/themes/theme-b.scss'
},
But adding a new entry point for every new theme that gets added feels wrong and I figure there must be a better way?
Here's your solution:
npm i --save-dev file-loader
In the loaders section, add this:
{
test: /themes\/.+\.scss$/,
loader: "file-loader?name=./compiled-themes/css/[name].css!css!sass"
},
There may be more scss files, if so there must be another section which bundles them as usual, but skips the themes:
{
test: /\.scss$/,
exclude: /themes\/.+\.scss$/,
loader: "css!sass"
},
The file loader writes files by filename, hash and extension so you are able to preserve the name.
Note the name=./compiled-themes/css/[name].css part, where [] vars are substituted.
https://github.com/webpack/file-loader
To avoid having to manually add the themes one possible solution is to create a function that reads contents of the themes folder and programatically add an entry for each *.scss file.
Something along the lines of this:
function getThemes(themePath) {
var themes = {};
fs.readdirSync(themePath).forEach(function(fileName) {
var fileNameWithPath = path.join(themePath, fileName);
var stat = fs.lstatSync(fileNameWithPath);
if (stat.isDirectory()) return;
if (!/\.scss$/.test(fileName)) return;
var nameWithoutExt = path.basename(fileName, '.scss');
themes[nameWithoutExt] = fileNameWithPath;
});
return themes;
}
var themes = getThemes('./src/scss/themes');
var config= {
entry: _.merge({ app: './src/js/init.js' }, themes),
// rest of options
};
This will require you to restart your dev-server or re-run you webpack build when adding new theme files though, but at least you won't have to touch your webpack config.

Webpack automatically require a file in all files...?

Using Webpack and building an app in React.
In all my files, I have to include to type:
var React = require('React');
seems like a useless repetition.
Is there something I can add in the config file ofwebpack to import/require React in all my files?
You can use ProvidePlugin to automatically require React when needed.
Just add to your webpack config file:
var webpack = require('webpack');
module.exports = {
// Your configuration stuff.
plugins: [
new webpack.ProvidePlugin({
React: 'react'
})
]
};
Every time React is used in a file it will be auto-required/imported without having to do it explicitly.
You only need to require it once in the entry point. While I don't use react, I only include Angular, or any other library that I use, once. Here's an example of how it might look:
// app.ts (my entry point)
namespace app {
"use strict";
//////////// Conditional Requires for Development /////////////
/* istanbul ignore if: only necessary for development environment */
if (process.env.NODE_ENV === "development") {
require("../src/index.html");
}
//////////// Require CSS /////////////////////////////////////
require("../node_modules/codemirror/lib/codemirror.css");
require("./main.css");
//////////// Require Libraries ///////////////////////////////
var angular: IAngularStatic = require("angular");
require("../node_modules/angular-resource");
require("../node_modules/angular-ui-codemirror");
require("../node_modules/angular-sanitize");
//////////// Initialize Angular //////////////////////////////
angular.module("app", [
"ui.codemirror",
"ngResource",
"ngSanitize"
]);
//////////// Require Application Components //////////////////
require("./components/durian.js");
require("./components/testbox.js");
require("./moreapplicationfiles/");
}
This is one file, which I use as the jumping off point for all required libraries and application files.
Once Webpack has packed all of the files together it will do so in the order that I have listed here into one file, so just put React above all application files that use React and they will have access to all React methods and properties. The same advice goes for Flux, Redux, jQuery, or any other library. Of course, not all libraries play nice with Webpack, but it's rare that one doesn't.
As far as adding to the config file... You can also add in additional entry points which can include your JavaScript libraries, by listing all of the libraries in an array at the "entry." You just have to make sure that it will pack these libraries first, so test that they are in the correct order:
// In your webpack.config.js file
{
entry: [ "./node_modules/react", "./app.js"],
}

Resources