New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Extending Builtins (HTMLElement) in @babel/core@7.0.0-beta.40 is not working #7506

Closed
jvoccia opened this Issue Mar 6, 2018 · 9 comments

Comments

Projects
None yet
8 participants
@jvoccia

jvoccia commented Mar 6, 2018

Choose one: is this a bug report or feature request?
Bug Report

Input Code

https://babeljs.io/repl/build/6204/#?babili=false&browsers=&build=&builtIns=false&code_lz=MYGwhgzhAEAqCmEAuB1AlgEwObyQYXCmngA8l4A7DGACVgFkAZAURHgFtKloBvAKD4BfPsACuyAPbtWHLhAB0GeADM0FeAAoAROWQBaAO6YcSLQBo4iVMdwFIEAJQBuPm24HoAXmgYJYzhRI8sAATvBg5DIBSNq6SIY2ps58QA&debug=false&forceAllTransforms=false&shippedProposals=false&circleciRepo=&evaluate=true&fileSize=false&lineWrap=true&presets=es2015%2Cenv&prettier=false&targets=&version=7.0.0-beta.35%2Bpr.7020&envVersion=7.0.0-beta.35%2Bpr.7020

class TestWidgetClass extends HTMLElement {

}
customElements.define("test-widget", TestWidgetClass);
let w = document.createElement("test-widget");

Babel/Babylon Configuration (.babelrc, package.json, cli command)

//Config 
{
                    sourceMap: true,
                    "presets": [["@babel/preset-env", {
                        "targets": {
                            "browsers": ["ie >= 11", "chrome >= 58", "safari >= 9", "firefox >= 47", "edge >= 38"]
                        }
                    }]]
           }

Expected Behavior

I would expect this Custom Element to be created without an error.

Current Behavior

Instead I see an error in the console saying:
The result of constructing a custom element must be a HTMLElement (Safari)
OR
Failed to construct 'CustomElement': The result must implement HTMLElement interface (Chrome)

Possible Solution

It looks like _wrapNativeSuper has a bug. It was adopted from code in another project, and it appears that code expected that the class it was wrapping had already been converted to ES5 by Babel. Ultimately what looks like is happening is Reflect.construct is never called.

// Two possible suggested fixes (not sure which is better):
function _wrapNativeSuper(Class) {
    if (typeof Class !== "function") {
        throw new TypeError("Super expression must either be null or a function");
    }
    if (typeof _cache !== "undefined") {
        if (_cache.has(Class))
            return _cache.get(Class);
        _cache.set(Class, Wrapper);
    }
    function Wrapper() {
      return _construct(Class, arguments, _gPO(this).constructor);
    }
    Wrapper.prototype = Object.create(Class.prototype, {
        constructor: {
            value: Wrapper,
            enumerable: false,
            writeable: true,
            configurable: true
        }
    });
    return _sPO(Wrapper, Class);
}

// OR 

function _wrapNativeSuper(Class) {
    if (typeof Class !== "function") {
        throw new TypeError("Super expression must either be null or a function");
    }
    if (typeof _cache !== "undefined") {
        if (_cache.has(Class))
            return _cache.get(Class);
        var Wrapper =  _sPO(function Super() {
          return _construct(Class, arguments, _gPO(this).constructor);
         }, Class);
        _cache.set(Class, Wrapper);
        return Wrapper;
    }
}

Context

This prevents using Custom Elements in Babel 7.

Your Environment

@babel/core": "^7.0.0-beta.40"
@babel/preset-env": "^7.0.0-beta.40"

@babel-bot

This comment has been minimized.

Collaborator

babel-bot commented Mar 6, 2018

Hey @jvoccia! We really appreciate you taking the time to report an issue. The collaborators
on this project attempt to help as many people as possible, but we're a limited number of volunteers,
so it's possible this won't be addressed swiftly.

If you need any help, or just have general Babel or JavaScript questions, we have a vibrant Slack
community that typically always has someone willing to help. You can sign-up here
for an invite.

@jvoccia

This comment has been minimized.

jvoccia commented Mar 6, 2018

@kidneyleung - That looks to be a separate problem.

Someone on slack pointed out that second option does not setup the prototype of Wrapper correctly, so I think this is the one that is correct.

function _wrapNativeSuper(Class) {
    if (typeof Class !== "function") {
        throw new TypeError("Super expression must either be null or a function");
    }
    if (typeof _cache !== "undefined") {
        if (_cache.has(Class))
            return _cache.get(Class);
        _cache.set(Class, Wrapper);
    }
    function Wrapper() {
      return _construct(Class, arguments, _gPO(this).constructor);
    }
    Wrapper.prototype = Object.create(Class.prototype, {
        constructor: {
            value: Wrapper,
            enumerable: false,
            writeable: true,
            configurable: true
        }
    });
    return _sPO(Wrapper, Class);
}
@jvoccia

This comment has been minimized.

jvoccia commented Mar 6, 2018

@kidneyleung - That issue with Reflect looks to be in _construct - since calling a function with an undefined variable results in an error - would have to do something like typeof Reflect === "object" instead of _typeof(Reflect) - which will fail with that error if Reflect does not exist at all.

@jjhesk

This comment has been minimized.

jjhesk commented Mar 7, 2018

same issue here

@xtuc

This comment has been minimized.

Member

xtuc commented Mar 8, 2018

Related to #7020

cc @WebReflection @nicolo-ribaudo

@kidneyleung you seems to have another bug, could you please open a new issue?

@trusktr

This comment has been minimized.

trusktr commented Apr 28, 2018

Here's a simple reproduction:

https://github.com/trusktr/babel/tree/issue-7020

(originally posted at #7020 (comment))

It includes the built output in build/test.js. Open test.html in your browser to see the error.

You can also try npm i && ./node_modules/.bin/babel test.js -o build/test.js to build it.

@trusktr

This comment has been minimized.

trusktr commented Apr 28, 2018

For anyone needing a quick solution, you can use @Mr0grog's newless, then you can call the constructor simply with .apply or .call.

Example:

import builtin from 'newless'

class MyEl extends builtin( HTMLElement ) {
  connectedCallback() {
    console.log('it works!')
  }
}

customElements.define('my-el', MyEl)

document.body.appendChild( document.createElement('my-el') )

You can use this to write Custom Elements the ES5-way too:

import native from 'newless'

const HTMLElement = native( window.HTMLElement )

function MyEl () {
  const self = HTMLElement.call(this)

  // ... use `self` here instead of `this`

  return self
}

MyEl.prototype = {
  __proto__: HTMLElement.prototype,
  constructor: MyEl,

  connectedCallback() {
    console.log(this) // <my-el></my-el>
  },
}

customElements.define('my-el', MyEl)

document.body.appendChild( document.createElement('my-el') )

Newless effectively does what plugin-transform-classes should do, and what @WebReflection's babel-plugin-transform-builtin-classes does.

Using newless means taking a manual step in your hand-written code, but it isn't much overhead to write.

I have my own version of newless over in lowclass with minor changes mostly around making the code more readable.

@nqthqn

This comment has been minimized.

nqthqn commented May 1, 2018

@trusktr I get

TypeError: Failed to construct 'HTMLElement': Please use the 'new' operator, this DOM object constructor cannot be called as a function.

My custom element:

import builtin from 'newless';

class Pop extends builtin(HTMLElement) {
  constructor() {
    super();
    // initialize
    this._guts = "42";
    this._laughter = this.laughter.bind(this);
  }

  // intercept changes to _properties_ on an element in JS
  get guts() {
    return this._guts;
  }
  set guts(val) {
    if (val == this._guts) return;
    console.log(val);
    this._guts = val;
  }

  laughter(val) {
    this._guts = val;
    this.dispatchEvent(new CustomEvent('laugh'));
  }

  attributeChangedCallback(name, previous, next) {
    // intercept changes to _attributes_ on a tag in the HTML
    console.log("Attribute changed");
  }

  connectedCallback() {
    // called when the element has been fully inserted into the DOM
    console.log("Fully inserted into DOM");
  }

  disconnectedCallback() {
    // called when the element has been fully removed from the DOM
    console.log("Removed from DOM");
  }
};

customElements.define('pop-menu', Pop);

FND added a commit to faucet-pipeline/faucet-pipeline-js that referenced this issue May 31, 2018

v1.0.0 RC6
updated Babel dependencies _should_ fix Custom Elements when transpiling
to ES5: babel/babel#7506

@lock lock bot added the outdated label Aug 16, 2018

@lock lock bot locked as resolved and limited conversation to collaborators Aug 16, 2018

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.