ttypescript icon indicating copy to clipboard operation
ttypescript copied to clipboard

Middleware support to allow arbitrary `ts.CompilerHost` changes

Open toriningen opened this issue 4 years ago • 8 comments

This PR adds an ability to intercept arguments of ts.createProgram in an extensible way — with mechanism resembling Express middlewares.

By hooking methods of ts.CompilerHost before they are passed further, it's possible to do things I find nice:

  • first class importing of non-TypeScript modules — e.g. for compiling GraphQL typedefs on the fly, or for using CSS files without bundling and import replacement.
  • virtual modules — because file system access is completely incapsulated by ts.CompilerHost, it's possible to add arbitrary resources that TypeScript compiler will see as files.
  • module resolution — enabling Yarn PnP without waiting for TypeScript team to agree on integration.
  • etc.

I didn't include any code for implementing these scenarios, as I believe it makes more sense to keep them separated from ttypescript, as I assume ts.CompilerHost interface might change. However, it's quite trivial to implement an ad-hoc ts.CompilerHost delegate with desired interface using this mechanism.

I've yet exposed only createProgram middleware, with an intent for future expansion, as this will allow to provide composable interface to arbitrary TypeScript APIs.

toriningen avatar Jun 22 '20 19:06 toriningen

Also, for convenience, until this PR is reviewed, merged and published, I'm hosting https://github.com/toriningen/ttypescript-only to enable installing of this forked package through NPM.

toriningen avatar Jun 22 '20 19:06 toriningen

I thought about this earlier, but in more wide context something like about more low-level plugin, which can combine many small transformers within one plugin. And program transformation runs through all plugins before file transformation stage.

And maybe in future also add macro transformation which can transform files before compiler starts check files

export default {
    transform: {
        program: (program, userconfig) => program,
        file: [(program, userconfig) => sourceFile => sourceFile],
        bundle: [(program, userconfig) => bundle => bundle],
        afterBuiltin: [(program, userconfig) => sourceFile => sourceFile]
        afterDTS: [(program, userconfig) => source => source],
    }
}

cevek avatar Jun 24 '20 13:06 cevek

What you are describing seems to be a more high-level take on the same idea. I'd say it relates to this PR as program transformer factory relates to raw factory. E.g. it's possible to implement transform with just raw, just transform is more convenient for those who don't need such level of control (and responsibility).

Similarly, what you describe can be an extension to middleware type. Welp, it could be even said that the whole transformation that is being done now in ts.createProgram hook imposed by ttypescript can be reworked as a middleware, but I thought it would be a way large change for a single PR.

toriningen avatar Jun 24 '20 15:06 toriningen

And maybe in future also add macro transformation which can transform files before compiler starts check files

I'm in need of something like this. I need to preprocess some files (simple regex replace) before they actually get parsed (otherwise they won't parse). From my cursory investigation it seems like I would do this by providing a custom CompilerHost with my own getSourceFile() implementation which would read the file, preprocess it, then pass it on to the compiler.

It seems like this PR would enable that, except that I still want to perform a before transformation, would this PR as-is require me to define them in separate plugins? Would there be some way for one (middleware) to pass data to the the other (before transformer)?

blaenk avatar Sep 23 '20 08:09 blaenk

I need a way to plug into diagnostics so that I can filter diagnostics for certain files. I can accomplish this via a language service plugin, but those are not run in tsc and tsc --watch.

Essentially, I also need a way to override getProgram, but not to add transformers, to filter the results of getSemanticDiagnostics and other diagnostics methods.

cspotcode avatar Feb 04 '21 22:02 cspotcode

@cspotcode ts-patch supports altering diagnostics. Not sure about your use case, but I believe it should work

nonara avatar Feb 05 '21 00:02 nonara

Thanks, I'll check it out. I'm trying to understand why ttypescript and ts-patch use different approaches to patching the compiler. It looks like ts-patch writes modifications to the filesystem, whereas ttypescript patches in-memory. Are there special considerations when using either tool with yarn2 PnP?

Are there any standards for writing a diagnostics filter that works for both CLI invocations and language service? For example, is there a language service plugin that will load the same set of diagnostics filtering plugins, so I can configure once and get the same filtering behavior everywhere?

cspotcode avatar Feb 05 '21 20:02 cspotcode

The main two reasons for ts-patch doing a filesystem patch are 1) reducing drag during compile time and 2) making configuration for tooling easier. Tooling is set by default to load from the typescript directory, so direct modification eliminates the various different configuration instructions needed for tts.

I haven't actually used or looked too deeply into yarn2 yet. It is possible to use ts-patch with yarn1, however—although they make it a little trickier because of cache rewriting. An example is in the crosstype repo.

Are there any standards for writing a diagnostics filter that works for both CLI invocations and language service

This would be great, but unfortunately not. This, the yarn issue, and the patching mechanism is something I've been thinking about for awhile now. I also saw the proposal to rename ttypescript.

Ultimately, I'd really like to rewrite ts-patch with some key differences. I decided to feel out interest here: https://github.com/cevek/ttypescript/issues/113

nonara avatar Feb 05 '21 23:02 nonara