将全局样式传递给 Svelte "custom element" 组件

Pass global styles down to Svelte "custom element" components

我正在“尝试”使用 Sveltejs 作为基于 Django 构建的项目的前端 Web 框架(相关,因为它定义了我的应用程序的结构)。我正在尝试将 Svelte 组件包含到我为 Django 应用程序构建的各种模板中。现在,使用 customElement API,我能够直接在我的 HTML 模板中编译和使用我的 Svelte 组件作为自定义元素,这很棒,除了一个主要问题:我的全局样式不是不会传播到组件。正如我发现的那样,这是因为所有自定义元素实际上都被编译为 Web 组件,这意味着它们包装了它们的内部 HTML 以防止样式影响它们。这不是我想要的行为。然而,这似乎是唯一真正以与我的 HTML 模板协同工作的方式使用我的 Svelte 组件的方法。我该如何解决这个问题?

我正在使用 webpack 作为我的模块打包器。如果有帮助,我可以在我的 webpack.config.js 中评论要点 link,但正如您所想象的,我正在为多个 Django“应用程序”进行编译,因此我的配置有点混乱。作为参考,我已经获得了所有内容,包括使用 Sass 的本地样式和自定义元素,以按预期工作。这只是一个“如何做某事”的问题,尽管我花了几个小时谷歌搜索,但我一直无法找到明确的答案。

我知道您可以直接使用客户端 API 来访问组件,但这不仅繁琐且混乱(尤其是当我需要在不同的 HTML 模板上将组件组合在一起时),而且我似乎无法 webpack 将我的 Svelte 组件公开为 Javascript 类。这是我如何使用这种方法(不起作用):

<!-- in the head -->
<script src="bundle.js"></script>

<!-- in the body -->
<script>new bundle.App{target: ...}</script>

它告诉我 App 没有定义。这是我 webpack.config.js 中的个人 output 配置的样子:

output: {
    library: 'bundle',
    // I do not understand at all what these two lines do -- I just found 
    // them somewhere on the interwebs as a suggestion to solve this problem
    libraryTarget: 'umd', 
    umdNamedDefine: true,
    // path, filename, etc.
}

归结起来,我确实有三个相互交织的问题:

  1. 我可以使用 customElement API(我更喜欢它)并仍然应用全局样式吗?
  2. 如果我不能使用 customElement API,是否有更好的方法来解决这个问题,允许全局样式?
  3. 如果没有其他选择,我该如何正确使用客户端 API 和 webpack

TL;DR: There is no clean/perfect answer for this.

这一次,无法将全局样式注入 Shadow Dom。话虽如此,您可以尝试的东西很少。

首先,如果您不使用slots,那么您可以编写自己的自定义元素注册函数并使用它来注册元素。您必须为从 HTMLElement class 扩展的 Web 组件编写自己的适配器。在这种方法中,每个 Svelte 组件都是独立的应用程序,您只需从 Web 组件中对其进行初始化即可。这是您可以探索的最佳选择

此外,您可以使用 Constructable Stylesheets. It allows you to programmatically construct a stylesheet object and attach it to a Shadow DOM. Of course, this work only when you have flat components. When your web components are nested within one-another, each would have its Shadow DOM. You would have to create a common global style as a constructable stylesheet and attach to each component. Look here for the example:

const sheet = new CSSStyleSheet();

// Replace all styles synchronously for this style sheet
sheet.replaceSync('p { color: green; }');

class FancyComponent1 extends HTMLElement {

  constructor() {
    super();

    const shadowRoot = this.attachShadow({ mode: 'open' });

    // Attaching the style sheet to the Shadow DOM of this component
    shadowRoot.adoptedStyleSheets = [sheet];

    shadowRoot.innerHTML = `
      <div>
        <p>Hello World</p>
      </div>
    `;

  }
}

class FancyComponent2 extends HTMLElement {

  constructor() {
    super();

    const shadowRoot = this.attachShadow({ mode: 'open' });

    // Same style sheet can also be used by another web component
    shadowRoot.adoptedStyleSheets = [sheet];

    // You can even manipulate the style sheet with plain JS manipulations
    setTimeout(() => shadowRoot.adoptedStyleSheets = [], 2000);

    shadowRoot.innerHTML = `
      <div>
        <p>Hello World</p>
      </div>
    `;

  }
}

在上面的示例中,sheet 是一个通用样式表,用于两个单独的 Web 组件。但是同样,您将不得不编写自己的 Web 组件包装器来实现这一点。

在自定义元素中使用 @import 指令是否有任何性能(或其他)缺点?

这就是我将全局样式传递给 svelte 自定义元素的方式:

/assets/theme.css
---

:root,:host{
  --some-var:1rem;
}

然后组件内部:

CustomElement.svelte
---

<svelte:options tag="custom-element"/>

...

<style>

@import "/assets/theme.css";

:host{
  padding:var(--some-var)
}

</style>