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

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)

Related

How dynamically imports images from my assets folder?

I've follow this tutorial to get the boiler plate code for my project.
The problem: my program is some kind of gallery of images that reads a folder to get the assets, so i don't have acces to the name of the files and i can't make a static import and the dynamic import did'nt find the assets.
My code:
import React from "react";
interface Props {
mock?: false;
src: string;
title: string;
compiler: string;
}
interface MockProps {
mock: true;
[x: string]: any;
}
const mockProps: Props = {
mock: false,
src: "../../public/assets/images/asd.png",
title: "./",
compiler: "",
};
export default function AlbumCard(props: Props | MockProps): JSX.Element {
const { mock } = props;
const realProps = mock ? mockProps : props;
return (
<div>
<div>
<img src={realProps.src} />
</div>
</div>
);
}
My webpack file:
module.exports = {
module: {
rules:[ {
test: /\.node$/,
use: "node-loader",
},
{
test: /\.(m?js|node)$/,
parser: { amd: false },
use: {
loader: "#marshallofsound/webpack-asset-relocator-loader",
options: {
outputAssetBase: "native_modules",
},
},
},
{
test: /\.tsx?$/,
exclude: /(node_modules|\.webpack)/,
use: {
loader: "ts-loader",
options: {
transpileOnly: true
}
}
},
{
test: /\.(png|jpe?g|gif|jp2|webp)$/,
loader: "file-loader",
options: {
name: "[name].[ext]",
},
},
{
test: /\.css$/,
use: [{ loader: "style-loader" }, { loader: "css-loader" }],
}]
},
plugins: plugins,
resolve: {
extensions: [".js", ".ts", ".jsx", ".tsx", ".css", ".png"]
},
};
What i get:
Failed to load resource: the server responded with a status of 404 (Not Found)
What am I missing here?

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

Nrwl Storybook React Not Rending Images

I have a react project built with Nrwl. I just noticed that all the images in my components are not loading. I researched a bit tried different techniques but couldn't fix it. I have these files for example
libs/src/lib/components/Nav/Nav.tsx
import React, { FC, MouseEvent } from "react";
import NavStyled from "./Nav.style";
export const Nav: FC<NavProps> = ({ click }: NavProps) => {
return (
<NavStyled>
<div className="nav__container">
<a className="logo" href="/">
<img src="../../assets/icons/logo.svg"/>
</a>
</div>
</NavStyled>
);
};
export default Nav;
libs/src/lib/components/Nav/Nav.stories.tsx
import React from "react";
import Nav from "./Nav";
export default {
component: Nav,
title: "Components/Nav"
};
export const defaultStory = () => <Nav />;
libs/storybook/.storybook/webpack/config.js
const rootWebpackConfig = require("../../../.storybook/webpack.config");
// Export a function. Accept the base config as the only param.
module.exports = async ({ config, mode }) => {
config = await rootWebpackConfig({ config, mode });
config.resolve.extensions.push(".tsx");
config.resolve.extensions.push(".ts");
config.module.rules.push({
test: /\.(ts|tsx)$/,
loader: require.resolve("babel-loader"),
options: {
presets: [
"#babel/preset-env",
"#babel/preset-react",
"#babel/preset-typescript"
]
}
});
return config;
};
.storybook/webpack.config.js
module.exports = async ({ config, mode }) => {
config.module.rules.push({
test: /\.stories\.tsx?$/,
loaders: [
{
loader: require.resolve("#storybook/source-loader"),
options: { parser: "typescript" }
}
],
enforce: "pre"
});
return config;
};
and I run the storybook using
nx run storybook:storybok
I think you're not using any image loader, you can try this loaded by adding this to your webpack config file.
const svgToMiniDataURI = require('mini-svg-data-uri');
const rootWebpackConfig = require("../../../.storybook/webpack.config");
// Export a function. Accept the base config as the only param.
module.exports = async ({ config, mode }) => {
config = await rootWebpackConfig({ config, mode });
config.resolve.extensions.push(".tsx");
config.resolve.extensions.push(".ts");
config.module.rules.push({
test: /\.(ts|tsx)$/,
loader: require.resolve("babel-loader"),
options: {
presets: [
"#babel/preset-env",
"#babel/preset-react",
"#babel/preset-typescript"
]
},
test: /\.(png|svg|jpg|gif)$/,
loader: 'url-loader',
options: {
limit: true,
generator: content => svgToMiniDataURI(content.toString()),
},
});
return config;
};

Obfuscate React classNames in existing codebase

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. :)

mobx-deep-action with Typescript async/await functions and #action decorator

I am struggeling to setup my project correctly to get rid of the mobx error when running in useStrict(true)
Error
mobx.module.js:2238 Uncaught (in promise) Error: [mobx] Invariant failed: Since strict-mode is enabled, changing observed observable values outside actions is not allowed. Please wrap the code in an `action` if this change is intended. Tried to modify: WikiStore#1.tags
at invariant (mobx.module.js:2238)
at fail (mobx.module.js:2233)
at checkIfStateModificationsAreAllowed (mobx.module.js:2794)
at ObservableValue.prepareNewValue (mobx.module.js:750)
at setPropertyValue (mobx.module.js:1605)
at WikiStore.set [as tags] (mobx.module.js:1575)
at WikiStore.<anonymous> (WikiStore.tsx:25)
at step (tslib.es6.js:91)
at Object.next (tslib.es6.js:72)
at fulfilled (tslib.es6.js:62)
My setup compiles but the error is still there, so there must be a mistake in my build chain.
I have used the information provided from the GitHub repo here, but its all a bit vague.
.babelrc
{
"passPerPreset": true,
"presets": [
{
"plugins": ["transform-regenerator"]
},
{
"plugins": ["mobx-deep-action"]
},
{
"plugins": ["babel-plugin-transform-decorators-legacy"]
},
"es2015", "react", "stage-0"
]
}
webpack.config.js
const path = require("path");
const ExtractTextPlugin = require("extract-text-webpack-plugin");
const webpack = require("webpack");
const CheckerPlugin = require('awesome-typescript-loader').CheckerPlugin;
const clientBundleOutputDir = "./wwwroot/dist";
module.exports = (env) => {
const clientConfig = {
stats: { modules: false },
entry: { "main-client": "./Client/index.tsx" },
resolve: { extensions: [".js", ".jsx", ".ts", ".tsx" ] },
output: {
filename: "[name].js",
path: path.join(__dirname, clientBundleOutputDir),
publicPath: "/dist/"
},
module: {
rules: [
{ test: /\.s?css$/, use: ["css-hot-loader"].concat(ExtractTextPlugin.extract({fallback: "style-loader", use: ["css-loader", "sass-loader"]})) },
{ test: /\.woff(2)?(\?v=[0-9]\.[0-9]\.[0-9])?$/, loader: "url-loader?limit=10000&mimetype=application/font-woff" },
{ test: /\.(ttf|eot|svg)(\?v=[0-9]\.[0-9]\.[0-9])?$/, loader: "file-loader" },
{ test: /\.(png|svg|jpg|gif|ico)$/, use: ["file-loader"] },
// { test: /\.tsx?$/, include: /Client/, use: "awesome-typescript-loader?silent=true&useBabel=true&useCache=true" }
{ test: /\.tsx?$/, include: /Client/, use: ["babel-loader", "ts-loader"] }
]
},
plugins: [
new CheckerPlugin(),
new ExtractTextPlugin("site.css"),
new webpack.SourceMapDevToolPlugin({
filename: "[file].map",
moduleFilenameTemplate: path.relative(clientBundleOutputDir, '[resourcePath]')
})
]
};
return [clientConfig];
};
I have tried the awesome typescript loader and also the ts-loader, babel-loader combo.
And here my store (short version)
import { autorun, observable, action } from "mobx";
import { Wiki } from "../models/Wiki";
import { Tag } from "../models/Tag";
import * as Api from "../api/api";
export class WikiStore {
#observable public tags: Tag[] = [];
constructor() {
this.getTags();
}
#action
public async getTags() {
this.tags = await Api.getTags();
}
}
export const wikiStore = new WikiStore();
As soon as i call wikiStore.getTags() from a React component i get the error. Everything just works fine without useStrict(true) (as expected)
Maybe someone has an idea.
I think you still need to split up the await and the assignment:
#action
public async getTags() {
const tags = await Api.getTags();
this.tags.replace(tags);
}
Please read here:
https://mobx.js.org/best/actions.html#async-await
As a result, #action only applies to the code block until the first await
So const tags = await Api.getTags(); must be wrapped again
#action
public async getTags() {
const tags = await Api.getTags();
runInAction(() => {
this.tags = tags;
})
}

Resources