Webpack 样式加载器的默认行为是注入样式标签作为 head 标签的最后一个子标签。但是,我想修改默认的插入方法,以便将 css 注入到影子根中。我查看了 docs 并测试了提供的代码,但它对我不起作用。
{
loader: "style-loader",
options: {
insert: function insertIntoTarget(element, options) {
var parent = options.target || document.head;
parent.appendChild(element);
},
},
}
这里的一些上下文是我正在开发一个 chrome 扩展,并且我的入口点之一具有注入到活动选项卡上的项目。然而,为了不干扰页面预先存在的样式,我在“内容”入口点动态创建了一个shadow-root,并尝试在shadow-root而不是head中加载CSS。
下面是我当前的 webpack 配置文件:
//webpack.config.js
const path = require('path');
const CopyPlugin = require('copy-webpack-plugin');
const HtmlPlugin = require('html-webpack-plugin');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const tailwindcss = require('tailwindcss')
const autoprefixer = require('autoprefixer')
module.exports = {
entry: {
popup: path.resolve('src/popup/index.tsx'),
background: path.resolve('src/background/background.ts'),
content: path.resolve('src/content/index.tsx'),
},
module: {
rules: [
{
use: 'ts-loader',
test: /\.tsx?$/,
exclude: /node_modules/,
},
{
test: /\.css$/i,
use: [
{
loader: 'style-loader',
options: {
insert: function insertIntoTarget(element, options) {
var parent = options.target || document.head;
parent.appendChild(element);
},
}
},
{
loader: 'css-loader',
options: {
importLoaders: 1,
},
},
{
loader: 'postcss-loader', // postcss loader needed for tailwindcss
options: {
postcssOptions: {
ident: 'postcss',
plugins: [tailwindcss, autoprefixer],
},
},
},
],
},
{
type: 'assets/resource',
test: /\.(png|jpg|jpeg|gif|woff|woff2|tff|eot|svg)$/,
},
]
},
plugins: [
new CleanWebpackPlugin({
cleanStaleWebpackAssets: false
}),
new CopyPlugin({
patterns: [{
from: path.resolve('src/static'),
to: path.resolve('dist')
}]
}),
...getHtmlPlugins([
'popup',
'content'
])
],
resolve: {
extensions: ['.tsx', '.js', '.ts']
},
output: {
filename: '[name].js',
path: path.join(__dirname, 'dist')
},
optimization: {
splitChunks: {
chunks(chunk) {
return chunk.name !== 'content';
},
}
}
}
function getHtmlPlugins(chunks) {
return chunks.map(chunk => new HtmlPlugin({
title: 'React Extension',
filename: `${chunk}.html`,
chunks: [chunk]
}))
}
下面是我的入口点,content/index.tsx:
import React from "react";
import { createRoot } from "react-dom/client";
import ActiveTabContextProvider from "../context/ActiveTabContextProvider";
import Content from "./content";
import '../assets/tailwind.css'
function init() {
const appContainer = document.createElement('div')
appContainer.id = "shadow-root-parent";
if (!appContainer) {
throw new Error("Can not find AppContainer");
}
const shadowRoot = appContainer.attachShadow({ mode: 'open' });
const root = createRoot(shadowRoot);
document.body.appendChild(appContainer);
root.render(
<React.StrictMode>
<ActiveTabContextProvider>
<Content />
</ActiveTabContextProvider>
</React.StrictMode>
);
}
init();
根据 style-loader 文档 style-loader 选项对象有一个插入字段,可以采用自定义函数来定位要注入的 html 元素对象:
{
loader: "style-loader",
options: {
insert: function insertIntoTarget(element, options) {
var parent = options.target || document.head;
parent.appendChild(element);
},
},
},
我对它的工作原理有点困惑。我什么时候将参数
document.getElementById('shadow-root-parent').shadowRoot
传递给函数?在这个样式加载器运行之前,shadow-root html 元素是否可用?
我发现最有效的解决方案是为插入字段编写一个快速函数:
function insertInShadow(element) {
if (window.location.pathname === '/') {
const appContainer = document.createElement('div');
appContainer.id = 'my-shadow-root';
const shadowRoot = appContainer.attachShadow({ mode: 'open' });
shadowRoot.appendChild(element);
document.body.appendChild(appContainer);
} else {
document.head.appendChild(element)
}
}
然后我可以在选项中添加此功能:
{
loader: 'style-loader',
options: {
insert: insertInShadow,
}
}
if 语句检查
window.location.pathname
可以知道样式加载器正在应用到哪个块。这样我们就可以有条件地将样式加载到 head 或 body 中。
插入函数中的
document.getElementByID
从未起作用的原因是样式加载器在index.tsx中的代码创建shadowRoot之前运行。所以我们可以更改这里的代码来查找由 style-loader 的 insert 创建的元素:
function init() {
const appContainer = document.getElementById('active-recall-shadow-root').shadowRoot;
if (!appContainer) {
throw new Error("Can not find AppContainer");
}
const root = createRoot(appContainer)
root.render(
<React.StrictMode>
<ActiveTabContextProvider>
<Content />
</ActiveTabContextProvider>
</React.StrictMode>
);
}