Skip to content

Commit

Permalink
feat: ESM support (#8536)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
giladgd committed Jan 31, 2022
1 parent 26581d0 commit 3a694dd
Show file tree
Hide file tree
Showing 20 changed files with 489 additions and 83 deletions.
38 changes: 38 additions & 0 deletions README.md
Expand Up @@ -71,6 +71,7 @@ TypeORM is highly influenced by other ORMs, such as [Hibernate](http://hibernate
* Supports MongoDB NoSQL database.
* Works in NodeJS / Browser / Ionic / Cordova / React Native / NativeScript / Expo / Electron platforms.
* TypeScript and JavaScript support.
* ESM and CommonJS support.
* Produced code is performant, flexible, clean and maintainable.
* Follows all possible best practices.
* CLI.
Expand Down Expand Up @@ -321,6 +322,9 @@ That's it, your application should successfully run and insert a new user into t
You can continue to work with this project and integrate other modules you need and start
creating more entities.

> You can generate an ESM project by running
`typeorm init --name MyProject --database postgres --module esm` command.

> You can generate an even more advanced project with express installed by running
`typeorm init --name MyProject --database mysql --express` command.

Expand Down Expand Up @@ -950,6 +954,40 @@ Note that we should use the `@JoinColumn` decorator only on one side of a relati
Whichever side you put this decorator on will be the owning side of the relationship.
The owning side of a relationship contains a column with a foreign key in the database.

### Relations in ESM projects

If you use ESM in your TypeScript project, you should use the `Relation` wrapper type in relation properties to avoid circular dependency issues.
Let's modify our entities:

```typescript
import { Entity, Column, PrimaryGeneratedColumn, OneToOne, JoinColumn, Relation } from "typeorm";
import { Photo } from "./Photo";

@Entity()
export class PhotoMetadata {

/* ... other columns */

@OneToOne(type => Photo, photo => photo.metadata)
@JoinColumn()
photo: Relation<Photo>;
}
```

```typescript
import { Entity, Column, PrimaryGeneratedColumn, OneToOne, Relation } from "typeorm";
import { PhotoMetadata } from "./PhotoMetadata";

@Entity()
export class Photo {

/* ... other columns */

@OneToOne(type => PhotoMetadata, photoMetadata => photoMetadata.photo)
metadata: Relation<PhotoMetadata>;
}
```

### Loading objects with their relations

Now let's load our photo and its photo metadata in a single query.
Expand Down
29 changes: 29 additions & 0 deletions docs/faq.md
Expand Up @@ -9,6 +9,7 @@
* [How to handle outDir TypeScript compiler option?](#how-to-handle-outdir-typescript-compiler-option)
* [How to use TypeORM with ts-node?](#how-to-use-typeorm-with-ts-node)
* [How to use Webpack for the backend](#how-to-use-webpack-for-the-backend)
* [How to use TypeORM in ESM projects?](#how-to-use-typeorm-in-esm-projects)


## How do I update a database schema?
Expand Down Expand Up @@ -182,6 +183,12 @@ Also, if you want to use the ts-node CLI, you can execute TypeORM the following
ts-node ./node_modules/.bin/typeorm schema:sync
```

For ESM projects use this instead:

```
node --loader ts-node/esm ./node_modules/.bin/typeorm schema:sync
```

## How to use Webpack for the backend?

Webpack produces warnings due to what it views as missing require statements -- require statements for all drivers supported by TypeORM. To suppress these warnings for unused drivers, you will need to edit your webpack config file.
Expand Down Expand Up @@ -277,3 +284,25 @@ module.exports = {
],
};
```

## How to use TypeORM in ESM projects?

Make sure to add `"type": "module"` in the `package.json` of your project so TypeORM will know to use `import( ... )` on files.

To avoid circular dependency import issues use the `Related` wrapper type for relation type definitions in entities:

```typescript
@Entity()
export class User {

@OneToOne(() => Profile, profile => profile.user)
profile: Relation<Profile>;

}
```

Doing this prevents the type of the property from being saved in the transpiled code in the property metadata, preventing circular dependency issues.

Since the type of the column is already defined using the `@OneToOne` decorator, there's no use of the additional type metadata saved by TypeScript.

> Important: Do not use `Related` on non-relation column types
5 changes: 5 additions & 0 deletions docs/migrations.md
Expand Up @@ -151,6 +151,11 @@ Example with `ts-node`:
ts-node --transpile-only ./node_modules/typeorm/cli.js migration:run
```

Example with `ts-node` in ESM projects:
```
node --loader ts-node/esm ./node_modules/typeorm/cli.js migration:run
```

Example `ts-node` not using `node_modules` directly:
```
ts-node $(yarn bin typeorm) migration:run
Expand Down
18 changes: 16 additions & 2 deletions docs/using-cli.md
Expand Up @@ -28,9 +28,9 @@ This CLI tool is written in javascript and to be run on node. If your entity fil

You may setup ts-node in your project to ease the operation as follows:

Install ts-node globally:
Install ts-node:
```
npm install -g ts-node
npm install ts-node --save-dev
```

Add typeorm command under scripts section in package.json
Expand All @@ -41,6 +41,14 @@ Add typeorm command under scripts section in package.json
}
```

For ESM projects add this instead:
```
"scripts": {
...
"typeorm": "node --loader ts-node/esm ./node_modules/typeorm/cli.js"
}
```

If you want to load more modules like [module-alias](https://github.com/ilearnio/module-alias) you can add more `--require my-module-supporting-register`

Then you may run the command like this:
Expand Down Expand Up @@ -91,6 +99,12 @@ To specify a specific database you use you can use `--database`:
typeorm init --database mssql
```

To generate an ESM base project you can use `--module esm`:

```
typeorm init --name my-project --module esm
```

You can also generate a base project with Express:

```
Expand Down
20 changes: 20 additions & 0 deletions gulpfile.ts
Expand Up @@ -4,6 +4,7 @@

import {Gulpclass, Task, SequenceTask, MergedTask} from "gulpclass";

const fs = require("fs");
const gulp = require("gulp");
const del = require("del");
const shell = require("gulp-shell");
Expand Down Expand Up @@ -170,6 +171,24 @@ export class Gulpfile {
.pipe(gulp.dest("./build/package"));
}

/**
* Create ESM index file in the final package directory.
*/
@Task()
async packageCreateEsmIndex() {
const buildDir = "./build/package";
const cjsIndex = require(`${buildDir}/index.js`);
const cjsKeys = Object.keys(cjsIndex).filter(key => key !== "default" && !key.startsWith("__"));

const indexMjsContent =
'import TypeORM from "./index.js";\n' +
`const {\n ${cjsKeys.join(",\n ")}\n} = TypeORM;\n` +
`export {\n ${cjsKeys.join(",\n ")}\n};\n` +
'export default TypeORM;\n';

fs.writeFileSync(`${buildDir}/index.mjs`, indexMjsContent, "utf8");
}

/**
* Removes /// <reference from compiled sources.
*/
Expand Down Expand Up @@ -230,6 +249,7 @@ export class Gulpfile {
["browserCopySources", "browserCopyTemplates"],
["packageCompile", "browserCompile"],
"packageMoveCompiledFiles",
"packageCreateEsmIndex",
[
"browserClearPackageDirectory",
"packageClearPackageDirectory",
Expand Down
10 changes: 10 additions & 0 deletions package.json
Expand Up @@ -9,7 +9,17 @@
"name": "Umed Khudoiberdiev",
"email": "pleerock.me@gmail.com"
},
"exports": {
".": {
"import": "./index.mjs",
"require": "./index.js"
},
"./*": "./*"
},
"main": "./index.js",
"module": "./index.mjs",
"types": "./index.d.ts",
"type": "commonjs",
"browser": {
"./browser/driver/aurora-data-api/AuroraDataApiDriver.js": "./browser/platform/BrowserDisabledDriversDummy.js",
"./browser/driver/cockroachdb/CockroachDriver.js": "./browser/platform/BrowserDisabledDriversDummy.js",
Expand Down

0 comments on commit 3a694dd

Please sign in to comment.