Как модули JS не позволяют пользовательским элементам, определенным внутри, выставлять свои API? - PullRequest
6 голосов
/ 11 июля 2019

Это не работает:

<!-- wtf.html -->
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8"/>
  <title></title>
  <script type="module" src="./wtf.js"></script>
</head>
<body>
<script>
  const myElement = document.createElement('my-element')
  document.body.appendChild(myElement)
  myElement.callMe()
</script>
</body>
</html>

// wtf.js
customElements.define('my-element', class extends HTMLElement {
  constructor() {
    super()
  }

  callMe() {
    window.alert('I am called!')
  }
})

Firefox бросает мне неприятное исключение в строке myElement.callMe(). Видимо, "myElement.callMe is not a function" .

Я запутался, почему это так? Насколько я понимаю, как только я набираю const myElement = document.createElement('my-element'), я получаю объект, тип которого не является общим HTMLElement, а объект моего класса, который я написал, который расширяет HTMLElement! И этот класс выставляет callMe.

Я подтвердил, что мое использование модулей, похоже, является виновником здесь. Этот код работает как положено:

<!-- wtf.html -->
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8"/>
  <title></title>
</head>
<body>
<script>
  customElements.define('my-element', class extends HTMLElement {
      constructor() {
        super()
    }

    callMe() {
        window.alert('I am called!')
    }
  })

  const myElement = document.createElement('my-element')
  document.body.appendChild(myElement)
  myElement.callMe()
</script>
</body>
</html>

Да, я знаю, что вещи, определенные в модуле, относятся к этому модулю. Но здесь это даже не кажется (для меня) проблемой вопроса. Например, если я сделаю внутри модуля что-то вроде этого:

function callMe() {/*blah blah */}

window.callMe = callMe

тогда я смогу использовать callMe вне модуля в любом случае, потому что модуль предоставил эту функцию другими способами, чем export (на этот раз путем назначения ее глобальной window объект).

То же самое, насколько я понимаю, должно происходить в моем случае использования. Несмотря на то, что я определяю callMe в классе, ограниченном модулем, этот метод класса должен быть доступен вне модуля, поскольку он является свойством объекта этого класса, который открывается путем вызова document.createElement('my-element'). Но, очевидно, этого не происходит.

Это действительно странно для меня. Похоже, что модуль принудительно устанавливает свою область видимости путем запутывания с типами, не относящимися к функциям, возвращающими (!!) - так что в этом случае это так же, как если бы модуль магически вызывал document.createElement для приведения объекта он возвращается в иерархию наследования (до HTMLElement)?!?! Это поразительно для меня.

Может кто-нибудь, пожалуйста, очистить мою путаницу?

(И если я определяю пользовательский элемент внутри модуля, как я могу предоставить его API за пределами этого модуля?)

Ответы [ 2 ]

4 голосов
/ 11 июля 2019

Проблема в том, что <script type="module"> неявно имеет атрибут defer , поэтому он не запускается сразу.

Даже если я определяю callMe в классена уровне модуля этот метод класса должен быть доступен вне модуля

Да, это так.Проблема только в том, что он определен асинхронно :-) Чтобы использовать вещи из модуля, вы должны явно import этот модуль объявить зависимость, которая гарантирует, что она вычисляется в правильном порядке.Это также сработало бы, если бы ваш глобальный скрипт был как-то defer красный.

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8"/>
  <title></title>
  <script type="module" src="./wtf.js"></script>
</head>
<body>
  <script type="module">
    import './wtf.js';
//  ^^^^^^^^^^^^^^^^^^
    const myElement = document.createElement('my-element')
    document.body.appendChild(myElement)
    myElement.callMe()
  </script>
</body>
</html>
0 голосов
/ 12 июля 2019

Вы также можете подождать, пока событие window.onload выполнит ваш встроенный скрипт:

document.onload = () => {
  const myElement = document.createElement('my-element')
  document.body.appendChild(myElement)
  myElement.callMe()
}

В качестве альтернативы, вы можете использовать classic <script> loading, чтобы убедиться, что ваш пользовательский элементзагружается синхронно:

<script src="./wtf.js"></script>
...