Search code examples
stenciljsstencil-componentstencil-compiler

Stencil: Sudden build error without message after adding `@Method` to component


I cannot give too much information here (because there really isn't), but only this:

All of the sudden, after adding a @Method function to a stencil component:

@Method()
async setMenuItems(items: Element[]): Promise<void> {
  // code here
} 

the component stopped compiling with the following - really unhelpful - error:

[ ERROR ]  ./src/components/menu-content/menu-content.tsx:63:44
           build error

     L62:  @Method()
     L63:  async setMenuItems(elements: Element[]): Promise<void> {
     L64:    const unsupportedChildren = elements.filter(e => !this.isSupportedChild(e)).map(e => e.tagName);

[12:37.1]  build failed in 7.02 s

Things to notice

  • the return type Promise<void> inside the error-message is highlighted red
  • there are other @Methods that do work within this component (even with the same return type).
  • the "broken" @Method is structurally equal to those that do work.
  • TypeScript compiler does not complain about anything
  • Only stencil compiler fails

I already tried...

  • to google for this issue - did not find any hints to this problem
  • to remove the async and add return Promise.resolve()
  • to rename the method (I mean.. why not)
  • to move the method to another place in class
  • to remove the whole method (compiles fine x( )
  • to remove the @Method decorator (compiled, but of course my method is removed from API)
  • to delete node_modules folder and reinstall

I remember that I already had this error once, and apparently I somehow fixed it (or not, idk). But if I did, I cannot remember how.

Does anyone have an idea how to debug this - or even better fix?


Solution

  • I figured it out. A more complete version of my component is:

    import { Element, ... } from '@stencil/core';
    
    class MenuContent {
      @Element() element: MenuContentElement;
    
      @Method()
      setMenuItems(elements: Element[]): Promise<void> {
        // ------------------^^^^^^^
        // Element is meant to be the built-in browser interface for Element
        // but stencil thinks that this is the imported Element from '@stencil/core'!
      }
    }
    

    The exact problem here is, that the stencil-compiler seems to assume that the elements parameter is of type Element that is imported from @stencil/core which is obviously wrong and leads to this strange unhelpful error.

    Possible Solutions

    1. Use an alias type for the built-in Element type

    // types.ts
    export type DomElement = Element;
    
    // menu-content.tsx
    import { DomElement } from './types';
    ...
    async setMenuItems(elements: DomElement[]): Promise<void> { ... }
    

    2. Switch to HTMLElement

    Note: This is only legit, when you don't need to support other Element-types such as SVGElements for example!

    async setMenuItems(elements: HTMLElement[]): Promise<void> { ... }
    

    3. Use alias in import statement

    Please note: When using @stencil eslint rules, they will complain about your renamed import and say that "own class members are not allowed to be public".

    I created a ticket for it here: https://github.com/ionic-team/stencil-eslint/issues/28

    import { Element as ElementDecorator} from '@stencil/core';
    
    class MenuContent {
      // eslint will complain about that:
      // "Own class properties cannot be public."
      @ElementDecorator() element: HTMLMenuContentElement;
    }