kogosoftwarellc / open-api

A Monorepo of various packages to power OpenAPI in node
MIT License
892 stars 235 forks source link

[TypeScript] Unknown file extension ".ts" #824

Closed Jule- closed 2 years ago

Jule- commented 2 years ago

I have issues running express-openapi in a basic TypeScript project run through ts-node this way: node -r ts-node/register, this happens when the framework is trying to import my paths .ts files on this line: https://github.com/kogosoftwarellc/open-api/blob/9e8204657c656410aeafff9249834f66af6f97c3/packages/openapi-framework/index.ts#L238

TypeError [ERR_UNKNOWN_FILE_EXTENSION]: Unknown file extension ".ts"

Worth mentioning, I am ~using~ trying to use OpenAPI 3.0.2 (a lot of issues with the current JSON schema used here but this is another topic) and run Node 16.

I have made a CodeSandbox reproducing this issue:

Step to reproduce:

  1. Go to ~https://codesandbox.io/s/express-openapi-typescript-4w91hl~

    EDIT: New sandbox with less noise and auto-reload for easier debug (you can skip step 2.): https://codesandbox.io/s/express-openapi-ts-node-we8kqn

  2. ~Restart Server (Left Menu: Server Control Panel > Control Container > Restart Server)~
  3. Read the output in the default terminal

And even while trying to workaround while building for production, I cannot access any endpoint nor api-docs:

Step to reproduce:

  1. Go to https://codesandbox.io/s/express-openapi-typescript-4w91hl
  2. Start new Terminal with the "+" button
  3. Run npm run build (or npm run build:watch if you want to play around)
  4. Start new Terminal
  5. Run npm run start:prod
  6. Use the browser to navigate to:
    • / => OK - {"foo": "bar"} (Express parent App is working)
    • /api/healthcheck => OK - 200 (Express Router is working)
    • /api/api-docs => KO (/api-docs is not served)
    • /api/test => OK - {"path": "test"} (Express child App is working)
    • /api/users/xyz => KO (/users/{id} is not served)

Can someone help me figure out what I should do to make this work? :pray: Or maybe someone has a current working example of TypeScript project using this lib?

Thanks in advance for your insights!

Jule- commented 2 years ago

Relevant issue but without founding a viable solution to my issue: https://github.com/TypeStrong/ts-node/issues/1062

Jule- commented 2 years ago

@cspotcode if you can have a look at this MRE code maybe you could enlighten some basic ts-node config issues or incompatibility/misuse of await import("/path/to/file.ts") with ts-node? Many thanks! :pray:

Jule- commented 2 years ago

Updated CodeSandbox with less noise and nodemon to play around with configs more easily: https://codesandbox.io/s/express-openapi-ts-node-we8kqn

cspotcode commented 2 years ago

Sounds like you need to use our ESM loader. Be warned: this may require learning new things about node. You may be surprised that node's ESM support requires breaking changes to your project. These problems are imposed by node, not by ts-node. Be ready to read some node docs, and be ready for surprises.

First, the easy way: can you run ts-node-esm instead of node -r?

Jule- commented 2 years ago

@cspotcode Thanks for the quick response! :slightly_smiling_face: I can give it a try but in fact, I do not want to use ESM at all.

Do you mean that this project requires ESM loader? I have already played a bit with ESM modules and know some of the implications, but this project is not an ESM module and that is what is making me confused: https://github.com/kogosoftwarellc/open-api/blob/9e8204657c656410aeafff9249834f66af6f97c3/packages/express-openapi/package.json#L7

cspotcode commented 2 years ago

This seems to be the problem:

this happens when the framework is trying to import my paths .ts files on this line:

const imported = await import(`file://${fsRoutesItem.path}`); 

The framework uses import() and gives it a path to a TS file, sorta like this: import('./path/to/ts/file.ts')

Even when the file being loaded is a CommonJS file, import() goes through node's internal ESM codepaths. Those codepaths can load CJS files, so it's not changing the interpretation of your code from CJS to ESM. That is not the problem: your code is still CJS.

However, node's ESM codepaths do not understand new file extensions. The only way to teach it about new file extensions, is to use ts-node's ESM loader. That is the only way to plug into node's internal ESM stuff.

This requires us to pass the --loader CLI flag to node, which means spawning a subprocess. Node's loaders API is also marked experimental (you can read about the definition of "experimental" in node's documentation)

For these reasons, we do not force projects to use an experimental API; you have to opt-in. ts-node-esm will opt-in. It's an alias for ts-node --esm, and you can also specify "esm": true in your tsconfig.

Jule- commented 2 years ago

@cspotcode :exploding_head: it sounds that ts-node-esm is working in the CodeSandbox! Nice bit of :magic_wand:. ~I think I am more confused.~

EDIT: Just seen your comment... Thank you so much for the explanation! :pray:

cspotcode commented 2 years ago

Excellent, happy to help. My time is limited so if you have any future questions, I recommend asking on the TypeScript Community Discord server. The server has a great help system with plenty of experts willing to offer advice.

Jule- commented 2 years ago

I understand. :pray: And I'll do, thanks for pointing me to this one! At least this explanation could enlighten other fellow devs searching on the web!