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

How to encapsulate Angular app's global CSS within exported web component?

目标

我有一个 Angular 11 站点,其中包含使用 Angular Material 的大型表单组件 -- 我们称它为 myExportableComponent。我想像往常一样在我的站点中使用 myExportableComponent,但 将其导出为通用 Web 组件(a.k.a。“自定义元素”,a.k.a. "Angular 元素").这样,它就可以在任何第 3 方网站上使用,无论他们的技术堆栈如何,而且它的工作方式与在我的网站上完全相同。

myExportableComponent 应该像我网站中的普通 Angular 组件一样工作 - 它应该可以通过 styles.scss 中的全局 CSS 和组件特定 CSS 在 my-exportable.component.scss.

中找到

当用作 Web 组件时,myExportableComponent 也应该像正常情况一样受到全局和组件特定 CSS 的影响,但是 CSS 中的 none 应该会泄漏出来并影响放置它的第 3 方页面的其余部分。这是我正在努力的部分。

设置

我在主项目的子项目中设置了 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>

我已经 myExportableComponent 通过进入 angular.json 并将其指向主应用程序的 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"
        ],

问题

全局 CSS 当 Web 组件在另一个站点上使用时未封装。例如,如果我在 styles.scss 中为我的应用添加全局 link 样式,它也会影响消费网站上的所有 link。 theme.scss (Material) 样式似乎没有泄漏,但我认为那是因为那些东西已经使用了自定义选择器。我担心如果消费站点碰巧也使用 Angular Material,它 泄漏。

想法 #1:封装设置

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

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

如果我将 class 添加到我的应用程序的 HTML 标记中,并让 Web 组件的使用者也将其添加到该组件周围,我就可以定位我所有的全局 CSS 规则只影响那些容器内的东西,比如:

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

这实际上确实可以从第 3 方页面封装我的全局样式,但它会以某种方式翻转样式,以便全局样式在不应该的情况下优先于特定于组件的样式。

想法 #3:在我的全局 CSS?

上使用 :host 或 :host-context 选择器

老实说,我很难掌握这些,我无法让它们在我的应用程序中执行任何操作。

想法 #4:构建时脚本?

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

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

我乐于接受建议!

这是我最后做的:

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

  2. 创建一个继承自myExportableComponent的新组件,但除此之外没有代码。它也没有自己的模板——它共享基本组件的模板。实际上,您甚至不需要从子项目的模块中导出该组件。这样主app默认只能用base one就好了

  3. 新子组件的styleUrls有两个文件。首先是特定于组件的样式表。它的设置与正常一样,但在它内部只有一个引用主站点全局样式表的导入,类似于 @import '../../../../../../src/app/styles.scss';。我想直接引用它,但一直收到奇怪的构建错误,所以将它导入“普通”组件特定样式表的这个技巧起作用了。第二个文件是对 base 组件的常规组件特定样式表的引用。

  4. 在为 Web 组件做 createCustomElement 时,使用子组件而不是父组件。

最重要的是,在构建通用 Web 组件时,主站点的全局 css 将包含在 myExportableComponent 的特定组件 css 的顶部,而不是全球范围内。然后全局样式被封装,并且仍然可以像正常一样被特定于组件的样式覆盖。

我想对包含 Material 主题的 theme.scss 做同样的事情,但似乎只有当它真正在全球范围内时才有效 (https://github.com/angular/components/issues/3386)。现在这对我来说不是一个交易破坏者。

优点:

  • 主站点项目没有特殊设置或中断。它和 myExportableComponent 可以在我们的主应用程序中以完全标准的方式使用。
  • 样式封装在导出的 Web 组件中的工作方式与在我们的主项目中的工作方式相同,并且不会泄漏到第 3 方站点。
  • 不需要特殊的构建脚本

缺点:

  • 这有点不正统,您需要确保其他开发人员没有在用作此构建解决方案管道的 dummy/inherited 文件中放置任何内容。
  • 据我所知,Material 主题 css 还不可能。