婚恋网站排名,如何制作手机网页,高端的食品行业网站开发,青海互动网站建设本文为翻译 本文译者为 360 奇舞团前端开发工程师原文标题#xff1a;CSS in Micro Frontends 原文作者#xff1a;Florian Rappl 原文地址#xff1a;https://dev.to/florianrappl/css-in-micro-frontends-4jai 我被问得最多的问题之一是如何在微前端中处理 CSS。毕竟… 本文为翻译 本文译者为 360 奇舞团前端开发工程师原文标题CSS in Micro Frontends 原文作者Florian Rappl 原文地址https://dev.to/florianrappl/css-in-micro-frontends-4jai 我被问得最多的问题之一是如何在微前端中处理 CSS。毕竟样式始终是任何UI片段所需要的东西然而它也是全局共享的东西因此它是潜在的冲突来源。 在这篇文章中我想回顾一下现有的不同策略来驯服 CSS 并使其扩展以开发微前端。如果这里的任何内容对您来说听起来合理那么也可以考虑研究“微前端的艺术”。 本文的代码可以在github.com/piral-samples/css-in-mf找到。请务必检查示例实现。 CSS 的处理是否会影响每个微前端解决方案让我们检查可用的类型来验证这一点。 微前端的类型 过去我写了很多关于存在哪些类型的微前端、为什么存在以及何时应该使用什么类型的微前端架构的文章。采用 Web 方法意味着使用 iframe 来使用来自不同微前端的 UI 片段。在这种情况下没有任何限制因为无论如何每个片段都是完全隔离的。在任何其他情况下无论您的解决方案使用客户端还是服务器端组合或介于两者之间的东西您最终都会得到在浏览器中评估的样式。因此在所有其他情况下您都会关心 CSS。让我们看看这里有哪些选项。 无特殊处理 好吧第一个也许是最或根据观点最不明显的解决方案是不进行任何特殊处理。相反每个微前端都可以附带额外的样式表然后在渲染微前端的组件时附加这些样式表。 理想情况下每个组件仅在首次渲染时加载所需的样式但是由于这些样式中的任何一个都可能与现有样式冲突我们也可以假装在微前端的任何组件渲染时加载所有有问题的样式。这种方法的问题在于当给出诸如div或之类的通用选择器时div a我们还将重新设计其他元素的样式而不仅仅是原始微前端的片段。更糟糕的是类和属性也不是故障保护措施。类似的类.foobar也可以用在另一个微前端中。您将在引用的演示存储库中找到两个冲突的微前端的示例网址为solutions/default。 摆脱这种痛苦的一个好方法是进一步隔离组件————就像 Web 组件一样。 Shadow DOM 在自定义元素中我们可以打开一个shadow root来将元素附加到专用的迷你文档该迷你文档实际上与其父文档相互隔离。总的来说这听起来是一个好主意但与这里介绍的所有其他解决方案一样并没有强制要求。理想情况下微前端可以自由决定如何实现组件。因此实际的 Shadow DOM 集成必须由微前端完成。 使用 Shadow DOM 有一些缺点。最重要的是虽然 Shadow DOM 内部的样式保留在内部但全局样式也不会影响 Shadow DOM。乍一看这似乎是一个优势但是由于整篇文章的主要目标只是隔离微前端的样式因此您可能会错过诸如应用某些全局设计系统例如 Bootstrap之类的要求。link要使用 Shadow DOM 进行样式设置我们可以通过引用或标签将样式放入 Shadow DOM 中style。由于 Shadow DOM 是无样式的并且外部的样式不会传播到其中因此我们实际上需要它。除了编写一些内联样式之外我们还可以使用捆绑器将.css或者类似的东西.shadow.css视为原始文本。这样我们只会得到一些文本。 piral-cli-esbuild对于 esbuild我们可以配置如下的预制配置 module.exports function(options) {options.loader[.css] text;options.plugins.splice(0, 1);return options;
}; 这会删除初始 CSS 处理器 (SASS) 并为.css文件配置标准加载器。现在shadow DOM 中的某些样式的工作方式如下 import css from ./style.css;customElements.define(name, class extends HTMLElement {constructor() {super();this.attachShadow({ mode: open });}connectedCallback() {this.style.display contents;const style this.shadowRoot.appendChild(document.createElement(style));style.textContent css;}}); 上面的代码是一个有效的自定义元素从样式的角度来看它将是透明的display: contents即只有其内容会反映在渲染树中。它托管一个包含单个style元素的Shadow DOM。style 的内容设置为style.css文件的文本内容。 您将在引用的演示存储库中找到两个冲突的微前端的示例网址为solutions/shadow-dom。 域组件避免使用Shadow DOM 的另一个原因是并不是每个 UI 框架都能够处理Shadow DOM 中的元素。因此无论如何都必须寻找一种替代方案。其中一种方式是转而使用一些 CSS 约定。 使用命名约定 如果每个微前端都遵循全局CSS约定那么就可以在根上避免冲突。最简单的约定是在每个类前面加上微前端的名称。例如如果调用一个微前端shopping并调用另一个微前端checkout则两者都会将其active类分别重命名为shopping-active/ checkout-active。 这同样适用于其他可能存在冲突的名称。例如如果有一个名为 shopping 的微前端那么我们可以将主按钮的ID从 primary-button 改为 shopping-primary-button。如果因某种原因需要为一个元素添加样式我们应该使用后代选择器例如 .shopping img来为 img 标签添加样式。这样会应用于具有 shopping 类的元素内部的 img 元素。这种方法的问题在于 shopping 微前端可能还会使用其他微前端的元素。如果我们看到 div.shopping div.checkout img即使通过 checkout 微前端引入的组件承载/集成了 img它仍然会受到 shopping 微前端 CSS 的样式影响。这并不理想。 您将在引用的演示存储库中找到两个冲突的微前端的示例网址为https://github.com/piral-samples/css-in-mf/tree/main/solutions/default。 尽管命名约定在一定程度上解决了问题但它们仍然容易出错并且使用起来很麻烦。如果我们重命名微前端会怎样如果微前端在不同的应用程序中获得不同的名称怎么办如果我们在某些时候忘记应用命名约定怎么办这就是工具帮助我们的地方。 CSS Modules 自动添加前缀并避免命名冲突的最简单方法之一是使用 CSS 模块。根据您选择的打包工具这可能是开箱即用的功能或者通过更改一些配置实现。 // Import default export from CSSimport styles from ./style.modules.css;// Applydiv className{styles.active}Active/div 导入的模块是一个生成的模块将原始类名例如 active映射到生成的类名。生成的类名通常是CSS规则内容与原始类名混合后的哈希值。这样生成的类名应该尽可能唯一. 例如让我们考虑一个使用esbuild构建的微前端. 对于esbuild您需要一个插件(esbuild-css-modules-plugin)和相应的配置更改来包含 CSS 模块。 使用Piral我们只需要调整已有的配置piral-cli-esbuild。我们删除标准 CSS 处理使用 SASS并替换为插件 const cssModulesPlugin require(esbuild-css-modules-plugin);module.exports function(options) {options.plugins.splice(0, 1, cssModulesPlugin());return options;
}; 现在我们可以像上面展示的那样在我们的代码中使用 CSS 模块了。 您将在引用的演示存储库中找到两个冲突的微前端的示例网址为solutions/css-modules。 使用 CSS 模块会带来一些缺点。首先它引入了几个语法扩展来区分我们想要导入的样式因此需要进行预处理/哈希和应保持原样的样式即稍后无需导入即可使用的样式 另一种方式是将 CSS 直接引入到 JS 文件中。 CSS-in-JS CSS-in-JS 最近的名声很差但是我认为这是一个误解。我也更喜欢将其称为“CSS-in-Components”因为它为组件本身带来了样式。一些框架Astro、Svelte 等甚至允许通过其他方式直接执行此操作。经常被提及的缺点是性能问题这通常是由于在浏览器中编写 CSS 造成的。然而这并不总是必要的在最好的情况下CSS-in-JS 库实际上是构建时间驱动的即没有任何性能缺陷。然而当我们谈论 CSS-in-JS或 CSS-in-Components时我们需要考虑现有的各种选择。为简单起见我只包含三个Emotion、Styled Components和Vanilla Extract。让我们看看它们如何帮助我们在一个应用程序中将多个微前端整合在一起时避免冲突。 Emotion Emotion 是一个非常棒的库它为诸如React之类的框架提供了辅助功能但并不要求将这些框架设置为先决条件。Emotion可以很好地优化和预先计算并允许我们使用各种可用的 CSS 技术。 使用“pure”Emotion相对来说很简单首先安装包 npm i emotion/css 现在您可以在代码中使用它如下所示 import { css } from emotion/css;const tile cssbackground: blue;color: yellow;flex: 1;display: flex;justify-content: center;align-items: center;;// laterdiv className{tile}Hello from Blue!/div css 助手允许我们编写 CSS将其解析并放置在样式表中。返回值是生成的类名。 如果我们特别想使用 React我们还可以使用Emotion 中的 jsx 工厂引入一个名为 css 的新标准属性或 styled 助手 npm i emotion/react emotion/styled 现在感觉很像样式是 React 本身的一部分。例如styled助手允许我们定义新组件 const Output styled.outputborder: 1px dashed red;padding: 1rem;font-weight: bold;;// laterOutputI am groot (from red)/Output 相反css助手属性使我们能够缩短表示法 div css{background: red;color: white;flex: 1;display: flex;justify-content: center;align-items: center;}Hello from Red!/div 总而言之这生成的类名不会冲突并提供了避免样式混乱的强大性能。特别是 styled 助手深受流行的 styled-components 库的启发。 您将在引用的演示存储库中找到两个冲突的微前端的示例网址为:solutions/emotion。 Styled Components styled-components库可以说是最受欢迎的CSS-in-JS解决方案而且往往也是这类解决方案声誉不佳的原因。从历史上看它实际上是关于在浏览器中组合CSS但在过去几年中它们确实在这方面取得了巨大进展。现在您也可以对所使用的样式进行一些非常好的服务器端组合. 与emotion相比styled-components库对于React需要安装一些少量的包。唯一的缺点是类型定义是事后添加的因此您需要安装两个包以获得完整的TypeScript支持 npm i styled-components --save
npm i types/styled-components --save-dev 安装后该库就已经完全可用 import styled from styled-components;const Tile styled.divbackground: blue;color: yellow;flex: 1;display: flex;justify-content: center;align-items: center;
;// later
TileHello from Blue!/Tile 原理与 相同emotion。因此, 让我们探讨另一种尝试从一开始就实现零成本的选择, 而不是事后添加的.您将在引用的演示存储库中找到两个冲突的微前端的示例网址为: solutions/styled-components Vanilla Extract 我之前提到的利用类型接近组件并避免不必要的运行时成本的方法正是最新一代的CSS-in-JS库所涵盖的。其中最有潜力的库之一是vanilla-extract/css。它允许你在JavaScript中直接编写CSS并静态提取类名从而减小打包大小提高性能。这是一种有前途的选择可以以类型安全和高效的方式管理样式。使用该库有两种主要方式 与你的打包工具/框架集成直接使用 CLI 在这个例子中我们选择了第一种方式———— 通过与 esbuild 集成。为了使集成正常工作我们需要使用该vanilla-extract/esbuild-plugin包。现在我们将其集成到构建过程中。使用piral-cli-esbuild配置, 我们只需要将其添加到配置的插件中即可 const { vanillaExtractPlugin } require(vanilla-extract/esbuild-plugin);module.exports function (options) {options.plugins.push(vanillaExtractPlugin());return options;
}; 为了使 Vanilla Extract 正常工作我们需要编写.css.ts文件而不是普通文件.css或.sass文件。这样的文件可能如下所示 import { style } from vanilla-extract/css;
export const heading style({color: blue,
}); 这是有效的 TypeScript 代码。最终我们将获得一个类名的导出————就像我们从 CSS modules、Emotion 等中获得的那样。因此最终上述样式将会应用如下 import { heading } from ./Page.css.ts;// later
h2 className{heading}Blue Title (should be blue)/h2 这将在构建时完全处理不会有任何运行时成本。 您将在引用的演示存储库中找到两个冲突的微前端的示例网址为:solutions/vanilla-extract。 您可能会感兴趣的另一种方法是使用 CSS 实用程序库例如 Tailwind。 CSS 实用程序例如 Tailwind 这是一个独立的类别但考虑到Tailwind是该类别中的主导工具我只会介绍Tailwind。Tailwind的主导地位甚至到了一些人问“你是写CSS还是Tailwind”这样的地步。这与2010年左右jQuery在DOM操作领域的主导地位非常相似当时人们会问“这是JavaScript还是jQuery” 无论如何使用CSS实用库的优势在于样式是基于使用而生成的。这些样式不会冲突因为它们始终由实用库以相同的方式定义。因此每个微前端只需提供所需的实用库部分以按预期显示微前端。 如果使用 Tailwind 和 esbuild我们还需要安装以下软件包 npm i autoprefixer tailwindcss esbuild-style-plugin esbuild的配置比以前略微复杂一些。esbuild-style-plugin本质上是esbuild的一个PostCSS插件所以必须正确配置 const postCssPlugin require(esbuild-style-plugin);module.exports function (options) {const postCss postCssPlugin({postcss: {plugins: [require(tailwindcss), require(autoprefixer)],},});options.plugins.splice(0, 1, postCss);return options;
}; 在这里我们移除了默认的CSS处理插件SASS并用PostCSS插件替代它——同时使用autoprefixer和tailwindcss这两个PostCSS扩展。现在我们需要添加一个有效的tailwind.config.js文件 module.exports {content: [./src/**/*.tsx],theme: {extend: {},},plugins: [],
}; 这本质上是配置 Tailwind 的最低要求。它只是提到tsx应该扫描文件以了解 Tailwind 实用程序类的使用情况。然后找到的类将被放入 CSS 文件中。 因此CSS 文件还需要知道生成/使用的声明应包含在哪里。作为最低要求我们只有以下CSS内容 tailwind utilities; 还有其他tailwind指令。例如Tailwind自带一个重置和基础层。但是在微前端中我们通常不关心这些层。这属于应用程序 shell 或编排应用程序的关注范围而不是领域应用程序的关注点。 然后CSS将被来自Tailwind中已经指定的类所替代 div classNamebg-red-600 text-white flex flex-1 justify-center items-centerHello from Red!/div 您将在引用的演示存储库中找到两个冲突的微前端的示例网址为solutions/tailwind。 比较 到目前为止所提出的几种方法都是微前端的可行选择。总的来说这些解决方案也可以混合使用。一个微前端可以采用Shadow DOM方法而另一个微前端可以使用Emotion。第三个库可能会选择Vanilla Extract。最重要的是所选择的解决方案不会产生冲突并且没有巨大的运行时成本。虽然有些方法比其他方法更高效但它们都提供了所需的样式隔离性。性能影响在很大程度上取决于实现方式。例如对于CSS-in-JS如果解析和组合都在运行时完成可能会产生很大的性能影响。如果样式已经预解析只在运行时组合则可能性能影响较小。对于类似Vanilla Extract这样的解决方案几乎没有任何性能影响。 对于 Shadow DOM主要的性能影响可能是 Shadow DOM 内部元素的投影或移动本质上为零以及标签的重新评估style。然而这是相当低的甚至可能会产生一些性能优势给定的样式总是切中要害并且仅专用于要在Shadow DOM 中显示的某个组件。在示例中我们有以下捆绑包大小对于Emotion和Styled Components这些数字仅供参考因为运行时可能并且很可能应该被共享。此外给定的微前端示例确实很小所有UI片段的总大小为3KB。对于一个更大的微前端增长肯定不会像这里描述的那样成为问题。 Shadow DOM解决方案的大小增加可以解释为我们提供的简单实用脚本用于将现有的React渲染轻松包装到Shadow DOM中而不创建新的树结构。如果这样的实用脚本在中心共享那么其大小将更接近于其他更轻量级的解决方案。 结论 在微前端解决方案中处理CSS并不需要变得困难只需要从一开始就以有结构、有序的方式进行处理否则就会出现冲突和问题。通常情况下建议选择 CSS 模块、Tailwind 或可扩展的 CSS-in-JS 实现等解决方案. - END - 关于奇舞团 奇舞团是 360 集团最大的大前端团队代表集团参与 W3C 和 ECMA 会员TC39工作。奇舞团非常重视人才培养有工程师、讲师、翻译官、业务接口人、团队 Leader 等多种发展方向供员工选择并辅以提供相应的技术力、专业力、通用力、领导力等培训课程。奇舞团以开放和求贤的心态欢迎各种优秀人才关注和加入奇舞团。