通过 Webpack 将 CSS 样式注入到 shadow root
Posted
技术标签:
【中文标题】通过 Webpack 将 CSS 样式注入到 shadow root【英文标题】:Inject CSS styles to shadow root via Webpack 【发布时间】:2020-06-24 19:42:00 【问题描述】:我正在尝试修改使用 shadow root 创建的 Web 组件的样式。
我看到样式被添加到head
标签,但它对shadow root
没有影响,因为它是封装的。
我需要加载所有组件的样式并让它们出现在shadow root
的内部。
这是创建 Web 组件的一部分:
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 // 常规反应组件
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',
);
由于 MockComponent 内部可以有更多的组件,我依靠 Webpack 将所有样式注入到shadow root
。
我正在使用style-loader
注入样式,但效果不佳。
我做错了什么,或者这个解决方案有什么替代方案。
【问题讨论】:
【参考方案1】:原来你只需要'css-loader',所以你应该完全删除'style-loader'及其选项。 所以在 webpack.config.ts 中:
module:
rules: [
test:/\.css$/, use:'css-loader'
]
然后您想导入我们从 css-loader 获得的样式字符串,并在您附加到容器 before 的影子根的样式元素中使用它。 所以在 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);
......
'style-loader' 给你带来问题的原因是,它被用来在最终的 html 文件中自行查找将 css 样式标记注入的位置,但你已经知道通过 JS 将它放在你的 shadowDOM 中的位置,所以你只需要'css-loader'。
【讨论】:
如果您的应用程序的其他部分确实需要“样式加载器”,您可以在导入语句中指定加载器,并在其前面加上“!!”为了禁用所有配置的加载器。更多详情webpack.js.org/concepts/loaders【参考方案2】:有一种方法可以使用style-loader
这一切都归结为执行顺序。你的index.tsx
在style-loader::insert
之后被执行。但是影子根必须在之前存在。
最简单的方法是修改index.html
。
这是一个完整的例子:
./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;
构建并提供您的应用后,您应该会看到带有绿色背景的“这是我的应用”,所有内容都在您的影子根中。
【讨论】:
【参考方案3】:其他解决方案是直接插入样式:
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import styles from './tmp/mockComponent.css'; //--> import the styles
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(<>
<style>styles</style> /* add styles*/
<App />
</>, container);
【讨论】:
在应用您的解决方案时出现此错误 - “对象作为 React 子级无效(发现:带有键 的对象)。如果您打算渲染一组子级,请改用数组。 " @ParfectShot 那是因为您没有以字符串形式接收样式,请检查您的模块包配置...从 './tmp/mockComponent.css 导入样式应该返回一个字符串...跨度> 感谢您的澄清。我最终创建了一个元素(附加在阴影中)并将样式放入其中。这对我有用。【参考方案4】:我想为这个问题添加我的解决方案。它基于@josef-wittmann 的想法,但允许将样式添加到自定义元素的任何新实例,而不仅仅是一个根元素。
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
export const styleTags: HTMLLinkElement[] = [];
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;
【讨论】:
【参考方案5】:我必须创建一个可以放置在常规 dom 元素内或元素的 shadow dom 内的 Web 组件。我还使用了很多必须先转换为 CSS 的 sass 文件。
我尝试使用@florian-bachmann 解决方案,但不知何故,require.resolve("./src/util/style-loader.ts")
行导致了一个问题,因为 style-loader.ts 文件没有得到解决,所以我决定使用 window 对象来存储样式标签并将其附加到文档正文或在 shadow dom 内,具体取决于我的自定义元素的放置位置。
index.html
...
<body>
<script src="main.js"></script>
<div>
#shadow-root(open)
<my-custom-element></my-custom-element>
</div>
</body>
...
webpack.config.js
module.exports =
module:
rules: [
test: /\.tsx?$/,
use: 'ts-loader',
exclude: /node_modules/,
,
test: /\.scss$/,
use: [
loader: 'style-loader',
options:
injectType: 'singletonStyleTag',
insert: function addToWindowObject(element)
const _window = typeof window !== 'undefined' ? window : ;
if (!_window.myCustomElementStyles)
_window.myCustomElementStyles = [];
element.classList.add('my-custom-element-styles');
_window.myCustomElementStyles.push(element);
,
,
,
'css-loader',
loader: 'sass-loader',
options:
sassOptions:
outputStyle: 'compressed',
,
,
,
],
exclude: /node_modules/,
,
],
,
output:
filename: "main.js",
path: path.resolve(__dirname, "dist"),
,
plugins: [...],
...
;
my-custom-element.ts
import './styles.scss';
export class MyCustomElement extends HTMLElement
constructor()
super();
connectedCallback()
const styletags = window['myCustomElementStyles'] as HTMLStyleElement[];
const rootNode = this.getRootNode();
if (rootNode instanceof ShadowRoot)
rootNode.append(...styletags.map((tag) => tag.cloneNode(true)));
else if (rootNode instanceof Document)
!document.getElementById('my-custom-element-styles') && document.head.append(...styletags);
...
...
customElements.define("my-custom-element", MyCustomElement);
style.scss
div
background: lightgreen;
【讨论】:
以上是关于通过 Webpack 将 CSS 样式注入到 shadow root的主要内容,如果未能解决你的问题,请参考以下文章
html 通过jQuery进行css样式注入。基本上调用body / head附加一个样式标记,你自己的css将添加一个新的标记和你的样式