apazureck / odatatools

OData Tools for vscode
24 stars 12 forks source link

Generated TypeScript Customization #23

Closed tonysneed closed 6 years ago

tonysneed commented 6 years ago

Is there a way to customize the generation of TypeSript code? For example, generate classes that extend a base class. /cc @lelong37

apazureck commented 6 years ago

Hi @tonysneed,

If I get it right you want to make for example the EntitySet class to inherit from some base class? Or the container class inherit from some base class? In general, it would be possible to add some "after generated" event. What do you want to do exactly?

Maybe you could use declaration merging in typescript, if you want to add some extra functions.

tonysneed commented 6 years ago

@apazureck So what I'm doing is generating TypeScript files based on model metadata. First, I'll extend a base class called TrackableEntity, and I'll insert a constructor that calls a method in the base class. Then I'll set the type of collection navigation properties to TrackableSet<T>. For EF6 I would do this via T4 templates, so that users of my library can alter the code generation to their liking. So I'm curious if this would also be a possibility with OData Tools? It doesn't have to be with T4. We could use handlebars instead, for example.

apazureck commented 6 years ago

Sorry, I never looked into T4 templates, so I don't have any clue how they do it. But maybe I check it out to see if there are some useful ideas.

For now I just deserialize the incoming XML, go through the entries and put it out as interfaces.

But I really like the thought of the templates and string replace. So it would be like a subfolder in the .vscode folder in the project which contains the templates. I would use the vscode syntax for variable replacements. The general interface would look like this:

export interface ${interfaceName} {
    ${interfaceProperties}
}

so you could modify it for example like this:

export class ${edmClassName} extends TrackableEntity {
    constructor() {
        super();
    }
    ${edmProperties}
    ${edmNavigationProperties}
}

Maybe I will switch that for the proxy generator v1.0.0, too.

So as you are the expert on T4, are there anymore useful settings?

tonysneed commented 6 years ago

T4 has been traditionally associated with Visual Studio on Windows, so the tide is shifting away from it and toward other templating engines. Even the Entity Framework team has opted not to use T4 for EF Core code generation.

An alternative to T4 which seems to have some traction is Handlebars, which is used by Breeze for code gen: https://github.com/Breeze/breeze.tooling/tree/master/TypeScriptModuleGen. I would suggest you have a look at that to see if you can glean something from it.

I'm working on a plug-in for EF Core that will use Handlebars.Net templates to generate both C# and TypeScript classes when the EF Core CLI scaffolding command is executed.

apazureck commented 6 years ago

Handlebars seems quite great, but {{ would be generally valid typescript. But I don't think sb. would it that way, as it does not make sense. I'm currently working on an rxjs version anyway and so I could make a good use of those templates. Thanks for the hint!

apazureck commented 6 years ago

Hey @tonysneed,

I just played around a little bit with handlebars; it works really great and is very intuitive. Made some little changes to convert the xml metadata to a cleaner class layout and I was able to recreate the interface creation as it was inteded before just in a few hours. This also will kick the stupid "Ambient" and "Modular" option.

I created a subfolder in the .vscode section of the project and if no template is there so far a default template will be copied to that location, so you will be able to modify it. I am also planning to extend this to the proxy generator, so everything will be fully customizable.

I am going to implement the templates this weekend or next week, so you can try it for yourself. Thank you for that hint again, this really rocks!

Here is the example:

{{Header}}

import * as xy from 'xy';

declare namespace {{Namespace}} {
    {{#each EntityTypes}}
    export interface {{this.Name}} {
        {{#each this.Properties}}
        {{this.Name}}: {{this.Type}};
        {{/each}}
        {{#each this.NavigationProperties}}
        {{this.Name}}{{#if Nullable}}?{{/if}}: {{this.Type}};
        {{/each}}
    }
    {{/each}}
}

type JSDate = Date;

declare namespace Edm {
    export type Duration = string;
    export type Binary = string;
    export type Boolean = boolean;
    export type Byte = number;
    export type Date = JSDate;
    export type DateTimeOffset = JSDate;
    export type Decimal = number;
    export type Double = number;
    export type Guid = string;
    export type Int16 = number;
    export type Int32 = number;
    export type Int64 = number;
    export type SByte = number;
    export type Single = number;
    export type String = string;
    export type TimeOfDay = string;
    export type Stream = string;
}
tonysneed commented 6 years ago

@apazureck 👍 on your customization strategy!

apazureck commented 6 years ago

@tonysneed: I nearly got it, but I won't make it this week-end. I hope I can finish it next week.

Got the interfaces so far, but I will remove the get interfaces entirely and just get all data from the service. Then it depends which template you want to use, so you are able to put all in one file or split it across more files, as you like.

If you want to use the interface generation just yet please check it out and run the extension, so you can use it. Demo templates are included.

Cheers

apazureck commented 6 years ago

@tonysneed: Sorry, work is delayed. I just wanted to unittest all the stuff but did not find time lately. My default template has still a few bugs and I think circular references may crash the generator. But I need to write some test scenarios for that.

But you can check out the development branch and start it by hand. Just set the version to "2.0".

As I finished some other projects lately I hope I have time the next weeks to finish the work.

apazureck commented 6 years ago

@tonysneed: OK, I had time tonight and finished a first draft. To get the template you have to

  1. set the extension to insider mode
  2. set the version to 2.0

(Workspace or user settings)

{
"odatatools.insiders": true,
"odatatools.version": "2.0"
}
  1. Create a new file
  2. Call create proxy function and give your address (dont't forget http:// at the beginning) After that the extension should create a folder in your workspace: ${workspaceRoot}/.vscode/odatatools/templates

There is a file called proxy.ot. This is my test proxy template I tested with some simple unit tests. Hope it works so far.

If you want to change the template have a look at the output window. In the OData Tools Output should be the resolved json of the metadata of your service. You can use this to modify the template.

You can also change the template. See the odata header at the top of the generated file. There is the file name. Copy the proxy.ot and rename it. After that you can reference the file.

Let me know if something is missing or not working for you.

apazureck commented 6 years ago

@tonysneed:

I'll close this issue as it seems to work. Sadly I did not have the time to unittest / integratetest every corner. I hope it works for you, let me know if it doesn't.