如何在导出的 Web 组件中封装 Angular 应用程序的全局 CSS?

4

目标

我有一个使用Angular Material的大型表单组件,名为myExportableComponent,版本为Angular 11。我想在我的网站中像平常一样使用myExportableComponent,同时也想将它导出为通用的Web组件(也叫“自定义元素”或“Angular Element”)。这样,无论第三方网站使用什么技术堆栈,都可以使用它,并且它可以像在我的网站上一样正常工作。

在我的网站中,myExportableComponent应该像正常的Angular组件一样工作-它应该可以通过styles.scss中的全局CSS和my-exportable.component.scss中的特定于组件的CSS进行样式设置。

当作为Web组件使用时,myExportableComponent 也应该像正常一样受到全局和特定于组件的CSS的影响,但是所有的CSS都不应该泄漏并影响放置它的第三方页面的其他部分。这就是我遇到的问题所在。

设置

我已经在主项目的子项目中设置了myExportableComponent。我可以分别构建它,并取得生成的js/css文件,成功地在另一个网站中使用Web组件。类似于这样:

<link rel="stylesheet" href="myExportableComponentStyles.css">
<script type="text/javascript" src="myExportableComponent.js"></script>

<div class="mat-app-background mat-typography">
    <my-exportable-component></my-exportable-component>
</div>

我已经在 angular.json 文件中,将 myExportableComponent 与主站点共享样式,指向了主应用程序的 theme.scss 文件(生成的 Angular Material 主题)以及 styles.scss (其他全局样式)。像这样...

angular.json:

"mainApp": {
    ...
        "styles": [
            "src/theme.scss",
            "src/styles.scss"
        ],
"myExportableComponentApp": {
    ...
        "styles": [
            "src/theme.scss",  //NOT "projects/myExportableComponentApp/src/theme.scss"
            "src/styles.scss"  //NOT "projects/myExportableComponentApp/src/styles.scss"
        ],

问题

当Web组件在另一个站点上使用时,全局CSS未被封装。例如,如果我在styles.scss中为我的应用添加一个全局链接样式,它也会影响消费站点上的所有链接。 theme.scss(Material)样式似乎不会泄漏出来,但我认为这是因为该内容已经使用了自定义选择器。我担心如果消费站点也使用Angular Material,则它泄漏出来。

想法#1:封装设置

我考虑尝试在myExportableComponent上使用encapsulation: ViewEncapsulation.ShadowDom,但是A)这会破坏一些Material样式和图标,并且B)这实际上并不是我想要的,因为这是组件级别的封装,而我的问题是关于全局CSS。实际上,我的特定于组件的CSS似乎已经使用默认设置进行了封装。

想法#2:将全局CSS目标定位到我的HTML

如果我向应用程序的HTML标记添加类,并要求Web组件的消费者在组件周围也添加该类,则可以将所有全局CSS规则定位到仅影响这些容器内的内容,例如:

.special-wrapper a {
   /* some style*/
}

尝试了一些方法后,发现将全局样式从第三方页面中封装确实可行,但是这种做法会导致全局样式在不应该的情况下占优势。

想法#3:在我的全局CSS中使用:host或:host-context选择器?

老实说,我很难理解它们,并且我无法在我的应用程序中让它们起作用。

想法#4:构建时脚本?

当构建myExportableComponent子项目时,如果我可以自动从theme.scssstyles.scss中剪切所有全局CSS并将其粘贴到my-exportable.component.scss的顶部,我认为那会奏效。因为“全局”样式将被封装到Web组件中,因为它们在组件特定文件中。

在运行ng build myExportableComponentApp --prod时是否可能做到这样的事情?我不知道从哪里开始。

欢迎提出建议!

2个回答

3

这是我的解决方案:

  1. 对于子项目,从 angular.json 中删除站点的全局 style.scss

  2. 创建一个继承自 myExportableComponent 的新组件,但不写任何代码。它也没有自己的模板——它共享基本组件的模板。实际上,你甚至不需要将此组件从子项目中的模块导出。这样主应用程序默认只能使用基本组件,这是好的。

  3. 新的子组件的 styleUrls 有两个文件。第一个是特定于组件的样式表。它像正常设置一样,但在里面只有一个引用主站点全局样式表的导入,类似于 @import '../../../../../../src/app/styles.scss';。我想直接引用它,但总是出现奇怪的编译错误,所以在“正常”特定于组件的样式表中导入它的技巧管用了。第二个文件是对于基本组件的普通特定于组件的样式表的一个引用。

  4. 在创建 Web 组件的时候,使用子组件而不是父组件进行 createCustomElement

底线是,在构建通用的 Web 组件时,主站点的全局 CSS 将会出现在 myExportableComponent 组件专属的 CSS 的顶部,而不是全局。全局样式随后会被封装,但像平常一样,仍然可以被组件特定的样式所覆盖。
我想对包含 Material 主题的 theme.scss 做同样的事情,但似乎只有当它真正处于全局范围内时才有效(https://github.com/angular/components/issues/3386)。目前来说,这对我来说并不是一个必须解决的问题。
优点:
  • 无需特殊设置或干扰主站点项目。在我们的主应用中,它和 myExportableComponent 可以完全按照标准方式处理。
  • 导出的 Web 组件中的样式封装方式与我们主项目中的相同,并且不会泄漏到第三方站点。
  • 不需要特殊的构建脚本。
缺点:
  • 这是有点不正规的,你需要确保其他开发人员不会在这个构建解决方案的虚拟/继承文件中放置任何内容,因为这些文件只是管道。
  • 就我所知,目前无法使用 Material 主题 css 实现。

我今天遇到了这个问题。这仍然是您在导出的 Web 组件中封装全局 CSS 的首选方法吗? - agritton
是的,这个项目还没有启动,但这就是我正在做的事情。我很惊讶这不是一个更常见的问题,有更简单的解决方案。 - Roobot

0

我正在使用一种不同的方法来处理scss/sass。

在我的(全局)styles.scss中,我将我的全局样式移动到webcomponents“标签”中。 它似乎也适用于嵌套导入,例如bootstrap。

https://sass-lang.com/documentation/at-rules/import#nesting

my-component-tag {
  @import '../../../libs/some.scss';
  
  body {
    margin: 0px;
    height: 100%;
    width: 100%;
    overflow-x: hidden;
  }
  
  .modal-content {
    padding: 20px;
  }
}

出于某些原因,“关键 CSS 内联”优化与构建未能正常工作,我遇到了“索引 HTML 生成失败”的问题。
因此,我在 angular.json 中将其禁用。
"architect": {
  "build": {
     ...
     "optimization": {
       "scripts": true,
       "styles": {
         "minify": true,
         "inlineCritical": false
       },
     "fonts": true
   }
  }
}

这个在导入Bootstrap时会有所改变

*, :after, :before {
    box-sizing: border-box;
}

my-component-tag *, my-component-tag :after, my-component-tag :before {
    box-sizing: border-box;
}

并且不再破坏嵌入的网站。

为了保持全局样式.scss“原样”,您可以创建一个webcomponent.scss

my-component-tag {
 @import "styles";
}

并将其用作构建配置:

"architect": {
  "build": {
     ...
     "configurations": {
       "webcomponent": {
         "styles": ["src/webcomponent.scss"],
       }
     }

   }
  }
}

你的回答可以通过提供更多支持信息来改进。请编辑以添加进一步的细节,例如引用或文档,以便他人可以确认你的答案是正确的。您可以在帮助中心中找到有关如何编写良好答案的更多信息。 - Community

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接