Obfuscate React classNames in existing codebase - reactjs

Hi guys
I am working on a big React application with an existing code-base (100+ components). Currently, we are using the traditional styling method, example:
JSX:
<div className="div" />
SCSS:
.div {
/* ... */
}
We are using webpack with these loaders:
/* ... */
use: [
{
loader: MiniCssExtractPlugin.loader,
options: {
publicPath: '../'
}
},
'css-loader',
'postcss-loader',
{
loader: 'sass-loader',
options: { implementation: sass }
}
]
/* ... */
Is there any way to obfuscate these classNames from both JSX and SCSS without rewriting the whole thing? Is it also possible to rewrite only some components to be obfuscated or do we have to rewrite it all?
Thanks.

css-loader has a modules option which when set to true obfuscates class names. Unfortunately, this will obfuscate all of the class names and will require you to change all usages of <div className="div" /> to:
import styles from <stylesheet>;
<div className={styles.div} />
Edit:
The only way I can think of avoiding having to change all of the usages at once is by splitting your css config in webpack with two patterns, for example:
{
test: /.*dirA\/.*\.css/,
use: [{ loader: 'style-loader' }, { loader: 'css-loader', options: { modules: true } }, ...]
},
{
test: /.*dirB\/.*\.css/,
use: [{ loader: 'style-loader' }, { loader: 'css-loader' }, ...]
}

Solution for ejected Create-react-app
Example classNames: a_a, a_b, a_c .... etc.
Eject app
Install incstr
Create getScopedName.js at the config folder.
const incstr = require('incstr');
const createUniqueIdGenerator = () => {
const uniqIds = {};
const generateNextId = incstr.idGenerator({
alphabet: 'abcefghijklmnopqrstuvwxyzABCEFGHJKLMNOPQRSTUVWXYZ',
});
return (name) => {
if (!uniqIds[name]) {
uniqIds[name] = generateNextId();
}
return uniqIds[name];
};
};
const localNameIdGenerator = createUniqueIdGenerator();
const componentNameIdGenerator = createUniqueIdGenerator();
module.exports = (localName, resourcePath) => {
const componentName = resourcePath
.split('/')
.slice(-2, -1)[0];
const localId = localNameIdGenerator(localName);
const componentId = componentNameIdGenerator(componentName);
return `${componentId}_${localId}`;
};
Now...
Open the webpack.config.js and add:
const getScopedName = require('./getScopedName')
Find the ~445-460 rows and replace with:
{
test: cssModuleRegex,
use: getStyleLoaders({
importLoaders: 1,
sourceMap: isEnvProduction && shouldUseSourceMap,
modules: {
...(isEnvDevelopment ? {
localIdentName: '[path]_[name]_[local]',
} : {
getLocalIdent: (context, localIdentName, localName) => (
getScopedName(localName, context.resourcePath)
),
})
},
}),
}
It's all. :)

Related

Issues with style-loader lazyloading for multiple elements in shadow-dom

I'm currently working with webpack and style-loader to inject my styles into components that use shadow-dom. However, the issue happens when I try to use multiple instances of the element (the styles stop injecting). I was able to properly solve this issue with another component by adding the unuse function to my disconnectCallback for that component. Unfortunately, for this component below, I expect it to appear multiple times at once on a page. Am I doing something wrong?
Component.tsx:
import React from 'react';
import { createRoot } from 'react-dom/client';
// Styles
import styles from '../styles/contentScript.css';
// Images
const icon = chrome.runtime.getURL('assets/icon.png');
export default class CustomButton extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
// Functions
this.handleShow = this.handleShow.bind(this);
}
handleShow = (e: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
...
};
connectedCallback() {
// Inject Styles
styles.use({ target: this.shadowRoot });
// Inject Component into ShadowRoot
createRoot(this.shadowRoot).render(this.render());
}
render() {
return (
<div className='absolute inset-0'>
<button className='main-button group' onClick={this.handleShow}>
<img src={icon} className='w-7' />
<span className='main-button-tooltip group-hover:scale-100'>
Open Popup
</span>
</button>
</div>
);
}
disconnectedCallback() {
styles.unuse();
}
}
customElements.define('custom-button', CustomButton);
webpack.config.js
// Imports...
module.exports = {
entry: {
...
},
module: {
rules: [
{
use: 'ts-loader',
test: /\.tsx?$/,
exclude: /node_modules/,
},
{
use: [
{
loader: 'style-loader',
options: {
injectType: 'lazyStyleTag',
insert: function insertIntoTarget(element, options) {
var parent = options.target || document.head;
parent.appendChild(element);
},
},
},
'css-loader',
{
loader: 'postcss-loader',
options: {
postcssOptions: {
indent: 'postcss',
plugins: [tailwindcss, autoprefixer],
},
},
},
],
test: /\.css$/i,
},
{
type: 'asset/resource',
test: /\.(png|jpg|jpeg|gif)$/i,
},
]
},
resolve: {
...
},
plugins: [
...
],
output: {
filename: '[name].js',
clean: true,
},
optimization: {
...
},
}
I should also note (in case it's important) I am using tailwind for styling so I've included postcss and autoprefixer. This is also for a chrome extension so I'm creating this component in my contentScript. I have also tried it without the unuse call in my disconnectCallback and faced the same issue.
to follow the discussion on github, if you're only browser target is Chrome, then I really suggest you to use the CSSStyleSheet class.
In that case, you should drop from your webpack configuration the style-loader, as it's not needed anymore:
{
loader: 'style-loader',
options: {
injectType: 'lazyStyleTag',
insert: function insertIntoTarget(element, options) {
var parent = options.target || document.head;
parent.appendChild(element);
},
},
},
Then modify the configuration of the css-loader to have the option exportType = "css-style-sheet" (https://webpack.js.org/loaders/css-loader/#exporttype).
In this way, the exported element is already an object of type CSSStyleSheet, and you can use it directly on your web component:
import sheet from "./styles.css" assert { type: "css" };
document.adoptedStyleSheets = [sheet];
shadowRoot.adoptedStyleSheets = [sheet];
Normally, the 'postcss' step should not pose problems.
No need anymore to use "use / unuse" then (because that's an API of style-loder that you should remove with this solution)

React does not load local svg

I need to load a local SVG calling as a string './MySvg.svg', but it just works if i call it as a component, like <MySvg />.
I followed this tutorial https://blog.logrocket.com/how-to-use-svgs-in-react/, installed file-loader but the problem persists.
I'm trying to put a custom svg on the nodes of a graph with react-d3-graph, but it needs the svg locally as string and will not accept as a component.
Svgteste just exports the svg tag of svgFile.svg
An example with a component
import * as React from 'react';
import { PageSection, Title } from '#patternfly/react-core';
import { Svgteste } from './Svgteste'
import { Test } from './svgFile.svg'
const Dashboard: React.FunctionComponent = () => {
return(
<PageSection>
<Svgteste /> {/*this works*/}
<img src={'./svgFile.svg'} /> {/*this won't work*/}
<img src={Test} /> {/*neither this*/}
</PageSection>
)
}
export { Dashboard };
My webpack
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const TsconfigPathsPlugin = require('tsconfig-paths-webpack-plugin');
const Dotenv = require('dotenv-webpack');
const BG_IMAGES_DIRNAME = 'bgimages';
module.exports = env => {
return {
entry: {
app: path.resolve(__dirname, 'src', 'index.tsx')
},
module: {
rules: [
{
test: /\.(tsx|ts|jsx)?$/,
use: [
{
loader: 'ts-loader',
options: {
transpileOnly: true,
experimentalWatchApi: true,
}
}
]
},
{
test: /\.(svg|ttf|eot|woff|woff2)$/,
// only process modules with this loader
// if they live under a 'fonts' or 'pficon' directory
use: {
loader: 'file-loader',
options: {
// Limit at 50k. larger files emited into separate files
limit: 5000,
outputPath: 'fonts',
name: '[name].[ext]',
}
}
},
{
test: /\.svg$/,
include: input => input.indexOf('background-filter.svg') > 1,
use: [
{
loader: 'url-loader',
options: {
limit: 5000,
outputPath: 'svgs',
name: '[name].[ext]',
}
}
]
},
{
test: /\.svg$/,
// only process SVG modules with this loader if they live under a 'bgimages' directory
// this is primarily useful when applying a CSS background using an SVG
include: input => input.indexOf(BG_IMAGES_DIRNAME) > -1,
use: {
loader: 'svg-url-loader',
options: {}
}
},
{
test: /\.svg$/,
// only process SVG modules with this loader when they don't live under a 'bgimages',
// 'fonts', or 'pficon' directory, those are handled with other loaders
include: input => (
(input.indexOf(BG_IMAGES_DIRNAME) === -1) &&
(input.indexOf('fonts') === -1) &&
(input.indexOf('background-filter') === -1) &&
(input.indexOf('pficon') === -1)
),
use: {
loader: 'raw-loader',
options: {}
}
},
{
test: /\.(jpg|jpeg|png|gif)$/i,
use: [
{
loader: 'url-loader',
options: {
limit: 5000,
outputPath: 'images',
name: '[name].[ext]',
}
}
]
},
{
test: /\.svg$/,
use: ['#svgr/webpack'],
},
{
test: /\.(png|jp(e*)g|svg|gif)$/,
use: [
{
loader: 'file-loader',
options: {
//name: 'images/[hash]-[name].[ext]',
},
},
],
},
]
},
output: {
filename: '[name].bundle.js',
path: path.resolve(__dirname, 'dist')
},
plugins: [
new HtmlWebpackPlugin({
template: path.resolve(__dirname, 'src', 'index.html')
}),
new Dotenv({
systemvars: true,
silent: true
})
],
resolve: {
extensions: ['.js', '.ts', '.tsx', '.jsx'],
plugins: [
new TsconfigPathsPlugin({
configFile: path.resolve(__dirname, './tsconfig.json')
})
],
symlinks: false,
cacheWithContext: false
}
}
};
I think, you have to remove the curly braces in the import. The curly braces indicate that you want to import a component that is exported with the given name, which is not the case when you import an svg.
import Test from './svgFile.svg'

ReferenceError: document is not defined when refresh nextjs page

i am trying to create a simple UI library using react for Nextjs 9.4, here what i am doing
// input.js in React UI Lib
import React from "react";
import styled from "./input.module.scss";
const Input = React.forwardRef((props, ref) => (
<>
{props.label && <label className={styled.label}>{props.label}</label>}
<input className={styled.input} {...props} ref={ref} />
</>
));
export default Input;
and made an index to export all modules for simplicity
// app.js the index file for the lib
import PrimaryButton from "./components/button/primaryButton";
import TextInput from "./components/input/input";
import PasswordInput from "./components/passwordInput/password";
import CheckBox from "./components/checkbox/checkbox";
export {
PrimaryButton,
TextInput,
PasswordInput,
CheckBox
};
also here is my webpack config to build for SSR Next
const path = require("path");
const autoprefixer = require("autoprefixer");
const nodeExternals = require("webpack-node-externals");
const CSSLoader = {
loader: "css-loader",
options: {
modules: "global",
importLoaders: 2,
sourceMap: false,
},
};
const CSSModlueLoader = {
loader: "css-loader",
options: {
modules: true,
importLoaders: 2,
sourceMap: false,
},
};
const PostCSSLoader = {
loader: "postcss-loader",
options: {
ident: "postcss",
sourceMap: false,
plugins: () => [autoprefixer()],
},
};
const SassLoader = {
loader: "sass-loader",
options: {
// Prefer `dart-sass`
implementation: require("sass"),
},
};
module.exports = {
target: "node",
entry: "./src/app.js",
output: {
path: path.resolve(__dirname, "dist"),
filename: "bundle.js",
chunkFilename: "[id].js",
publicPath: "",
library: "",
libraryTarget: "commonjs",
},
externals: [nodeExternals()],
resolve: {
extensions: [".js", ".jsx"],
},
module: {
rules: [
{
test: /\.(js|jsx)$/,
loader: "babel-loader",
exclude: /node_modules/,
},
{
test: /\.(sa|sc|c)ss$/i,
exclude: [/node_modules/, /\.module\.(sa|sc|c)ss$/i],
use: ["style-loader", CSSLoader, PostCSSLoader, SassLoader],
},
{
test: /\.module\.(sa|sc|c)ss$/i,
exclude: /node_modules/,
use: ["style-loader", CSSModlueLoader, PostCSSLoader, SassLoader],
},
{
test: /\.(png|jpe?g|gif)$/,
loader: "url-loader?limit=10000&name=img/[name].[ext]",
},
],
},
};
1-i build
2-publush on npm
3-import in Nextjs
then everything works well , but the problem is when i try to refresh (F5) the page during development i get the error
Unhandled Runtime Error
ReferenceError: document is not defined
how can i fix that ?
try to render component only in client side you can do with:
typeof window !== 'undefined' ? <Component /> : null
you are using style-loader in your webpack config, it will inject styles into head using document.createElement that is not availabe in SSR, you can choose other options like mini-css-extract-plugin
const Example = dynamic( () => import('example'), { ssr: false } )
https://github.com/elrumordelaluz/reactour/issues/130
try to check component in client side rendering ex:
const isBrowser = typeof window !== 'undefined';
isBrowser ? <Component/> : null;
another option is try to render using ssr false:
const DynamicComponentWithNoSSR = dynamic(
() => import('../components/hello3'),
{ ssr: false }
)
Thanks..
You may not always want to include a module on server-side. For example, when the module includes a library that only works in the browser.
Take a look at the following example:
import dynamic from 'next/dynamic'
const DynamicComponentWithNoSSR = dynamic(
() => import('../components/hello3'),
{ ssr: false }
)
function Home() {
return (
<div>
<Header />
<DynamicComponentWithNoSSR />
<p>HOME PAGE is here!</p>
</div>
)
}
export default Home

Storybook UI with CSS modules and Less

Is it possible to use Storybook UI with React, CSS modules and Less? Have anyone managed to configure this kind of setup?
Adding .storybook/webpack.config.js helped me fix the issue, with
module.exports = {
module: {
rules: [
{
test: /\.css$/i,
use: ['style-loader'],
}, {
test: /\.css$/,
use: {
loader: "css-loader",
options: {
modules: true,
}
}
}
],
},
}
I had same case. Resolved by adding webpackFinal to .storybook/main.js :
module.exports = {
stories: ['../src/**/*.stories.[tj]s'],
webpackFinal: async (config, { configType }) => {
config.module.rules.push(
{
test: /\.less$/,
use: [
require.resolve('style-loader'),
{
loader: require.resolve('css-loader'),
options: {
modules: true,
importLoaders: 1,
localIdentName: '[name]__[local]___[hash:base64:5]'
},
},
require.resolve('less-loader')
]
},
);
return config;
},
};
With sass as example:
Step 1: configure webpack in main.js. You can find documentation here: https://storybook.js.org/docs/configurations/custom-webpack-config/
const path = require('path');
module.exports = {
webpackFinal: async (config, { configType }) => {
// `configType` has a value of 'DEVELOPMENT' or 'PRODUCTION'
// You can change the configuration based on that.
// 'PRODUCTION' is used when building the static version of storybook.
// Make whatever fine-grained changes you need
config.module.rules.push({
test: /\.scss$/,
use: ['style-loader', 'css-loader', 'sass-loader'],
include: path.resolve(__dirname, '../'),
});
// Return the altered config
return config;
},
stories: ['../stories/**/*.stories.js'],
};
Step 2: Install sass-loader
https://webpack.js.org/loaders/sass-loader/
Step 3: Create your scss files
#import "../stories/main.scss";
h1{color:$mediumBlue}
Modify your .storybook.main.js
module.exports = {
stories: ['../src/**/*.stories.js'],
addons: [
'#storybook/preset-create-react-app',
'#storybook/addon-actions',
'#storybook/addon-links',
],
webpackFinal: async (webpackConfig, { configType }) => {
// `configType` has a value of 'DEVELOPMENT' or 'PRODUCTION'
// You can change the configuration based on that.
// 'PRODUCTION' is used when building the static version of storybook.
const { loadCracoConfig } = require('#craco/craco/lib/config');
const { getCraPaths } = require('#craco/craco/lib/cra');
const context = {env: process.env.NODE_ENV};
const cracoConfig = loadCracoConfig(context);
context.paths = getCraPaths(cracoConfig);
const {overrideWebpackConfig} = require('#semantic-ui-react/craco-less');
overrideWebpackConfig({
context,
webpackConfig
});
// Return the altered config
return webpackConfig;
},
};
This is taken from node_modules/#craco/craco/scripts/start.js
localIdentName option moved in css-loader configuration so this is the new configuration.
source: https://github.com/rails/webpacker/issues/2197#issuecomment-517234086
module.exports = {
stories: ['../src/**/*.stories.mdx', '../src/**/*.stories.#(js|jsx|ts|tsx)'],
webpackFinal: async (config) => {
config.module.rules.push({
test: /\.less$/,
use: [
require.resolve('style-loader'),
{
loader: require.resolve('css-loader'),
options: {
importLoaders: 1,
modules: {
localIdentName: '[name]__[local]___[hash:base64:5]',
},
},
},
require.resolve('less-loader'),
],
});
return config;
},
};

tailwind css with css modules in next.js

How to config next.js to support Simultaneous tailwind css with css modules?
I want tailwindcss wrap whole project:
// /tailwind.scss
:global {
#import "tailwindcss/base";
#import "tailwindcss/components";
#import "tailwindcss/utilities";
}
// /test-module.css
.example {
font-size: 36px;
}
// /pages/_app.jsx
import '../talwind.scss';
...
And in a sample component:
// /components/my-component.jsx
import css from '../test-module.css';
const Test = () => (
<div className={`bg-red-500` ${css.example}}>Test Tailwind with CSS</div>
);
A solution is split webpack style loader. A loader for global css another for css modules loader so webpack loader is looks like below:
{
test: /\.s?[ac]ss$/,
exclude: /\.global.css$/,
use: [
{
loader: MiniCssExtractPlugin.loader,
options: {
hmr: !isProduction,
reloadAll: true,
},
},
// 'css-loader',
{
loader: 'css-loader',
options: {
importLoaders: 1,
modules: {
localIdentName: '[name]__[local]___[hash:base64:5]',
},
},
},
'postcss-loader',
{ loader: 'sass-loader', options: { sourceMap: true } },
],
},
{
test: /\.global.css$/,
use: [
{
loader: MiniCssExtractPlugin.loader,
options: {
hmr: !isProduction,
reloadAll: true,
},
},
'css-loader',
'postcss-loader',
],
},
I had the same problem, I did the following and it worked:
const Test = () => (
<div className={["bg-red-500", css.example].join(" ")}>
Test Tailwind with CSS
</div>
);
You need to write classname like this
const App = () => {
<h1 className={[style[`bg-teal-600`], style.holi].join(' ')}>Holi</h1>;
};

Resources