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

Preserve Typescript types in output #372

Closed
CKGrafico opened this issue May 20, 2022 · 17 comments
Closed

Preserve Typescript types in output #372

CKGrafico opened this issue May 20, 2022 · 17 comments
Labels
cli core Mitosis Core enhancement New feature or request help wanted Extra attention is needed TypeScript

Comments

@CKGrafico
Copy link
Contributor

CKGrafico commented May 20, 2022

Hello not sure if I'm understanding well the purpose of this library but when I create a component like:

export type ButtonProps = {
  name: string;
};

export default function Button(props: ButtonProps) {
  return <button>{props.name}</button>;
}

The result is:

export default function Button(props) {
  return <button>{props.name}</button>;
}

or:

import { Component, Input } from "@angular/core";

@Component({
  selector: "button",
  template: `
    <button>{{name}}</button>
  `,
})
export default class Button {
  @Input() name: any;
}

If I want to use mitosis to create my own components library, how I can get the types of my components to be able to expose them?

@PatrickJS
Copy link
Collaborator

yeah thats a good point. @samijaber what do you think is the best way to transfer the types over?

@samijaber
Copy link
Contributor

If I want to use mitosis to create my own components library, how I can get the types of my components to be able to expose them?

Hey @CKGrafico, that's a very good question. Unfortunately, at the moment, all types get stripped out when the component JSON is generated from the Mitosis source code. Preserving types is definitely an important feature that we want to incorporate into Mitosis though, so let me try to break down what this requires!

Our JSX parser already supports TypeScript syntax:

presets: [[tsPreset, { isTSX: true, allExtensions: true }]],

However, we do not store any of these types yet. It will be a lot of work to store TS types, but we can definitely chip away at this problem incrementally.

I'm going to lay out what steps I think are needed to accomplish that. Sharing it in the open as notes for myself, but also in case you or anyone else is intrigued and would like to help in some capacity!

Example

If we were to use this very simple example:

https://astexplorer.net/#/gist/2ab2a4286ccc434c75e93310bfea78bd/d697bb79cffd991440d7bcb0e6cc87898541f73b

You can see:

  • TypeAliasDeclaration and InterfaceDeclaration which represent the type declarations Props and Props2
  • Parameter objects have a type: TypeReference value which stores, e.g. : Prop within the (prop: Prop) argument declaration.

1. Store type information

We would need to start storing this information in the Mitosis JSON. We could have root-level types and interfaces keys in our MitosisComponent object that store such information as a start (this wouldn't account for types defined within the component body for now, but that's fine):

export type MitosisComponent = {

2. Store type references

Now that we are storing types and interfaces, we need to make sure that the arguments and variables/constants are holding on to their type references.

For props, seeing as that's a big of a special case, we might get away with a propsTypeReference: string key in MitosisComponent that holds on to the name of the type reference.

For the rest of our code (hooks, state properties, etc.), we currently use babel's generator function to convert a Node into code, and then store that code:

const parseCodeJson = (node: babel.types.Node) => {
const code = generate(node).code;
return tryParseJson(code);
};

This would have to change such that we are also storing type information. I am not exactly sure if we can re-purpose this generator, or would have to use a different tool to generate the code (perhaps typescript?) such that it still preserves type information. Ideally, the code: string values that we store could simply be improved such that they hold on to type information.

3. inject type information in each generator

Now that we have the types, it is fairly easy to go into each generator (vue, react, svelte, etc.) and make sure to also inject that type information into the generated source code:

This would likely look like:

(props: ${json.propTypeReference ?? 'any'))`

And if we replace the Babel generator with some other TS-based generator, then the code strings we inject in the generators should work as-is. But if we end up storing the types in a separate field, then that will also require more stitching together.

NOTEs

  • svelte needs a lang=ts attribute added to its <script> to handle TS
  • we would definitely need to make types optional so that folks can still get plain JS if they'd like.
  • context files have their own build process that we would need to account for to preserve their types.
  • when running mitosis build, non-mitosis files inside of src/ are currently transpiled into JS, we'd need to stop doing that and instead keep them as-is.
  • when running mitosis build, React/React-Native components run our transpile() helper function, which uses esbuild.Transform under the hood:

export const transpile = async ({
path,
content,
target,
options,
}: {
path: string;
content?: string | null;
target: Target;
options: MitosisConfig;
}) => {
try {
const transpilerOptions = options.options[target]?.transpiler;
const format =
transpilerOptions?.format || getDefaultFormatForTarget(target);
let useContent = content ?? (await readFile(path, 'utf8'));
useContent = useContent.replace(/getTarget\(\)/g, `"${target}"`);
const output = await esbuild.transform(useContent, {
format: format,
loader: 'tsx',
target: 'es6',
});

This strips away type information. We would need to explore if that can be removed or circumvented

@CKGrafico
Copy link
Contributor Author

Hello thanks for your awesome and detailed reply, that was really useful :D

@samijaber samijaber added the help wanted Extra attention is needed label May 21, 2022
@decadef20
Copy link
Contributor

Very clear explanation. It seems like a changeling issue. If possible, I want to have a try.

@samijaber
Copy link
Contributor

@decadef20 That would be great! This is a critical and complex feature, so if you do take it on, please reach out with any questions as you build it out, so we can make sure you're approaching it the same way we are envisioning it and not waste your time rewriting a lot of code. We're here to help 👍🏽

@decadef20
Copy link
Contributor

@decadef20 That would be great! This is a critical and complex feature, so if you do take it on, please reach out with any questions as you build it out, so we can make sure you're approaching it the same way we are envisioning it and not waste your time rewriting a lot of code. We're here to help 👍🏽

OK。At first,I plan to implement it on react framework. Make sure what I build out is as expected.

@samijaber samijaber changed the title Where are the Typescript types after build? Preserve Typescript types in output Jun 7, 2022
@samijaber samijaber added enhancement New feature or request core Mitosis Core cli labels Jun 7, 2022
@PatrickJS
Copy link
Collaborator

PatrickJS commented Jun 7, 2022

this is working correctly in React now a701452 via #420. We can create new issues for each framework when others need it. to convert the types for Angular that can get pretty tricky

@originswift-sys
Copy link

@PatrickJS how soon will this be published to npm?

@PatrickJS
Copy link
Collaborator

@samijaber can you publish for @originswift-sys

@samijaber
Copy link
Contributor

@originswift-sys Released in core version 0.0.56-14, and CLI version 0.0.17-13

@originswift-sys
Copy link

originswift-sys commented Jun 8, 2022

Little hiccup...

The source:

import { HTMLAttributes } from "react";
import { Show } from "@builder.io/mitosis";

export const headingSizes = [1, 2, 3, 4, 5, 6];

export interface HeadingProps extends HTMLAttributes<HTMLHeadingElement> {
  size: typeof headingSizes[number];
}

export default function Heading(props: HeadingProps) {
  return (
    <>
      <Show when={props.size === 1}>
        <h1 className="font-extrabold text-4xl">{props.children}</h1>
      </Show>
      <Show when={props.size === 2}>
        <h2 className="font-extrabold text-3xl">{props.children}</h2>
      </Show>
      <Show when={props.size === 3}>
        <h3 className="font-extrabold text-2xl">{props.children}</h3>
      </Show>
      <Show when={props.size === 4}>
        <h4 className="font-extrabold text-xl">{props.children}</h4>
      </Show>
      <Show when={props.size === 5}>
        <h5 className="font-extrabold text-lg">{props.children}</h5>
      </Show>
      <Show when={props.size === 6}>
        <h6 className="font-extrabold text-base">{props.children}</h6>
      </Show>
    </>
  );
}

generates...

export interface HeadingProps extends HTMLAttributes<HTMLHeadingElement> {
  size: typeof headingSizes[number];
}

export const headingSizes = [1, 2, 3, 4, 5, 6];

export default function Heading(props: HeadingProps) {
  return (
    <>
      {props.size === 1 ? (
        <>
          <h1 className="font-extrabold text-4xl">{props.children}</h1>
        </>
      ) : null}

      {props.size === 2 ? (
        <>
          <h2 className="font-extrabold text-3xl">{props.children}</h2>
        </>
      ) : null}

      {props.size === 3 ? (
        <>
          <h3 className="font-extrabold text-2xl">{props.children}</h3>
        </>
      ) : null}

      {props.size === 4 ? (
        <>
          <h4 className="font-extrabold text-xl">{props.children}</h4>
        </>
      ) : null}

      {props.size === 5 ? (
        <>
          <h5 className="font-extrabold text-lg">{props.children}</h5>
        </>
      ) : null}

      {props.size === 6 ? (
        <>
          <h6 className="font-extrabold text-base">{props.children}</h6>
        </>
      ) : null}
    </>
  );
}

All imports are not exported like before. Did I miss something? I was on v0.0.55 and v0.0.16 before.

@samijaber
Copy link
Contributor

All imports are not exported like before

I don't understand this statement. What are you expecting to see? The generated output you shared looks good to me. What is it missing?

@originswift-sys
Copy link

It's missing import { HTMLAttributes } from "react"; and any other imports I add.

I've been able to avoid getting stuck by using the react global React.HTMLAttributes, to avoid imports.

If exporting imports has been removed in recent versions, I'm not aware, but the previous versions I mentioned reexported all my imports.

@samijaber
Copy link
Contributor

Oh, I missed that. Thanks for explaining. Will file a separate issue for this.

@originswift-sys
Copy link

Great. Thanks!

@PatrickJS
Copy link
Collaborator

we also do React.HTMLAttributes for these types for now

@samijaber
Copy link
Contributor

Closing this issue in favour of individual issues per framework. The core engine now supports storing type information in the Mitosis JSON 👍🏽

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
cli core Mitosis Core enhancement New feature or request help wanted Extra attention is needed TypeScript
Projects
Status: Done
Development

No branches or pull requests

5 participants