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

Allow the use of TypeScript mixins with <svelte:options extend /> #9032

Closed
willnationsdev opened this issue Jul 23, 2023 · 4 comments
Closed

Comments

@willnationsdev
Copy link

willnationsdev commented Jul 23, 2023

Describe the problem

Related to #4168.

I thought I could work around the lack of base type overrides by at least using mixins to automatically establish some properties on components from a single location using a mixin, like so:

// mixins.ts

type Constructor<T> = new(...args: any[]) => T;

export function CustomElement<T extends Constructor<Element>>(Base: T) {
  return class extends Base {
    constructor(...args: any[]) {
      super(...args);
      this.host = this;
    }
  }
}

export function FormItem<T extends Constructor<HTMLElement>>(Base: T) {
  return class extends CustomElement(Base) {
    static formAssociated = true;

    constructor(...args: any[]) {
      super(...args);
      this.internals = this.attachInternals();
    }
  }
}

Then, import and use those mixins in the extend function, like so:

<!-- my-input.svelte -->
<script context="module">
  import { FormItem } from "./mixins";
</script>

<svelte:options
  customElement={{
    tag: "my-input",
    extend: (ctor) => {
      return class extends FormItem(ctor) {
        // According to a TypeScript GitHub pull request explaining it,
        // TypeScript mixins require a syntax matching the `Constructor<T>`.
        // However, that means we need an `...args: any[]` parameter.
        // If you attempt to add it to the constructor though, Svelte has a parse error.
        constructor() {
          super();
        }
      }
    }
  }}
/>

<script lang="ts">
  export let host: HTMLElement = null;
  export let internals: ElementInternals = null;
  export let name: string;
  export let type: string;
</script>

<input {type} {name} />

Describe the proposed solution

I'd like to be able to adjust the extend lambda so that it looks like this:

{
  extend: (ctor) => {
    return class extends FormItem(ctor) {
      constructor(...args: any[]) {
        super(...args);
      }
    }
  }
}

Alternatives considered

As far as I know (based on the GitHub PR), this is a requirement of the language, so I don't think there are any alternatives available.

As it stands, I have to copy/paste the same base class logic in every single custom element I make with Svelte components that happen to share functionality (not ideal, obviously).

Importance

would make my life easier

@willnationsdev
Copy link
Author

willnationsdev commented Jul 23, 2023

It would seem that you can still use mixins if the mixins themselves are written in a plain JavaScript file rather than in a TypeScript file. So this is actually just a bug specific to TypeScript. That downgrades the "Importance" here from "would make my life easier" to a "nice to have".

@willnationsdev willnationsdev changed the title Allow the use of mixins with <svelte:options extend /> Allow the use of TypeScript mixins with <svelte:options extend /> Jul 23, 2023
@hackape
Copy link
Contributor

hackape commented Jul 24, 2023

@willnationsdev I'd like to explain a bit about Svelte's architecture so that you can get the big picture about what works and what not.

Svelte only understands JS, it supports other languages (TS included) through preprocessor that compiles into JS. Internally it uses acorn to parse expressions between curly braces, and since acorn is a JS parser, those epxressions must be plain JS.

To each component file there're 3 parts that're pre-processable: markup, script, and style. Although you can write TS between <script lang="ts">, that part is handled by the script preprocessor. However <svelte:options> belongs to markup, you'll need a markup preprocessor to support TS between curly braces. Same applies to other markup element e.g. <div>{foo as number}</div> does not work either.

This architecture is highly unlikely to change in any foreseeable future, and out of the scope of Svelte core compiler. If it's important to you, you might want to ask for a markup preprocessor that handles TS between curly braces in the svelte-preprocess repo instead.

Meanwhile, a easy workaround is to write your extend function inside script tag and reference it in markup.

<svelte:options
  customElement={{
    tag: "my-input",
    extend: extendElement,
  }}
/>

<script lang="ts">
  import { FormItem } from "./mixins";

  function extendElement(ctor: new () => HTMLElement) {
    return class extends FormItem(ctor) {
      constructor(...args: any[]) {
        super(...args);
      }
    }
  }
</script>

@willnationsdev
Copy link
Author

@hackape Thanks. After I made the comment, I forked Svelte and investigated it myself and deduced much of what you said above. The thing is, I coulda sworn I encountered this problem even after I moved the logic to a dedicated .ts file. I'll try again tomorrow and see if I can reproduce the issue or if it was just my bad memory. Either way, if the Svelte options parser is a JS parser, then there is indeed no reason to even consider this a bug, provided I can verify the linked mixin works as expected.

@dummdidumm
Copy link
Member

This is essentially a duplicate of #4701 - closing in favor of that. Right now you can't use TS inside the template, and this is counted as part of the template technically speaking.

@dummdidumm dummdidumm closed this as not planned Won't fix, can't repro, duplicate, stale Jul 24, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants