使用 Rollup 和 scss 动态注入每个组件的样式标签
Posted
技术标签:
【中文标题】使用 Rollup 和 scss 动态注入每个组件的样式标签【英文标题】:Inject per-component style tags dynamically with Rollup and scss 【发布时间】:2019-11-14 10:21:22 【问题描述】:我正在构建一个 React 组件库,其源代码采用这种通用结构:
- src
- common.scss (contains things like re-usable css variables)
- components
- button
- index.js
- button.scss
- dialog
- index.js
- dialog.scss
我的组件负责导入自己的每个组件样式(使用 scss),例如,button/index.js
有这一行:
import "./button.scss";
到目前为止,在我的应用程序中,我一直在直接从源代码中使用我的库,如下所示:
// app.js
import "mylib/src/common.scss" // load global styles
import Button from 'mylib/src/components/button/index.js'
import Dialog from 'mylib/src/components/dialog/index.js'
// ...application code...
当我的应用程序使用 webpack 和 style-loader
时,每个组件的 css 在第一次使用组件时动态地附加为 head
中的 style
标签。这是一个很好的性能提升,因为每个组件的样式在真正需要之前不需要被浏览器解析。
不过,现在我想使用 Rollup 分发我的库,因此应用程序使用者会执行以下操作:
import Button, Dialog from 'mylib'
import "mylib/common.css" // load global styles
// ...application code...
当我使用rollup-plugin-scss
时,它只是将每个组件的样式捆绑在一起,而不是像以前那样动态添加它们。
是否有一种技术可以合并到我的汇总构建中,以便我的每个组件样式在使用时动态添加为head
标记中的style
标记?
【问题讨论】:
【参考方案1】:一种方法是将您的 SCSS 作为 CSS 样式表字符串加载到插件中的 output:false
选项(请参阅 Options section of the docs),然后在您的组件中使用 react-helmet
在运行时注入样式表:
import componentCss from './myComponent.scss'; // plain CSS from rollup plugin
import Helmet from 'react-helmet';
function MyComponent(props)
return (
<>
<ActualComponentStuff ...props />
<Helmet>
<style> componentCss </style>
</Helmet>
</>
);
这个基本想法应该可行,但我不会使用这个实现有两个原因:
-
渲染两个
MyComponent
实例会导致样式表被注入两次,从而导致大量不必要的 DOM 注入
要包装每个组件需要大量样板文件(即使我们将 Helmet
实例分解为一个很好的包装器)
因此,您最好使用自定义钩子,并传入一个uniqueId
,它允许您的钩子对样式表进行重复数据删除。像这样的:
// -------------- myComponent.js -------------------
import componentCss from "./myComponent.scss"; // plain CSS from rollup plugin
import useCss from "./useCss";
function MyComponent(props)
useCss(componentCss, "my-component");
return (
<ActualComponentStuff ...props />
);
// ------------------ useCss.js ------------------
import useEffect from "react";
const cssInstances = ;
function addCssToDocument(css)
const cssElement = document.createElement("style");
cssElement.setAttribute("type", "text/css");
//normally this would be dangerous, but it's OK for
// a style element because there's no execution!
cssElement.innerhtml = css;
document.head.appendChild(cssElement);
return cssElement;
function registerInstance(uniqueId, instanceSymbol, css)
if (cssInstances[uniqueId])
cssInstances[uniqueId].symbols.push(instanceSymbol);
else
const cssElement = addCssToDocument(css);
cssInstances[uniqueId] =
symbols: [instanceSymbol],
cssElement
;
function deregisterInstance(uniqueId, instanceSymbol)
const instances = cssInstances[uniqueId];
if (instances)
//removes this instance by symbol
instances.symbols = instances.symbols.filter(symbol => symbol !== instanceSymbol);
if (instances.symbols.length === 0)
document.head.removeChild(instances.cssElement);
instances.cssElement = undefined;
else
console.error(`useCss() failure - tried to deregister and instance of $uniqueId but none existed!`);
export default function useCss(css, uniqueId)
return useEffect(() =>
// each instance of our component gets a unique symbol
// to track its creation and removal
const instanceSymbol = Symbol();
registerInstance(uniqueId, instanceSymbol, css);
return () => deregisterInstance(uniqueId, instanceSymbol);
, [css, uniqueId]);
这应该会更好 - 钩子将有效地使用应用程序范围的全局来跟踪组件的实例,在第一次渲染时动态添加 CSS,并在最后一个组件死亡时删除它。您需要做的就是在每个组件中添加单个钩子作为额外的行(假设您仅使用函数 React 组件 - 如果您使用的是类,则需要包装它们,可能使用 HOC 或类似的)。
它应该可以正常工作,但它也有一些缺点:
我们正在有效地使用全局状态 (cssInstances
,如果我们试图防止来自 React 树的不同部分的冲突,这是不可避免的。我希望有一种方法可以通过存储状态来做到这一点在 DOM 本身中(考虑到我们的重复数据删除阶段是 DOM,这是有道理的),但我找不到。另一种方法是使用 React Context API 而不是模块级全局。这可以正常工作也更容易测试;如果这是你想要的,用useContext()
重写钩子应该不难,但是集成应用程序需要在根级别设置上下文提供程序,这为集成商创造了更多的工作、更多文档等。
-
动态添加/删除样式标签的整个方法意味着样式表顺序不仅是不确定的(在使用 Rollup 之类的捆绑器进行样式加载时已经如此),而且可以在运行时更改,所以如果您有样式表这种冲突,行为可能会在运行时改变。理想情况下,您的样式表的范围应该太小而不会发生冲突,但我发现在加载了多个 MUI 实例的 Material UI 应用程序中出现了这种问题 - 真的很难调试!
目前的主流方法似乎是 JSS - 使用类似 nano-renderer 的东西将 JS 对象转换为 CSS,然后注入它们。对于文本 CSS,我似乎找不到任何可以做到这一点的东西。
希望这是一个有用的答案。我已经测试了钩子本身,它工作正常,但我对 Rollup 并不完全有信心,所以我依赖这里的插件文档。不管怎样,祝项目好运!
【讨论】:
@o-t-w 我已经继续前进,没有时间尝试这个答案......如果应该接受这个答案,你能评论一下吗?以上是关于使用 Rollup 和 scss 动态注入每个组件的样式标签的主要内容,如果未能解决你的问题,请参考以下文章