Skip to content
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

Remove Babel class properties transformation #3537

Open
Tracked by #2964
nolanlawson opened this issue May 25, 2023 · 2 comments
Open
Tracked by #2964

Remove Babel class properties transformation #3537

nolanlawson opened this issue May 25, 2023 · 2 comments

Comments

@nolanlawson
Copy link
Contributor

Currently the LWC compiler converts

class Foo {
  bar = undefined
}

into

class Foo {
  constructor(...args) {
    super(...args)
    this.bar = undefined
  }
}

This breaks some scenarios since it's not exactly the same thing, even though the syntax is similar.

This would be an observable change.

[babelClassPropertiesPlugin, { loose: true }],

@nolanlawson
Copy link
Contributor Author

Example of a scenario that this breaks:

customElements.define('x-foo', class extends HTMLElement {
  ariaLabel = 'foo'
})

document.createElement('x-foo')

This works in vanilla JS, but it fails in LWC due to the Babel transform. The browser throws an error:

Uncaught DOMException: Failed to construct 'CustomElement': The result must not have attributes

This is because this.ariaLabel = 'foo' in the constructor ends up triggering the attribute aria-label to be set. (Note this applies with or without the LWC ARIA reflection polyfill, at least in browsers that support ARIA reflection like Chrome and Safari.)

@pmdartus
Copy link
Member

I experimented with removing the class field transformation a while back and one of the major issues I ran into was around reactive fields.

For context, standard class fields are properties defined on the class instance. However, Babel's class fields transformation in loose mode generates properties assignments. The key difference is that property definitions never invoke the reactivity getters/setters assigned to the class prototype. Which is problematic for reactivity.

class ReactiveExample {
    #foo;

    get foo() {
        console.log('getter');
        return this.#foo;
    }

    set foo(v) {
        console.log('setter', v);
        this.#foo = v;
    }
}

class Standard extends ReactiveExample {
    foo = 'standard';
}

class Loose extends ReactiveExample {
    constructor() {
        super();
        this.foo = 'loose';
    }
}

new Standard(); // Prints nothing!
new Loose();    // Prints "setter loose"

I don't think it's impossible to make it work, but it would force us to change how we handle reactivity today.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants