compiling custom elements

1,342 views
Skip to first unread message

Nate

unread,
Feb 19, 2018, 2:05:18 PM2/19/18
to Closure Compiler Discuss

Hi!


I'm trying to create custom elements in various ways.


I can compile an element using classic or goog.defineClass.


Using Reflect.construct is required (probably can be done with calling new/inherits):

goog.provide('com.example.legacy.CustomElement');

/* @extends {HTMLElement} */
com.example.legacy.CustomElement = goog.defineClass(HTMLElement, {
  constructor: function(instanceProperty){
    var instance = Reflect.construct(HTMLElement, [], com.example.legacy.CustomElement);
    instance.instanceProperty = instanceProperty;
    return instance;
  },
  connectedCallback: function(){
    console.log('connected');

    if(!this.instanceProperty){
        // load properties from attributes
        this.instanceProperty = this.getAttribute("instanceProperty");
    }    

    var span = document.createElement("span");
    span.innerHTML = this.instanceProperty;
    this.appendChild(span);
  }
});


The ES6 version fails:

/**
    @customElement
*/
class Test extends HTMLElement {
    constructor(){
        super()
    }

    connectedCallback(){
        alert('!')
    }
}
window.customElements && window.customElements.define('testy-test', Test)


Getting compiled into:

var module$customcomponent_es6 = {};
module$customcomponent_es6.CustomComponent = CustomComponent$$module$customcomponent_es6;
var Test$$module$test_es6 = function() {
  return HTMLElement.call(this) || this;
};
$jscomp.inherits(Test$$module$test_es6, HTMLElement);
Test$$module$test_es6.prototype.connectedCallback = function() {
  alert("!");
};
window.customElements && window.customElements.define("testy-tester", Test$$module$test_es6);

When the element is constructed (in html or new Test), chrome will raise Uncaught TypeError: Failed to construct 'HTMLElement': Please use the 'new' operator, this DOM object constructor cannot be called as a function., at the the line: return HTMLElement.call(this) || this


Taking a look at the compiler, I'm assuming the code Es6ConvertSuperConstructorCalls.createNewSuperCall is responsible for this transformation. See: https://github.com/google/closure-compiler/blob/master/src/com/google/javascript/jscomp/Es6ConvertSuperConstructorCalls.java#L266


Special casing HTMLElement in Es6ConvertSuperConstructorCalls seems to be one option to get this working.


I also noticed a customElement annotation. Seems to be only docs related. Having to remember an extra annotation might be asking too much, though, it could be used instead of potentially having to traverse the class parents looking for an HTMLElement... Wondering at this point how to get the correct es6 -> es5 transformation for custom elements, and if anyone is working on it.


Thanks!


(NOTE: deleted cross post in clsoure-library-discuss)

Chad Killingsworth

unread,
Feb 19, 2018, 4:17:59 PM2/19/18
to Closure Compiler Discuss
Custom Elements require es6 class syntax. They cannot be transpiled to ES5 without additional helpers.

The custom-elements-es5-adapter.js works around this limitation. It should be included in browsers which have native Custom Elements 1.0 support and should not be included in other browsers.

To see the recommended load order, see the demo in the polymer-webpack-loader project.

Chad Killingsworth

Nate

unread,
Feb 19, 2018, 5:55:28 PM2/19/18
to Closure Compiler Discuss
I've gotten them to work with entirely vanilla es5 syntax in browsers; various cases seem manageable. I currently have a functioning custom elements and es5 hybrid (with tricks and an old version of the compiler); compiled to advanced. The advantage being i can use some custom elements where i see fit, and the rest of the output is still compatible. Events seem ok, and I can start shaving some goog.ui.Component.

I do not intend to polyfill this functionality at this moment.

I'm using --language_in=ECMASCRIPT6_STRICT and --language_out=ECMASCRIPT5.

I saw that shim earlier, but I can't make sense of it's purpose. It's called "custom-elements-es5-adapter.js" but it has es6 inside of it, which is unparseable in es5.

Chad Killingsworth

unread,
Feb 19, 2018, 6:02:20 PM2/19/18
to Closure Compiler Discuss
It's purpose is to shim support for browsers with native Custom Elements 1.0 support, but where the custom element code itself has been transpiled to ES5. Like I mentioned, extending a custom element requires ES6 class style code. There are many native types that can only be extended with an ES6 class (Promises, Arrays, etc).

These questions are best answered by the Polymer team. Yes you are using vanilla js components, but the polyfills and shims are not specific to Polymer.

If you do not intend to polyfill the code, then you should use a language out level of ECMASCRIPT6. Any browser which supports Custom Elements 1.0 also supports ES6 style code.

Chad Killingsworth

Nate

unread,
Feb 19, 2018, 6:29:33 PM2/19/18
to Closure Compiler Discuss
I'm not following the limitation here. From a quick one, arrays can be extended: https://gist.github.com/Gozala/666251 as well as Promises https://gist.github.com/domenic/8ed6048b187ee8f2ec75 ... Though, that exercise might be like extending bool... Composition will usually work better.

I've never heard of a distinction of es5 and es6 classes from the browsers point of view. From what I recall they are native c++ classes/objects (or some jit-ted version of them) -- representing a prototype-based model. As understand it, classes defined in the browser with es6 syntax get converted to objects in the machine with the same proto chains as es5. Getters and setters, as you may know, are inconsistent throughout implementations, but I'm fairly certain we've had those for a long time too.

Maybe instance of could break; more tests and investigation into the prototype chain probably required. This fails progressive enhancement -- we'll need to ship 2 different code bases/outputs depending on user agent; completely unnecessary. 

Chad Killingsworth

unread,
Feb 19, 2018, 7:08:28 PM2/19/18
to Closure Compiler Discuss
There are some fairly major distinctions between ES5 functions and ES6 classes. Classes are more than syntactic sugar - and calls to super cannot be exactly represented in ES5 style code. The custom element shim source will help provide some guidance for you.

This limitation exists in all transpilers.

Chad Killlingsworth

Mario Foerster

unread,
Jun 15, 2019, 2:26:31 PM6/15/19
to Closure Compiler Discuss
Why a call to super cannot be exactly represented in ES5 style code? I saw the es6 class extensions of the language as a way to include some folks which are comming from the object oriented world. All the stuff which could normally done by a class, could be done in function style with closure before! The restriction of constructing a HTMLElement only with the new operator feels like a bad joke. It currently prevents e.g. from encapsulating/stuff which should not be used from outside. Where exactly the call to super cannot represent ES5 style code / ES6 functional style code? I try to understand, maybe you can tell me.

Mario Förster

Bradford Smith

unread,
Jun 18, 2019, 7:51:13 PM6/18/19
to Closure Compiler Discuss
I think the discussion and link to further info in this other thread may help explain things.

Reply all
Reply to author
Forward
0 new messages