woutervh- / typescript-is

MIT License
956 stars 35 forks source link

[Feature Request] babel macro plugin #18

Open andykais opened 5 years ago

andykais commented 5 years ago

Hi! This one is a big ask, but I want to know if it is in the works at all. Currently, this is what my webpack loaders setup looks like:

  module: {
    rules: [
      {
        test: /\.(ts|js)$/,
        exclude: /node_modules/,
        loader: 'babel-loader'
      },
      {
        test: /\.runtime\.ts/,
        exclude: /node_modules/,
        use: [{ loader: 'ts-loader', options: { compiler: 'ttypescript' } }]
      },
    ]
  },

and my .babelrc

{
  "presets": [
    [
      "@babel/env",
      {
        "targets": {
          "node": "8.0"
        }
      }
    ],
    "@babel/typescript"
  ],
  "plugins": ["@babel/plugin-proposal-class-properties"]
}

Before introducing typescript-is, I used the single babel-loader to transpile both typescript and javascript. Now, the code has to go through two different loaders, which slows the process significantly, since typechecking the entire project is essential for the loader to properly transform the code. It also adds more dependencies specifically for this library, and makes tsconfig.json non-standard.

A similar framework to the typescript transformer api in babel land is the babel-plugin-macros project. Importing a typescript-is.macro would operate identically to the current import/transform.

Of course, this hinges on the hope that a babel macro and a typescript transformer could share some core code. If not, then maintaining two alongside each other is likely too much of an undertaking.

andykais commented 5 years ago

Update

I opened an issue in the babel repo to see if typescript transformers could be used in babel, and it appears to not be possible. https://github.com/babel/babel/issues/9813

woutervh- commented 5 years ago

Hi @andykais

Sounds like a good case to support babel. I've never tried compiling TypeScript with babel, so this is new territory for me. I will do some investigation and see if it's possible/how to do it. Will get back to you when I learn more.

andykais commented 5 years ago

If this is helpful, here is an example of the optional chaining syntax written as both a typescript transformer and a babel macro. As far as I can see, there is no code shared between them, but it does still show overlap in their apis.

woutervh- commented 5 years ago

Hi @andykais

Thanks for the links. After some investigation I'm afraid this would be quite a lot of work to get done. As mentioned by @nicolo-ribaudo over in babel/babel#9813, it would require rewriting almost the entire package because the Babel and TypeScript AST are different implementations.

I've glanced at some examples of the Babel AST, and it looks similar to the TypeScript AST, so it wouldn't be impossible. It would take a while to do so. Basically it would require (1) copy & paste the source code of typescript-is (2) replace the dependencies on TypeScript by the Babel equivalents (3) fix the resulting errors.

As I'm a complete beginner to the Babel ecosystem I'm afraid it would take me way too long to get this done. If someone wants to take on this task I will be glad to support them.

andykais commented 5 years ago

Fair enough. Maintaining them side-by-side would be a large undertaking. I also have no babel experience, but I may look into this in the future. Up to you if you wish to close this issue now (and/or label it #wontfix)

thorn0 commented 5 years ago

Turns out there is already an attempt to make a Babel macro: https://github.com/vhfmag/tsguard.macro

Resolving type references seems to be the biggest challenge. Babel doesn't compile TypeScript the same way tsc does. It just removes types without analyzing them, so if we want to understand what MyType is in is<MyType>, we'll have to do all the resolving ourselves, most probably using the TS language service.

woutervh- commented 5 years ago

Thanks for the info @thorn0. I didn't know about this tsguard.macro. It's interesting that babel does some stuff with the types but doesn't go as far as TypeScript to analyze the types. If we do as you propose, to use TS language services, then it sounds like we'll end up compiling everything using TS anyway, even from the babel macro. I'm not sure what the implications are and I'm dreading to find out :)

thorn0 commented 5 years ago

Not compiling, not checking type correctness, just parsing and resolving symbols. It still might be not very fast, but it also means that your existing code can be partially reused.

woutervh- commented 5 years ago

Good point @thorn0. I think it's worth investigating what TypeScript can do for us there. When I get some time to look into this I'll check it out.

woutervh- commented 5 years ago

I don't think this will happen in the foreseeable future. I will close this for house-keeping.

benwiley4000 commented 4 years ago

@woutervh- I'd love for this to happen. Could the issue be reopened? :) I'd maybe be able to contribute if I could get any pointers. I would suggest taking a look at the source code for the Babel typescript transform which inherits the TypeScript syntax plugin, i.e. it works on an AST which already understands TypeScript symbols. I think we'd be working from a good starting point to try to modify that transform.

As you can see in that file, most of the transform behavior seems to be configuring different TypeScript node types to be deleted.

Our typescript-is transform could run before, and obviously we'd remove the node deletion behavior and implement typescript-is instead.

I really don't know too much about how either TypeScript or Babel transforms work so again I might need some pointers for what to do. But given that the source for this transform, and the source for the babel plugin, both seem to be relatively short, I can't imagine it's an impossible task.

woutervh- commented 4 years ago

hi @benwiley4000

The transform function in typescript-is may seem short if you're only looking at the file src/transform-inline/transform-node.ts, but if you look at visitors that are invoked by the transformer (all the files in src/transform-inline starting with visitor), there is quite a lot of code there. All this code is tightly coupled with the TypeScript compiler and type checker API.

All this code will have to be rewritten using the babel API.

If you're up to the challenge: that's great :) I'll help out with the knowledge I have gained from this project. I guess the very first step would be to try to transform the TypeScript source code using only the simplest types and work from there. With that I mean: number, string, boolean. And with those simple types, try to return some simple boolean expression. If you can manage to transform the source code, using the babel compiler, to emit type guard functions that handle these simple cases, it would be a milestone to move on to more complex types.

is<number>(...);

Will need to be transformed into something like:

(obj => typeof obj === 'number')(...);

It would be kind of like the "hello world" of type guards in babel transformers :)