如何将 Web 组件分离到单个文件并加载它们?

jsl*_*oop 14 html javascript templates separation-of-concerns web-component

我有一个 web 组件x-counter,它在一个文件中。

const template = document.createElement('template');
template.innerHTML = `
  <style>
    button, p {
      display: inline-block;
    }
  </style>
  <button aria-label="decrement">-</button>
    <p>0</p>
  <button aria-label="increment">+</button>
`;

class XCounter extends HTMLElement {
  set value(value) {
    this._value = value;
    this.valueElement.innerText = this._value;
  }

  get value() {
    return this._value;
  }

  constructor() {
    super();
    this._value = 0;

    this.root = this.attachShadow({ mode: 'open' });
    this.root.appendChild(template.content.cloneNode(true));

    this.valueElement = this.root.querySelector('p');
    this.incrementButton = this.root.querySelectorAll('button')[1];
    this.decrementButton = this.root.querySelectorAll('button')[0];

    this.incrementButton
      .addEventListener('click', (e) => this.value++);

    this.decrementButton
      .addEventListener('click', (e) => this.value--);
  }
}

customElements.define('x-counter', XCounter);
Run Code Online (Sandbox Code Playgroud)

这里模板被定义为使用 JavaScript 并且 html 内容被添加为内联字符串。有没有办法将模板与x-counter.html文件、cssx-counter.css和相应的 JavaScript 代码分开xcounter.js并加载到 index.html 中?

我查找的每个示例都混合了 Web 组件。我想要关注点分离,但我不确定如何使用组件来做到这一点。你能提供一个示例代码吗?谢谢。

Sup*_*arp 24

在主文件中,用于<script>加载 Javascript 文件x-counter.js

在 Javascript 文件中,用于fetch()加载 HTML 代码x-counter.html

在 HTML 文件中,用于<link rel="stylesheet">加载 CSS 文件x-counter.css

CSS文件: x-counter.css

button, p {
    display: inline-block;
    color: dodgerblue;
}
Run Code Online (Sandbox Code Playgroud)

HTML 文件: x-counter.html

<link rel="stylesheet" href="x-counter.css">
<button aria-label="decrement">-</button>
    <p>0</p>
<button aria-label="increment">+</button>
Run Code Online (Sandbox Code Playgroud)

Javascript 文件: x-counter.js

<link rel="stylesheet" href="x-counter.css">
<button aria-label="decrement">-</button>
    <p>0</p>
<button aria-label="increment">+</button>
Run Code Online (Sandbox Code Playgroud)

主文件: index.html

<html>
<head>
    <!-- ... -->
    <script src="x-counter.js"></script>
</head>
<body>
    <x-counter></x-counter>
</body>
</html>
Run Code Online (Sandbox Code Playgroud)


mor*_*ney 6

使用顶级等待的通用模式,没有副作用:

my-component/
  element.js
  template.html
  styles.css
Run Code Online (Sandbox Code Playgroud)

template.html(确保链接到 styles.css)

my-component/
  element.js
  template.html
  styles.css
Run Code Online (Sandbox Code Playgroud)

styles.css(常规 CSS 文件)

<template>
  <link rel="stylesheet" href="./styles.css" />

  <!-- other HTML/Slots/Etc. -->
  <slot></slot>
</template>
Run Code Online (Sandbox Code Playgroud)

element.js(在导出中使用顶级等待)

:host {
  border: 1px solid red;
}
Run Code Online (Sandbox Code Playgroud)

index.html(加载和定义元素)

const setup = async () => {
  const parser = new DOMParser()
  const resp = await fetch('./template.html')
  const html = await resp.text()
  const template = parser.parseFromString(html, 'text/html').querySelector('template')

  return class MyComponent extends HTMLElement {
    constructor() {
      super()

      this.attachShadow({ mode: 'open'}).appendChild(template.content.cloneNode(true))
    }

    // Rest of element implementation...
  }
}

export default await setup()
Run Code Online (Sandbox Code Playgroud)

您可以而且应该明确副作用(例如在全局范围内注册自定义元素)。除了创建一些init函数来调用元素之外,您还可以提供不同的导入路径,例如:

Defined.js(Element.js 的同级)

<!doctype html>
<html>
  <head>
    <title>Custom Element Separate Files</title>
    <script type="module">
      import MyComponent from './element.js'

      if (!customElements.get('my-component')) {
        customElements.define('my-component', MyComponent)
      }
    </script>
  </head>
  <body>
    <my-component>hello world</my-component>
  </body>
</html>
Run Code Online (Sandbox Code Playgroud)

index.html(通过导入路径明确显示副作用)

import MyComponent from './element.js'

const define = async () => {
  let ctor = null

  customElements.define('my-component', MyComponent)
  ctor = await customElements.whenDefined('my-component')

  return ctor
}

export default await define()
Run Code Online (Sandbox Code Playgroud)

在定义自定义元素时,这种方法还可以通过在内部包含类似的内容来支持任意名称define

<!doctype html>
<html>
  <head>
    <title>Custom Element Separate Files</title>
    <script type="module" src="./defined.js"></script>
  </head>
  <body>
    <my-component>hello world</my-component>
  </body>
</html>
Run Code Online (Sandbox Code Playgroud)

然后name在导入说明符中传递查询参数:

new URL(import.meta.url).searchParams.get('name')
Run Code Online (Sandbox Code Playgroud)

下面是一个使用tts-element 的示例片段,它结合了所有三种方法(请参阅开发控制台中的网络选项卡):

<script type="module" src="./defined.js?name=custom-name"></script>
<custom-name>hello</custom-name>
Run Code Online (Sandbox Code Playgroud)