I am attempting to modify the styles of web component that created with shadow root.
I see that the styles are added to a head
tag but it has no effect on the shadow root
as it's encapsulated.
What i need is to load the styles of all components and make them appear inisde the shadow root
.
This is a part of creating the web component:
index.tsx
import React from 'react';
import ReactDOM from 'react-dom';
import { App } from './App';
import './tmp/mockComponent.css'; // This is the styling i wish to inject
let container: HTMLElement;
class AssetsWebComponent extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
const { shadowRoot } = this;
container = document.createElement('div');
shadowRoot.appendChild(container);
ReactDOM.render(<App />, container);
}
}
window.customElements.define('assets-component', AssetsWebComponent);
App.ts // Regular react component
import React from 'react';
import './App.css';
import { MockComponent } from './tmp/mockComponent'
export const App: React.FunctionComponent = (props) => {
return (
<MockComponent />
);
};
webpack.config.ts
// @ts-ignore
const path = require('path');
const common = require('./webpack.common.ts');
const merge = require('webpack-merge');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = merge(common, {
mode: 'development',
output: {
filename: '[name].bundle.js',
path: path.resolve(__dirname, 'dist'),
},
module: {
rules: [
{
test: /\.css$/,
use: [
{
loader: 'style-loader',
options: {
insert: function (element) {
const parent = document.querySelector('assets-component');
***This what i want, to inject inside the shadowRoot but it
never steps inside since the shadowRoot not created yet***
if (parent.shadowRoot) {
parent.shadowRoot.appendChild(element);
}
parent.appendChild(element);
},
},
},
'css-loader',
],
},
{
test: /\.scss$/,
use: [
'style-loader',
'css-loader',
'sass-loader',
],
},
],
},
plugins: [
new HtmlWebpackPlugin({
template: './src/template.html',
}),
],
devtool: 'inline-source-map',
});
Since the MockComponent can have more components inside, I rely on Webpack to inject all styles to the shadow root
.
I am using style-loader
to inject the styles but it's not working well.
What I am doing wrong or is there any alternative for this solution.
It turns out all you need is 'css-loader', so you should delete 'style-loader' completely along with its options. So in webpack.config.ts :
module: {
rules: [
{test:/\.css$/, use:'css-loader'}
]
}
Then you want to import a style string we got from css-loader and use it in a style element that you append to the shadow root before your container. So in index.tsx:
......
import style from './tmp/mockComponent.css';
......
constructor() {
super();
this.attachShadow({ mode: 'open' });
const { shadowRoot } = this;
container = document.createElement('div');
styleTag = document.createElement('style');
styleTag.innerHTML = style;
shadowRoot.appendChild(styleTag);
shadowRoot.appendChild(container);
ReactDOM.render(<App />, container);
}
......
The reason 'style-loader' is giving you problems is that it's made to find where to inject the css style tag on its own in the final html file, but you already know where to put it in your shadowDOM via JS so you only need 'css-loader'.
style-loader
for thatIt all comes down to order of execution. Your index.tsx
gets exectued after style-loader::insert
. But the shadow root has to exist before.
The easiest way to do this is to modify index.html
.
Here is a full example:
./src/index.html
...
<body>
<div id="root"></div>
<script>
<!-- This gets executed before any other script. -->
document.querySelector("#root").attachShadow({ mode: 'open' })
</script>
</body>
...
./webpack.config.js
var HtmlWebpackPlugin = require('html-webpack-plugin');
const cssRegex = /\.css$/i
const customStyleLoader = {
loader: 'style-loader',
options: {
insert: function (linkTag) {
const parent = document.querySelector('#root').shadowRoot
parent.appendChild(linkTag)
},
},
}
module.exports = {
module: {
rules: [
{
test: cssRegex,
use: [
customStyleLoader,
'css-loader'
],
},
],
},
plugins: [
new HtmlWebpackPlugin({
template: './src/index.html'
})
],
};
./src/index.js
import "./style.css";
const root = document.getElementById('root').shadowRoot
// Create a new app root inside the shadow DOM to avoid overwriting stuff (applies to React, too)
const appRoot = document.createElement("div")
appRoot.id = "app-root"
root.appendChild(appRoot)
appRoot.textContent = "This is my app!"
./src/style.css
#app-root {
background: lightgreen;
}
After building and serving your app, you should see "This is my app" with green background, everything inside your shadow root.
I'd like to add my solution to this problem. It's based on @josef-wittmann's idea, but allows to add the styles to any new instance of a custom element, instead of just one root element.
index.html
...
<body>
<script src="main.js"></script>
<my-custom-element></my-custom-element>
</body>
...
./webpack.config.js
module.exports = {
module: {
rules: [
{
test: /\.css$/i,
use: [
{
loader: "style-loader",
options: {
insert: require.resolve("./src/util/style-loader.ts"),
},
},
"css-loader",
],
},
{
test: /\.tsx?$/,
exclude: /node_modules/,
loader: "ts-loader",
options: {
configFile: "tsconfig.app.json",
},
},
],
},
plugins: [
new HtmlWebpackPlugin({
template: "./src/index.html",
}),
],
output: {
filename: "main.js",
path: path.resolve(__dirname, "dist"),
},
};
./src/util/style-loader.ts
const styleTags: HTMLLinkElement[] = [];
export const getStyleTags = () => styleTags.map(node => node.cloneNode(true));
export default function (linkTag) {
styleTags.push(linkTag);
}
./src/index.ts
import "./style.css";
import { styleTags } from "./util/style-loader";
customElements.define(
"my-custom-element",
class extends HTMLElement {
async connectedCallback() {
this.attachShadow({ mode: "open" });
const myElement = document.createElement("div");
myElement.innerHTML = "Hello";
this.shadowRoot?.append(...styleTags, myElement );
}
}
);
./src/style.css
div {
background: lightgreen;
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With