lwc icon indicating copy to clipboard operation
lwc copied to clipboard

Component compiles incorrectly for `export { ... as default }` ESM syntax

Open nolanlawson opened this issue 1 year ago • 9 comments

Description

Consider this component:

class Component extends LightningElement {}
export { Component as default }

Per ES module syntax, it is equivalent to this component:

export default class Component extends LightningElement {}

However, the second one correctly renders, whereas the first does not. (Repro: 08c93cd41 .) The component appears to not be treated as a LightningElement per the @lwc/compiler – it is simply treated as a plain JavaScript (non-LWC) module.

Steps to Reproduce

08c93cd41

Expected Results

export default class Component extends LightningElement {}

is equivalent to

class Component extends LightningElement {}
export { Component as default }

Actual Results

Nothing is rendered, and the component is not registered at runtime as a proper LightningElement.

Version

This issue exists in master today, as well as LWC v5.0.9 (Spring '24) and v3.0.4 (Winter '24). It even repros in LWC v2.11.8 (Summer '22). So this is a longstanding issue.

Possible Solution

Use the export default class ... syntax instead.

nolanlawson avatar Feb 27 '24 22:02 nolanlawson

This issue has been linked to a new work item: W-15135580

git2gus[bot] avatar Feb 27 '24 22:02 git2gus[bot]

I think to solve this, a bare-minimum solution would be:

  1. Detect usage of export { Foo as default }
  2. Check the same JS module to see if there is a class Foo extends LightningElement {}

(The solution would likely be in @lwc/babel-plugin-component.)

I think other, exotic patterns are not strictly necessary to support:

// const Foo
const Foo = class extends LightningElement {}
export { Foo as default }
// Foo imported from elsewhere
import { Foo } from './elsewhere.js'
export { Foo as default }
// really zany
const Foo = (() => { return true && class extends LightningElement {} })()
export { Foo as default }

nolanlawson avatar Feb 27 '24 23:02 nolanlawson

We can probably also support some other simple patterns:

class Foo extends LightningElement {}

export default Foo

Maybe even:

const Foo = class extends LightningElement {}

export default Foo

nolanlawson avatar Feb 28 '24 18:02 nolanlawson

What are the behavioral differences (in ESM land, outside the LWC context) between export default const Foo = ... vs export { default as Foo }? In particular, when we compile could we just change export { Foo as default } to export default class Foo {} or would that cause side effects?

wjhsf avatar Feb 28 '24 18:02 wjhsf

@wjhsf We can't always safely change the ordering, because it matters in certain cases. For example:

export default Foo
class Foo {}

^ This is a syntax error (Cannot access 'Foo' before initialization). (Some minifiers like Terser and SWC will "helpfully" fix it for you though. 🙃)

In particular, when we compile could we just change export { Foo as default } to export default class Foo {} or would that cause side effects?

Maybe, but I wonder if it's better to just leave things alone, since otherwise we could cause other unforeseen problems.

nolanlawson avatar Feb 28 '24 18:02 nolanlawson

Hi @nolanlawson i would like to contribute and help fix more bugs please let me know where i can be helpful.

HermanBide avatar May 07 '24 17:05 HermanBide