samchon / typia

Super-fast/easy runtime validators and serializers via transformation
https://typia.io/
MIT License
4.56k stars 157 forks source link

[Feat request] Plugin for modern build and testing tools #834

Closed ruizmarc closed 4 months ago

ruizmarc commented 1 year ago

Feature Request

It would be great to have a small plugin for each modern build tool such as Esbuild and SWC that allows an easy integration in existing projects as tsc is being falling behind those modern build tools in terms of build time.

The main problem we face is that we work with a monorepo of microservices and we have a lot of pipelines to automate all our internal process such as build, type checking or having live reload during development so only the affected microservices by the change reloads. As we have a very huge codebase, we moved to ESBuild to reduce our build times by a factor of 10 or higher.

Integrating Typia in our projects would require modifying all those pipelines and would add extra efforts to be able to develop with live reload if we have an external tool generating files to compile in addition to ours. However, if it is integrated in the life cycle of Esbuild (our case) or SWC for others, it would make integration very straight forward and they already provide an API for plugin development.

In addition to this, it would be also be very useful to have a plugin/transformer for Jest. Because in our case, we use Jest with Esbuild transfomer to test all our code, but there is also a lot of people using Jest + SWC as in both cases you can achieve an amazing reduction of testing time (x10).

I know we can use generation mode as an standalone tool, but it would be amazing to have it integrated in those tools as it is integrated with tsc.

Thanks in advance, it is an amazing lib!

tsengia commented 1 year ago

NOTE: I am not the author of this repo, but leaving a suggestion here in case it helps

I don't think this is possible currently with Typia, and ESBuild integration would be impossible because ESBuild does not provide plugins access to its transform API:

"Plugins can also only be used with the build API, not with the transform API." https://esbuild.github.io/plugins

samchon commented 1 year ago

SWC and EsBuild erases every TypeScript type infos.

So it is not possible to support. Without type info, typia cannot do anything.

ruizmarc commented 1 year ago

I was mentioning to use a plugin in the lifecycle as they might have a hook before erasing the types, so we can do the code generation on that file, before they get removed. I've seen they have an OnLoad Hook when it loads the file to compile, maybe we can use it then.

I don't know enough about esbuild or SWC and not sure if they expose the code before removing types, but I've seen a lot of plugins generating code during the build process and I think it is just what we need, generating the code before types get removed.

ruizmarc commented 1 year ago

In fact, for example https://github.com/Hookyns/tst-reflect is preparing a new version of the library for type reflection and they have done exactly what I was suggesting. They have the same need about types that Typia.

The new version has a esbuild plugin that makes it work with esbuild. https://stackblitz.com/edit/rttist-playground-esbuild-with-plugin?file=src%2Findex.ts

samchon commented 1 year ago

Interesting, I'll study it. But it would be next year because my current year's plans are full.

samchon commented 1 year ago

@ruizmarc

By the way, this is my personal question. It seems like that the plugin setting makes the program to use not esbuild compiler, but standard TypeScript compiler instead. In that case, what is different with just using typescript compiler like below case? User installs esbuild, but cannot take any advantage of the esbuild's speedy compilation.

https://typia.io/docs/setup/#vite

ruizmarc commented 1 year ago

Esbuild and Vite are not the same thing. Vite uses esbuild as the default compiler but Vite is a development tool for frontend with a lot of features. So, if you have a setup in which you need transformers (very common in frontend) that are not yet supported or migrated to esbuild, you can choose to use all the power of Vite, but using tsc as compiler. Using Vite plugins doesn't not require disabling esbuild, it just depends on your need.

tsengia commented 1 year ago

Just a thought here, could Typia's generation be invoked whenever the onLoad plugin hook for esbuild in invoked? https://esbuild.github.io/plugins/#on-load

tsengia commented 1 year ago

I briefly started down the path of looking into this and here are some of my thoughts:

  1. ESBuild uses the same "file in file out" approach that Babel uses as well, which is counter to TypeScript's (and thus Typia's) "project in project out" mentality
  2. ESBuild does not allow esbuild plugins to the transform API
  3. Many people (like myself) will use Vite in their projects. Vite only allows esbuild plugins that operate during dependency scan and optimization (see this thread)
  4. Because Vite provides a superset of Rollup plugin API, it supports Rollup's transform() build hook that can be use to perform file by file transpilation.

The main roadblock I see here is Typia's "project in project out" transform strategy versus Rollup's "file in file out" transform strategy.

Would it be possible to add an option to Typia that causes it to generate static parsing/serialization methods for classes/interfaces that can then be imported by other classes/interfaces in other modules?

For example:

/// ChildInterface.ts
export default interface ChildInterface {
      first: number;
      second: boolean;
}

/// MyClass.ts
import ChildInterface from "./ChildInterface";

class MyClass {
    a: string;
    b: number;
    c: ChildInterface;
}

let m: MyClass = typia.json.parse(json_string);

Would be transformed into something like:

/// ChildInterface.ts
export default interface ChildInterface {
      first: number;
      second: boolean;

      public static _$typiaJsonParse(input: object) {
         /// Typia generated code for parsing ChildInterface would go here
      }
}

/// MyClass.ts
import ChildInterface from "./ChildInterface";

class MyClass {
    a: string;
    b: number;
    c: ChildInterface;

      public static _$typiaJsonParse(input: object) {
         /// Typia generated code for parsing MyClass would go here
         c = ChildInterface._$typiaJsonParse(input.c);
      }
}

let m = MyClass._$typiaJsonParse(object);

Performance may take a small hit due to an extra function call being added, however I believe this may be worth it in order to support a larger range of tooling options.

Keeping track of dependencies might be a little complex using the above example. This is because it still breaks the "file in file out" strategy because one file (MyClass.ts) implicitly requires Typia generation to occur in another separate file (ChildInterface.ts).

Here's another example that may simplify the logic of tracking which classes need to be transformed:

/// ChildInterface.ts
import { TypiaSerializable } from "Typia";

export default interface ChildInterface extends TypiaSerializable {
      first: number;
      second: boolean;
}

/// MyClass.ts
import { TypiaSerializable, typia } from "Typia";
import ChildInterface from "./ChildInterface";

class MyClass extends TypiaSerializable {
    a: string;
    b: number;
    c: ChildInterface;
}

let m: MyClass = typia.json.parse(json_string);

would be transformed into something like:

/// ChildInterface.ts
import { TypiaSerializable, typia } from "Typia";

export default interface ChildInterface extends TypiaSerializable {
      first: number;
      second: boolean;

      public static _$typiaJsonParse(input: object) {
         /// Typia generated code for parsing ChildInterface would go here
      }
}

/// MyClass.ts
import ChildInterface from "./ChildInterface";
import { TypiaSerializable, typia } from "Typia";

class MyClass extends TypiaSerializable {
    a: string;
    b: number;
    c: ChildInterface;

      public static _$typiaJsonParse(input: object) {
         /// Typia generated code for parsing MyClass would go here
         c = ChildInterface._$typiaJsonParse(input.c);
      }
}

let m = MyClass._$typiaJsonParse(object);

This would allow Typia to perform file-by-file transforms, but requires the user to ensure all interfaces/classes that are serialized/parsed by Typia extend the TypiaSerializable abstract class. Additionally, performance may take a slight hit due to the extra function call.

@samchon what do you think of this? If Typia were able to support the "file in file out" paradigm that Rollup uses, then I believe writing plugins for Rollup, Vite, etc. would be much easier. However I don't know if my suggestion would even work or if I'm talking nonsense...

ruizmarc commented 1 year ago

If Typia can support file In file out, that would allow to use it also with Jest transformers. Jest is one of the most used testing frameworks, and it would be an awesome feature to be able to support it without extra efforts.

reosablo commented 11 months ago

I made a PoC of esbuild plugin.

https://gist.github.com/reosablo/c679a6cff340c5a6c007c2e6e697d7f6

This plugin breaks source map. The TypeScript's TS to TS transformer doesn't seem to support source map generation for now.

ryoppippi commented 4 months ago

Hi guys! I made a plugin for bundlers including esbuild! Check this out and plz give us a feedback!

https://typia.io/docs/setup/#unplugin-typia

ryoppippi commented 4 months ago

@samchon Same as https://github.com/samchon/typia/issues/812#issuecomment-2175598578