Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ESM support in cli #7159

Closed
1 of 21 tasks
zeakd opened this issue Dec 5, 2020 · 14 comments · Fixed by #8536
Closed
1 of 21 tasks

ESM support in cli #7159

zeakd opened this issue Dec 5, 2020 · 14 comments · Fixed by #8536

Comments

@zeakd
Copy link

zeakd commented Dec 5, 2020

Feature Description

The Problem

node 14 is now officially support ESM module, and ts-node is trying to support esm too.

https://nodejs.org/api/esm.html
TypeStrong/ts-node#1007

ESM module needs to set "type": "module" in package.json, but this option break typeorm cli because typeorm use require to import ts files

https://github.com/typeorm/typeorm/blob/master/src/connection/ConnectionOptionsReader.ts#L110
https://github.com/typeorm/typeorm/blob/master/src/util/DirectoryExportedClassesLoader.ts#L41

The Solution

use import() instead of require()
maybe package compiling environment needs to update.

Considered Alternatives

Additional Context

Relevant Database Driver(s)

  • aurora-data-api
  • aurora-data-api-pg
  • better-sqlite3
  • cockroachdb
  • cordova
  • expo
  • mongodb
  • mysql
  • nativescript
  • oracle
  • postgres
  • react-native
  • sap
  • sqlite
  • sqlite-abstract
  • sqljs
  • sqlserver

Are you willing to resolve this issue by submitting a Pull Request?

  • Yes, I have the time, and I know how to start.
  • Yes, I have the time, but I don't know how to start. I would need guidance.
  • No, I don't have the time, although I believe I could do it if I had the time...
  • No, I don't have the time and I wouldn't even know how to start.
@nebkat
Copy link
Contributor

nebkat commented Dec 5, 2020

All instances of require() could be replaced with a function in PlatformTools so that when the switch from require() to import() is made it is a single line change, or can be made version dependent. For now though we support all the way down to Node v10 so this change can't yet be made.

@cspotcode
Copy link

Here is how mocha supports both require() hooks and ESM hooks.
https://github.com/mochajs/mocha/blob/273dbbbf4c21debded604c2094591bdbcbed7705/lib/esm-utils.js#L11-L28

It's important for mocha to attempt require() first, in case there's a custom require.extensions hook installed. It detects a special node error and switches to import().

As far as I know, import() does not support require.extensions, so it's not true that you can use import() everywhere and get CommonJS support for free. You still need to attempt require().

Having said that, if you experiment and find out that I'm wrong, please let me know. Perhaps my information is out-of-date.

@zeakd
Copy link
Author

zeakd commented Dec 15, 2020

@cspotcode It looks good. can typeorm use this fallback in PlatformTools?

@cspotcode
Copy link

cspotcode commented Dec 15, 2020 via email

@cspotcode
Copy link

Disregard my last comment; I thought this was in a different issue.

Are you asking if typeorm can use the same technique as mocha? I think so yes. But I don't know anything about platformtools, so I don't know what it tries to do.

@zeakd
Copy link
Author

zeakd commented Dec 15, 2020

@cspotcode oh right sorry. i thought you are in typeorm

I'll try mocha way and maybe can make pr next week or new year

@glen-84
Copy link

glen-84 commented Feb 10, 2021

Here is how mocha supports both require() hooks and ESM hooks.
https://github.com/mochajs/mocha/blob/273dbbbf4c21debded604c2094591bdbcbed7705/lib/esm-utils.js#L11-L28

It's important for mocha to attempt require() first, in case there's a custom require.extensions hook installed. It detects a special node error and switches to import().

As far as I know, import() does not support require.extensions, so it's not true that you can use import() everywhere and get CommonJS support for free. You still need to attempt require().

Having said that, if you experiment and find out that I'm wrong, please let me know. Perhaps my information is out-of-date.

@cspotcode,

require.extensions has been deprecated since Node.js v0.10.6 – is it really necessary to support that?

I think you could just detect the package type and conditionally use import or require.

@cspotcode
Copy link

require.extensions is essentially the transformSource hook for CommonJS. It also affects resolution, so it's a little bit of the resolve hook too.

The age of its deprecation is brought up a lot. I understand why, but it's the only way to do certain things. If you want to do the equivalent of transformSource for CommonJS, you need to use require.extensions.

@glen-84
Copy link

glen-84 commented Feb 14, 2021

@cspotcode,

I see. What type of libraries/projects are using transformSource / how common is its use?

Perhaps the best option would be to use import by default, and have an opt-out for those who may rely on the behaviour of require.extensions.

@cspotcode
Copy link

To get a sense for all the different require.extensions module loaders out there:
https://www.npmjs.com/package/interpret
The list above is extensive, so it will include some loaders that do not make much sense and some that do.

The big ones are things like ts-node, @babel/register, @swc/register, stuff like that.

@redplant3d
Copy link

Can you at least include a flag to configure this easily?

@GerkinDev
Copy link

const configModule = await require(configFile);

For this one, a dynamic import can be AFAICT a drop-in replacement, since it is already in an async context and auto-support of cjs is induced by one of the possible extensions. It would worth to add support to .mjs extension though once migrating to ESM.
In one of my programs, I use ts-node to load ts files on the fly imported via dynamic imports. I don't remember where did I saw the piece of doc talking about this behavior but this is possible in cjs (not sure for ESM interop though).

const dirs = allFiles
.filter(file => {
const dtsExtension = file.substring(file.length - 5, file.length);
return formats.indexOf(PlatformTools.pathExtname(file)) !== -1 && dtsExtension !== ".d.ts";
})
.map(file => require(PlatformTools.pathResolve(file)));

This one is a bit trickier since this method is called in multiple places, some of them being in a sync context.

I think I'm gonna fork and try to tweak a bit.

@GerkinDev
Copy link

GerkinDev commented Aug 25, 2021

So, for what I've seen so far, there is some heavy tooling rework to do to make it happen. For instance, functional tests include ts files compiled as cjs to test cjs imports, so reworking tsconfig to build package as both esm & cjs would require some weird inclusion patterns matching those files. Or, other option, write them by hand as cjs/esm to avoid ts transpilation altogether.
Plus, mixing both esm & cjs is quite a pain with mocha.

I've bypassed the problem by modifying my build tool chains to use default imports and destructure them on the fly.

I somewhat doubt that the maintainers will accept easily the PR required to make it happen, since it would change a lot of things.

If a maintainer is passing by: have you tried anything about this ?

@imnotjames imnotjames self-assigned this Aug 28, 2021
giladgd added a commit to giladgd/typeorm that referenced this issue Jan 18, 2022
When TypeORM tries to load an entity file or a connection config file, it will determine what is the appropriate module system to use for the file and then `import` or `require` it as it sees fit.

Closes: typeorm#7516
Closes: typeorm#7159
@giladgd giladgd mentioned this issue Jan 18, 2022
7 tasks
@Ginden
Copy link
Collaborator

Ginden commented Jan 27, 2022

About:

type Relation<T> = T;

I would suggest using propertyName: T | null in general for relations - it's possible that relation wasn't loaded.

pleerock pushed a commit that referenced this issue Jan 31, 2022
* feat: support importing TypeORM in esm projects

Closes: #6974
Closes: #6941

* bugfix: generate index.mjs directly out of commonjs exports

The new implementation generates ESM exports directly out of the commonjs exports, and provides a default export to maintain compatability with existing `import`s of the commonjs implementation

* feat: support loading ESM entity and connection config files

When TypeORM tries to load an entity file or a connection config file, it will determine what is the appropriate module system to use for the file and then `import` or `require` it as it sees fit.

Closes: #7516
Closes: #7159

* fix: adapt ImportUtils.importOrRequireFile tests to older version of nodejs

* fix: improved importOrRequireFile implementation

* feat: add solution to circular dependency issue in ESM projects

* docs: added FAQ regarding ESM projects

* chore: add `"type": "commonjs"` to package.json

* style

* docs: improve `ts-node` usage examples for CLI commands in ESM projects

* feat: add support for generating an ESM base project

* refactor: renamed `Related` type to `Relation`

* docs: added a section in the Getting Started guide regarding the `Relation` wrapper type in ESM projects

* docs: improved documentation of the `Relation` type

* docs: improved documentation of the `Relation` type

* docs: added ESM support to the list of TypeORM features
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

7 participants