Skip to content

[Feature] Support transformers written as ES modules #58

Closed

Description

@aomarks

It is not currently possible to import a transformer that is written a standard ES module. Only transformers written as CommonJS modules work currently.

Node has supported standard ES modules since v12, so that's all currently supported versions of Node: https://nodejs.org/en/about/releases/. Node packages are increasingly being published as ES modules instead of CommonJS, so it would be great to be able to use them here too! One huge advantage of ES modules is that they can run directly both in Node and browsers.

https://nodejs.org/api/esm.html#esm_introduction

Note this doesn't require writing ts-patch itself in ES modules, since there is bi-directional interoperability in Node. ​To import an ES module from a CommonJS module, use const mod = await import("./some-module.js") instead of require. To check if a module should be imported as an ES module, look in its package.json for the {"type": "module"} field.

I see a new version is being written at #40 -- I think this would be an awesome thing to include in the rewrite.

Thanks!

Activity

  1. nonara commented on Aug 21, 2021

    @nonara
    Owner

    Thanks for the report. ESModule support is definitely important. I'm not sure if I'm missing something, but I'm not sure how you could implement this into CJS code without dramatic rewrites.

    For example, the typescript compiler is a synchronous cjs file. In the case of tsc.js, you could probably make it work because you can pre-resolve the transformer files early (as you know exactly what tsconfig you're working with at execution, and you're only doing one). Even still, you'd still have to wrap the actual executable portion and only call it after you've resolved the promise for resolution.

    In the case of the API (ie. typescript.js), it becomes much more difficult. You'd need to convert createProgram to an async function, and thus modify everything that leads to it, and the whole chain that use those, etc. Am I misunderstanding something? Is there a way to synchronously import via expression?

    Edit:

    I think the simple answer here is that typescript, itself, doesn't support esm transformers. Rewiring it to do so on our end wouldn't be a viable or sustainable route to take.

    That said, however, our current support for .ts transformers is due to pre-transpiling. I think we could reasonably achieve esm support through the same means, by either:

    1. Detecting if transformer uses esm programmatically (via package.json if provided, or perhaps catch, if not)
    2. Adding a flag to indicate transformer is esm

    Then, if it is necessary, we simply transpile ahead of time, like we do for .ts transformers

    Again, I don't have a lot of knowledge around the actual esm spec or any recent developments thereof, so if there's anything I'm not seeing, I'd be glad to be wrong! From what I currently know, I think this may be the best means of achieving support.

  2. aomarks commented on Aug 22, 2021

    @aomarks
    Author

    Ah, interesting. It didn't occur to me that because you are patching TypeScript, it is not easy for you to add arbitrary asynchronous setup work. It is true that there is no synchronous dynamic import for ES modules. I can see that this would be quite hard to support!

    I'm coming over from ttypescript (since it seems a bit abandoned). I'm guessing it might be easier to do this in the model it uses, because it's got an alternate frontend -- maybe it could just do arbitrary work before invoking any TypeScript APIs (in this case waiting for all the modules specified in compilerOptions.plugins.transform to be imported)?

  3. nonara commented on Aug 22, 2021

    @nonara
    Owner

    We're essentially doing the same thing as ttypescript, with the main differences being that we add a few more features and ours is done once, where tts does it each time it's loaded.

    Both libraries patch ts.createProgram, which gets called from many places throughout the compiler. If we made that an async function, it would affect everything that calls it.

    Actually just supporting tsc would be relatively simple. As you mentioned, it could do the resolution work before executing the compilation. It's easy for us to add that in. The issue is that we also support the TypeScript compiler API, which can be called multiple times, with different configs. But as I mentioned, the real issue is that the typescript library (which natively handles transformers) doesn't support esm transformers.

    But, this is really just me thinking out loud. I don't think any of that is relevant to the ask. The right move is to add support further upstream, like we did to natively support .ts transformers.

    Have you tried using --loader ts-node/esm as a node parameter (or specifying in NODE_OPTIONS env)?

  4. changed the title [-]Support transformers written as ES modules[/-] [+][Feature] Support transformers written as ES modules[/+] on Jan 6, 2023
  5. clearfram3 commented on Jan 12, 2023

    @clearfram3

    Does TS 5.0 being mostly ESM now change this at all?

  6. nonara commented on Apr 20, 2023

    @nonara
    Owner

    This is now supported in v3

  7. added a commit that references this issue on Jun 13, 2023
    cd69c1c
  8. colecrouter commented on Aug 17, 2023

    @colecrouter

    I'm on v3.0.2, and I'm unable to get ESM working.

    plugins: "..." does not have an export "default": {}`.
    

    It seems when loadPlugin is called, it tries to require(transformerPath). However, require does not support ESM as far as I know. It's also worth noting that transformerPath is "" anyway, so require returns {}. transformerPath is empty because it's assigned using require.resolve.

    Unless I'm totally mistaken here, using import instead of require would fix the issue. However, that would require the use of async/await. I'm not sure about require.resolve, though, because using import.meta.resolve can only be called inside of a module, itself.

  9. nonara commented on Aug 18, 2023

    @nonara
    Owner

    @Mexican-Man Can you make a repro?

    Esm requires the esm package, but it should warn you of that. The plugin path should be relative file path to the plugin file.

    transformerPath should not be empty, as it's resolved from your file path in your plugin config entry in tsconfig. ESM wouldn't play a factor at that point.

    Sounds like your config had an issue somewhere

  10. colecrouter commented on Aug 18, 2023

    @colecrouter

    @nonara It's more than likely I'm messing something up. Here's a reproduction with instructions. Please let me know if you spot anything.

  11. zero1zero commented on Nov 21, 2024

    @zero1zero

    did you ever get to the bottom of this? I'm using ESM and continue to get:

    evalmachine.:385
    throw new tsp.TsPatchError(tsconfig.json > plugins: "${configTransformValue}" does not have an export "${importKey}": +
    ^
    TsPatchError: tsconfig.json > plugins: "test" does not have an export "default": {}

    Not sure how to get around it or where the issue is

  12. zero1zero commented on Nov 21, 2024

    @zero1zero

    Opened a new issue here: #172

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions