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

Output TypeScript typedefs #11

Open
bencompton opened this issue Mar 27, 2018 · 15 comments
Open

Output TypeScript typedefs #11

bencompton opened this issue Mar 27, 2018 · 15 comments
Labels
Feature Request New feature or request High Priority

Comments

@bencompton
Copy link
Collaborator

No description provided.

@JasonKleban
Copy link
Contributor

(continued from https://github.com/JasonKleban/Framework7.d.ts/issues/31#issuecomment-414347635)

@nolimits4web - great! I glanced over the source. I have some questions.

In Phenome.js, are you planning to write out typescript source for each of vue, react, and web component outputs and then have the .d.ts extracted automatically during a tsc/vue build? Or are you planning on having additional /lib/compilers/*/ compilers for each of them that directly generates companion .d.ts files?

Do you have an outline of the desired generated definitions for each? Like, an idea of the structure of the generated .d.ts; or .tsx, .ts.vue, and ... whatever web components' type safety is supposed to look like? If not, I think there's a bit of an art to structuring it. My new v3 branch has some ideas (especially in framework7-react) (not sure of them though) on declaration merging that might allow Phenome.js to make a single pass down each component, grouping all component-level and app-level changes together by the component that adds them. Since I haven't gotten very far, you may be able to forsee also the different facets of generation that need to be supported. So far I have seen app-level and component-level params/props, methods, events, and maybe even static methods? What else does it need templates for?

Could you start a compiler of .d.ts files within Phenome.js source that merely lists out the components? I'm currently of the opinion that react is much more typesafe friendly (and, in fairness, that typescript is more react-friendly) compared to vue, so I'd suggest starting with /lib/compilers/react/jsx-transformer/typedefs.js (?).

The code in, eg, lib/compilers/react/jsx-transformer/index.js is a little dense for me as an outsider (which is why I push typescript 💁). Could you maybe comment it more? Or heavily comment the new skeletal typedefs.js?

@nolimits4web
Copy link
Member

nolimits4web commented Aug 22, 2018

Hey @JasonKleban

First of all, we need to abstract from Framework7 :). Here we only compile/generate components. And i think we need the thing that will generate .d.ts companion files to already compiled components.

Phenome manipulates with AST in format generated by acorn. Under the hood it takes original component declaration object and continuously modifies and transforms it for final output. Each Vue/React compilers are separate. First stage is JSX transformer which transforms JSX to render function, like babel's JSX transform but better 😈 , next stage is component transformer that modifies object and transforms it to class like in case of React.

With ts generation i think it should be easier as we don't need to modify original object. We need to walk through original object, for example:

export default {
  name: 'my-component',
  props: {
    foo: Boolean,
    bar: String,
  },
  ...
  methods: {
    open() {
      this.dispatchEvent('open', el);
      ...
    },
    close() {
      ...
    },
  },
}

Then using AST walk we need to collect (as you mentioned) props, methods and events and generate the string that will be saved to .d.ts file, something like this (based on your f7.d.ts):

namespace MyComponent {
    export interface Props {
        // Props
        foo?: boolean
        bar?: string
        // Events
        onOpen?: (el : HTMLElement) => void
    }
}

export class MyComponent extends React.Component<MyComponent.Props, {}> {
    // Methods
    open(animate: true) : void
    close(animate: true) : void
}

So yes, i think we need additional lib/compilers/react/ts-transformer/index.js to do this. We can setup there kind of string template:

const dtsTemplate = `
namespace {{name}} {
    export interface Props {
       {{props}}
       {{events}}
    }
}
export class {{name}} extends React.Component<{{name}}.Props, {}> {
    {{methods}}
}

And based on collected data from AST walk, just to fill it.

If you want, i will try to setup React' ts-transformer file and stick it to component logic with basic template so it should be easier for you to dig in and test the output.

But i'm not too good in TS and maybe wrong in some points here :)

Also we need to hear an input from @bencompton

@nolimits4web
Copy link
Member

Ok, just pushed dts branch where i set up initial typescript definitions generator https://github.com/phenomejs/phenome/blob/dts/lib/compilers/react/typescript-generator/index.js

It doesn't save to file yet, but at least it is good for initial testing where you can see generated output (in console). I also already added methods to collect component props, methods and events into usual arrays so they can be easier processed to fill the dts template

@bencompton
Copy link
Collaborator Author

bencompton commented Aug 23, 2018

I've been meaning to start working on this issue pretty soon. Looks like you guys have a great start on it! @nolimits4web - gathering the props, methods, and events is looking good!

Another feature I had planned TypeScript support for slots. For the components generated by Phenome, that would be easy--just add slot: string to every props type def. For other, more general components, it would probably require extending the base React typings. There may possibly be a way to make the typings for each component allow only the supported slots to be specified based on what the parent component is, but that would probably not be so easy if it were possible. The simple slot: string is definitely a great start.

@nolimits4web
Copy link
Member

nolimits4web commented Aug 29, 2018

Guys, I pushed final bits to dts branch, now it:

  • saves .d.ts file near the compiled component
  • correctly maps prop types to TypeSctipt’s types
  • adds “slot: string” type as well

So far, looks good. At least VSCode correctly suggests component props during writing, so I guess it is working right :)

I would like you to check/test it and if all is good we can do the release.

Yes, and all this for React only. I couldn’t find good or correct example of ts definition for Vue components so maybe need your help with it

@nolimits4web
Copy link
Member

@bencompton

There may possibly be a way to make the typings for each component allow only the supported slots to be specified based on what the parent component is

This can be extremely useful, but not sure it is possible :)

@bencompton
Copy link
Collaborator Author

@nolimits4web - looking good!

This covers props, methods, events, and slots on the Phenome components themselves, and I can't really think of anything else at the moment.

I was initially thinking that we would have to do something to allow slots on non-Phenome components in React (i.e., React HTML elements), but it turns out that @types/react already has slots covered:

    interface HTMLAttributes<T> extends DOMAttributes<T> {
        ...
        // Standard HTML Attributes
        accessKey?: string;
        className?: string;
        contentEditable?: boolean;
        contextMenu?: string;
        dir?: string;
        draggable?: boolean;
        hidden?: boolean;
        id?: string;
        lang?: string;
        placeholder?: string;
        slot?: string;
        ...

Once F7 is generating type definitions, that should be the best test. As for Vue, that situation is more complicated like you said. React typings is a great feature for now! Nice work!

@bencompton
Copy link
Collaborator Author

The only thing I saw was that method return types aren't getting generated and are being left as unknown. Ideally, they would be typed as either void or whatever the actual return type is, but I don't think it is really worth the trouble. In React, it is frowned upon to call methods from outside of a component anyhow, so hopefully nobody will be calling methods on components very often.

@JasonKleban
Copy link
Contributor

Yes, the methods deserve the correct type. If it's not in the Phenome model, perhaps the model can be extended? Unless they are ALWAYS : void

  const output = dtsTemplate
    .replace(/{{name}}/g, camelCaseName)
    .replace(/{{props}}/g, renderedProps)
    .replace(/{{events}}/g, renderedEvents)
    .replace(/{{methods}}/g, renderedMethods);

This is a little dangerous - imagine that renderedProps included documentation from the original Phenome model that included the literal phrase {{methods}} for some reason. Then the final replace logic will corrupt the output of that documentation.

Perhaps something like a single .replace(/({{[a-z]}})/g, (_, placeholder) => { switch (placeholder) {...} } would be a little safer and a little easier to read than the run-on expression in my original?

@JasonKleban
Copy link
Contributor

Along with PropTypes could there be a typescript-type-override option in Phenom models that should be coalesced to the converted proptype when the type is a particular kind of object, like an F7 View or Route or other kind of React Element?

nolimits4web added a commit that referenced this issue Aug 31, 2018
nolimits4web added a commit that referenced this issue Aug 31, 2018
@nolimits4web
Copy link
Member

Guys, just merged dts branch to master and did the release. Made few code tweaks mentioned by @JasonKleban . Also added few bits of awesomeness and it will look for object if it specified as object spreed in props, like we have with Mixins in F7. It even correctly reads it if it was imported from other file like mixins.js. So F7-React definitions have all props in place.

@JasonKleban I’m not sure how to realize ts props override because comments are not parsed by AST so we can’t see them. The only thing I made is if prop type is type of Class (starts from Uppercase) then it will be left as is

@ariofrio
Copy link
Contributor

What version of TypeScript is this targeting? Getting a bunch of compilation errors using TypeScript 3.0.1.

@ariofrio
Copy link
Contributor

ariofrio commented Sep 22, 2018

I committed some fixes (in the above pull requests) to get this to compile. I have one project that's running on it.

What's missing:

  • Support for f7Actions, f7SmartSelect, etc as instance fields of the React component class (i.e. F7Actions, F7ListItem, etc).

    Spent some time looking into this, and I don't see a clear way to add this to phenome: it would require referencing a TypeScript type like so, so it's not as simple as providing an import path:

    import { SmartSelect as SS } from 'framework7/components/smart-select/smart-select';
    type SmartSelect = SS.SmartSelect;
    

    Considered adding a special componentInstances field to phenome components with the name of the framework7-core component, but I imagine you want to keep phenome non-specific to Framework7.

    Also tried to add these typings to framework7-phenome.ts.d, but it doesn't seem possible to augment a class/module that's exported default. Augmenting does work, however, if the class is exported non-default. But it would require changing the signature of the modules phenome generates, or at least making it configurable.

    For now, I'm working around this in my project by doing something like this:

    import * as React from 'react';
    import { ListItem } from 'framework7-react';
    import { SmartSelect as SS } from 'framework7/components/smart-select/smart-select';
    type SmartSelect = SS.SmartSelect;
    
    class MyComponent extends React.Component {
      private listItemRef: React.RefObject<ListItem & {f7SmartSelect: SmartSelect}>;
      // ...
    }
    
  • Anything else?

@nolimits4web
Copy link
Member

@ariofrio thanks for fixes you sent, great job 👍

Good point, i was also thinking about ability to add some extra output to generated TS declarations. So i just pushed 0.1.5 release of Phenome with support such extras using comments in phenome component. 3 types of extras are supported:

  1. Add extra imports
/* phenome-dts-imports
import Something from 'somwhere';
import Something2 from 'somwhere2';
import { Messages as MessagesNamespace } from 'framework7/components/messages/messages';
*/
  1. Add extra props
/* phenome-dts-props
someProp: string
someProp2: any[]
*/
  1. Add extra instance methods or properties:
/* phenome-dts-instance
f7Messages: MessagesNamespace.Messages
*/

nolimits4web added a commit to framework7io/framework7 that referenced this issue Sep 22, 2018
@nolimits4web
Copy link
Member

Pushed F7 fix here framework7io/framework7@bee4ba5

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Feature Request New feature or request High Priority
Projects
None yet
Development

No branches or pull requests

4 participants