javascript - EmberJS 和 Web 组件 : extend an existing HTML tag within an initializer (is it possible? )

标签 javascript ember.js

编辑:此问题似乎仅出现在 .hbs 渲染中,因为当扩展 Web 组件插入顶级索引时此代码确实有效EmberJS 项目的 .html

编辑 2:但是,当我将其放入最小的 .hbs 示例中时,它确实起作用了。所以这显然是与 EmberJS 渲染的交互,也许还有与 Handlebars 的交互。

编辑 3:当我使用 Glimmer 作为独立渲染库并通过 constructor() 构建这些 Web 组件时,问题仍然存在。 Glimmer 组件的功能(我知道这很老套,但它适用于独立的自定义 Web 组件)。

我试图了解如何在 EmberJS 中使用 Web 组件。我知道我可以将 Web 组件重写为 EmberJS 组件,但这不是重点。我想知道如何将Web组件直接集成到EmberJS中。

有一个教程介绍了如何制作自定义 Web 组件并通过制作初始化程序将其放入 EmberJS 中。

https://tenbit.co/blog/a-simple-way-to-integrate-web-components-with-your-ember-app/

所以您可能会认为,为扩展的原生 HTML 组件做同样的事情是小菜一碟,对吗?事实证明并非如此。

这是一个不起作用的最小示例。在这个特定的例子中<p>hello world</p>显示但 <p>hello Mars</p>才不是。但是,在独立的 HTML 文件中(请参阅本文底部)具有相同的类和 define()调用,然后它就可以工作了。

我还制作了一个 EmberJS Twiddle,您可以在其中看到它:https://ember-twiddle.com/5b85957f52aba288bfe9e94ce42b811e

// app/initializers/custom-elements.js
class HelloWorld extends HTMLElement {
  constructor() {
    super();
    console.log("constructor() HelloWorld");
    let shadowRoot = this.attachShadow({ mode: "open" });
    shadowRoot.innerHTML = `<p>hello world</p>`;
  }
}

//similar to HelloWorld -- it's simply an extension
class TestingAParagraph extends HTMLParagraphElement {
  constructor() {
    super();
    console.log("constructor() TestingAParagraph");
    let shadowRoot = this.attachShadow({ mode: "open" });
    shadowRoot.innerHTML = `<p>hello Mars</p>`;
  }
}

export function initialize(application) {
  window.customElements.define("hello-world", HelloWorld);
  window.customElements.define("testing-a-paragraph", TestingAParagraph, {
    extends: "p"
  });
}

export default {
  initialize
};

这是 Handlebars 文件:

{{!-- my-template.hbs --}}
<p>Bladiebla</p>
<hello-world></hello-world>
<p>A test is coming</p>
<p is="testing-a-paragraph"></p>

注意:这个最小的示例确实可以作为独立的 HTML 文件使用。

<!DOCTYPE html>
    <html>
    <head>
      <script type="text/javascript">
        // app/initializers/custom-elements.js
        class HelloWorld extends HTMLElement {
          constructor() {
            super();
            console.log("constructor() HelloWorld");
            let shadowRoot = this.attachShadow({ mode: "open" });
            shadowRoot.innerHTML = `<p>hello world</p>`;
          }
        }
    
        //similar to HelloWorld -- it's simply an extension
        class TestingAParagraph extends HTMLParagraphElement {
          constructor() {
            super();
            console.log("constructor() TestingAParagraph");
            let shadowRoot = this.attachShadow({ mode: "open" });
            shadowRoot.innerHTML = `<p>hello Mars</p>`;
          }
        }
    
    
        window.customElements.define("hello-world", HelloWorld);
        window.customElements.define("testing-a-paragraph", TestingAParagraph, {
          extends: "p"
        });
      </script>
    </head>
    <body>
      <p>Bladiebla</p>
      <hello-world></hello-world>
      <p>A test is coming</p>
      <p is="testing-a-paragraph"></p>
    </body>
    </html>

最佳答案

在答案中,我的可扩展组件是 h1,而不是 p。因为它使事情更容易调试。

我发现该流程的工作原理如 jelhan 在评论中所述。

  1. 有一个 createElement (我 100% 肯定,因为我在调试器中看到我的 h1 自定义标记在 __openElement 上损坏了)。

  2. 然后它使用 setAttribute 一一解析所有属性(100% 肯定,因为调试器)。最终,“is”属性由setAttribute 设置。我知道当使用 setAttribute 时,Web 组件将不会被实例化。

  3. 它以 insertBefore 调用结束,在 flushElement 或其他地方(我在调试器中看到了这一点,没有费心去查明它) .

Stackoverflow 上有一个关于如何 a web component should be added in vanillaJS 的问题.

有了这些知识,我创建了一个 JS fiddle ,您可以在其中看到 Glimmer 现在的工作流程(工作流程方面),以及 Glimmer 可能如何做到这一点。

我还为 .hbs 文件中静态定义的元素创建了一个 hacky 解决方法(警告:我不创建库,我只是在其中闲逛)

您需要将 __setAttribute 替换为如下内容:

__setAttribute(name, value, namespace) {
  if(name === "is"){ //hack, hack, hackerdeehack
    //I need the actual document, this.dom doesn't cut it. I suppose it's kind of like a shadow DOM, similar to what ReactJS does.
    let webComponent = window.document.createElement(this.constructing.tagName, { is: value });
    //copy attributes that are already parsed
    [...this.constructing.attributes].forEach( attr => { this.dom.setAttribute(webComponent, attr.nodeName, attr.nodeValue, namespace) });
    this.constructing = webComponent;
  }
  this.dom.setAttribute(this.constructing, name, value, namespace);
}

最终,由 EmberJS/Glimmer 维护者提出某种前进的方向。之所以进行这个特殊的 hack,是因为我不想弄乱 VM,也不想弄乱 Glimmer 的解析行为。但我确实想看看我的想法是否正确,而且确实如此(两天前,我不知道 Glimmer 的存在,所以我需要彻底一点)。

为好奇的人提供一些额外信息

我看到的术语:

  • 操作码:VM 的东西,我有点忘记它在 VM 上下文中的含义,但我在编译器构建和计算机体系结构类(class)中看到过它。我总是将其翻译为:CPU 理解它们应该执行什么指令的数字表。
  • 系统调用:DOM 函数调用是系统调用,因为它们是渲染引擎的作用。

几个方便的断点(+ console.log()s),以验证我概述的工作流程:

//element-builder.js
__openElement(tag) {
  console.log('NewElementBuilder - __openElement', tag, [this.element], this.element.attributes, 'modules');
  debugger;
  return this.dom.createElement(tag, this.element);
}

__setAttribute(name, value, namespace) {
  debugger;
  this.dom.setAttribute(this.constructing, name, value, namespace); //this is how the "is" attribute is set
}

//note: it could also be that __flushElement is called... I didn't check this thoroughly
__appendText(text) { 
  let { dom, element, nextSibling } = this;
  debugger;
  console.log('__appendText', dom, element, nextSibling, text);
  let node = dom.createTextNode(text);
  dom.insertBefore(element, node, nextSibling);
  return node;
}

// just to be sure
__flushElement(parent, constructing) {
  debugger;
  this.dom.insertBefore(parent, constructing, this.nextSibling); 
}

关于javascript - EmberJS 和 Web 组件 : extend an existing HTML tag within an initializer (is it possible? ),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/58529308/

相关文章:

JavaScript:如何确定用户浏览器是否为 Chrome?

javascript - Rails 不显眼的 javascript - 无法加载资源

javascript - AJAX : Applying effect to CSS class

javascript - 使用 EmberJS 路由器的绑定(bind)?

javascript - 嵌套数组元素的 Ember 计算属性

java - 如何让 Java/Spring MVC 返回基于 jsonapi.org ID 的格式?

javascript - 在 ember 中使用 javascript 添加或更改 JSON 键的值不起作用

javascript - 试图让 window.URL 在 IE8 中工作。获取未定义的错误

javascript - 通过动态属性从对象数组创建对象

ruby-on-rails - 在设计中使用 rails secret 来加盐认证 key