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
feat: add ESM support #8536
feat: add ESM support #8536
Conversation
9b76f2a
to
eae1c03
Compare
I would like to get a confirmation if these changes aren't breaking. |
also @imnotjames maybe you want to review this one since I don't really have experience working with ESM yet. |
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
@pleerock I've changed the implementation to a more custom and safe approach to make sure it maintains compatibility with current usages in both commonjs and ESM projects. Previously you would have needed to do the following in ESM projects: import typeorm from "typeorm";
const {createConnection} = typeorm;
import type {Connection, Logger} from "typeorm"; After this PR you would be able to also do this instead: import {createConnection, Connection, Logger} from "typeorm"; The previous example will still continue to work |
I just converted my project to ESM and ran into an error: I am attempting to import with Would this solve this situation? |
@KevinNovak Yes, this is the purpose of this PR |
@giladgd Thank you, looking forward to this update! |
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
Thanks for working on the circular dependency fix too. I have entities that have a |
I compiled your branch and tested it against my ESM project.
Amazing work, thank you for this! |
@KevinNovak Thank you for sharing :) |
@@ -96,9 +103,9 @@ export class InitCommand implements yargs.CommandModule { | |||
// Protected Static Methods | |||
// ------------------------------------------------------------------------- | |||
|
|||
protected static executeCommand(command: string) { | |||
protected static executeCommand(command: string, cwd: string) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Previously, npm install
command failed when a new folder was created for the new project
docs/faq.md
Outdated
} | ||
``` | ||
|
||
Doing this prevents the type of the key from being saved in the transpiled code in the key metadata, preventing circular dependency imports. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can you please explain me what exact problem with the "key" stored in the transpiled code and what is called key? Is it "profile"?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's say we have these TS ESM files:
// Profile.ts
import {Entity, OneToOne} from "typeorm";
import User from "./User.js";
@Entity()
export default class Profile {
@OneToOne(() => User, user => user.profile)
user: User;
}
// User.ts
import {Entity, OneToOne} from "typeorm";
import Profile from "./Profile.js";
@Entity()
export default class User {
@OneToOne(() => Profile, profile => profile.user)
profile: Profile;
}
Their compiled code will look like this:
Profile.js
// Profile.js
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
import { Entity, OneToOne } from "typeorm";
import User from "./User.js";
let Profile = class Profile {
};
__decorate([
OneToOne(() => User, user => user.profile),
__metadata("design:type", User)
], Profile.prototype, "user", void 0);
Profile = __decorate([
Entity()
], Profile);
export default Profile;
//# sourceMappingURL=Profile.js.map
User.js
// User.js
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
import { Entity, OneToOne } from "typeorm";
import Profile from "./Profile.js";
let User = class User {
};
__decorate([
OneToOne(() => Profile, profile => profile.user),
__metadata("design:type", Profile)
], User.prototype, "profile", void 0);
User = __decorate([
Entity()
], User);
export default User;
//# sourceMappingURL=User.js.map
And when we run the code we will get this error:
file:///Users/user/Documents/workspace/project/dist/entity/User.js:17
__metadata("design:type", Profile)
^
ReferenceError: Cannot access 'Profile' before initialization
at file:///Users/user/Documents/workspace/project/dist/entity/User.js:17:31
at ModuleJob.run (node:internal/modules/esm/module_job:195:25)
at async Promise.all (index 0)
at async ESMLoader.import (node:internal/modules/esm/loader:331:24)
at async Command.<anonymous> (file:///Users/user/Documents/workspace/project/dist/index.js:29:34)
The issue is that the statement __metadata("design:type", Profile)
on User.js
access Profile
while the statement __metadata("design:type", User)
on Profile.js
access User
, thus creating a circular dependency between 2 files that require the other file to be already loaded in order to finish loading itself.
Since in these types of cases the metadata of the property type is redundant since the relation decorator already requires you to provide the type of the column (via () => Profile
in this example), removing the property type information will not damage any functionality.
This is a known issue with tsconfig.json
's emitDecoratorMetadata
, fixing it on the TypeScript level is very complicated so I gave up on that.
src/common/RelatedType.ts
Outdated
* | ||
* } | ||
*/ | ||
export type Related<T> = T; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
not sure I like this name. Maybe call it Relation
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
@pleerock Please let me know if I can help to merge this PR |
Thanks a lot for this contribution. Once I release a new version in couple days please make sure to check if both mjs and non-mjs commands are working properly in a production. Thank you! |
Hello, @giladgd! Thanks for your job! While I was looking for a solution to my ESM related problem, I came across this thread. I've looked at all the changes and it still doesn't seem to solve my problem. I'm afraid to make a mistake, but here's my situation. I'm using typeorm along with electron and quasar, which builds the entire project out of the box with webpack. In fact, my package.json does not and cannot contain the “module” item, but all my files work through ESM, and following the code, if I do not rename the files to mts, then it will still try to require, which will cause an error. I will be very happy to help, I will try to deploy the situation locally, but I do not know how long it may take, because I may not have enough experience yet. |
@igolka97 Do you have |
Wow, awesome! Is there a release date having this? @giladgd Is this PR compact with |
@KevinNovak unfortunately yes, I have already tried. Still getting import { Column, Entity, ManyToOne, PrimaryGeneratedColumn } from 'typeorm';
^^^^^^
SyntaxError: Cannot use import statement outside a module I'm still investigating this issue and I think webpack is to blame. |
@igolka97 I think webpack is not very ESM friendly. Have you try |
Hi @igolka97, This PR was merged but a new version of TypeORM hasn't been released yet, so please wait until a new version is released (probably in a few days) before trying to use TypeORM in an ESM project. Once the new version is released, you could run this command in order to generate a working ESM TS project to use as a starting point: npx typeorm init --name MyProject --database postgres --module esm |
@loynoir I using webpack since it using quasar bundler. Unfortunately this is payment for the convenience i get. |
@loynoir I havn't tried using it with esbuild, but I don't see a reason why shouldn't it work, as long as esbuild supports importing node modules |
Great that we have this 🎉 ; I just realized that TypeORM is quite the stumbling stone when I attempted to convert our project to ES Modules. Is there already a release date planned for the next version of TypeORM? |
we're using a prerelase version of typeorm where ESM support (typeorm/typeorm#8536) is merged
we're using a prerelase version of typeorm where ESM support (typeorm/typeorm#8536) is merged
we're using a prerelase version of typeorm where ESM support (typeorm/typeorm#8536) is merged
we're using a prerelase version of typeorm where ESM support (typeorm/typeorm#8536) is merged
@marcelgerber it was released. |
Thank you, it's great! |
I run into the error "ERR_LOADER_CHAIN_INCOMPLETE" when trying to run "npm start" on my ESM project with typeorm. Does anyone know how to fix it? |
Description of change
typeorm init
commandAdded
index.mjs
to the package, so ESM imports will work.index.mjs
exposes the rest of the project which still uses commonjs.Improved the implementation of dynamic file importing, the new implementation uses a function that determines the module system of the file and then uses
require
orimport
as necessary.Fixes #6974
Fixes #6941
Fixes: #7516
Fixes: #7159
Improves #8530
Pull-Request Checklist
master
branchnpm run lint
passes with this changenpm run test
passes with this changeFixes #0000