leonardfactory / babel-plugin-transform-typescript-metadata Goto Github PK
View Code? Open in Web Editor NEWBabel plugin to emit decorator metadata like typescript compiler
License: MIT License
Babel plugin to emit decorator metadata like typescript compiler
License: MIT License
Disclaimer: I am not entirely sure I understand everything that is happening here, but I'll try my best to make sense.
I am using Typeorm while compiling Typescript with Babel 7, so I need experimentalDecorators (with @babel/plugin-proposal-decorators) and emitDecoratorMetadata (with this package).
Everything runs fine, but when I run the output code, I get the following error: Data type "undefined" in "CharacterEntity.gender" is not supported by "mysql" database.
, which refer to:
// CharacterEntity.ts
@Entity('characters')
export class CharacterEntity {
// ...
@Column()
gender: CharacterGender;
// ...
}
// CharacterGender.ts
export enum CharacterGender {
MALE = 'male',
FEMALE = 'female'
}
When looking at the code procuded by Babel, I believe only these lines are relevant:
_dec9 = (0, _typeorm.Column)(), _dec10 = Reflect.metadata("design:type", typeof _enums.CharacterGender === "undefined" ? Object : _enums.CharacterGender)
// ...
_descriptor4 = _applyDecoratedDescriptor(_class2.prototype, "gender", [_dec9, _dec10], {
configurable: true,
enumerable: true,
writable: true,
initializer: null
})
// ...
_initializerDefineProperty(this, "gender", _descriptor4, this);
So this issue is blocking me. Note that everything compiles and runs fine when using the Typescript compiler, which seems to produce:
__decorate([
typeorm_1.Column(),
__metadata("design:type", String)
], CharacterEntity.prototype, "gender", void 0);
I hope this is clear enough to provide enough information, it would be great if you could look into this or simply explain what I'm doing wrong :)
Thanks !
Issue
It seems like the generated code through this Babel plugin raises an issue in using a variable before it is initialized.
let User = ...
_dec9 = Reflect.metadata("design:type", typeof Profile === "undefined" ? Object : Profile)
...
let Profile = ...
_dec9 = Reflect.metadata("design:type", typeof User === "undefined" ? Object : User)
I did see the same issue reported by someone here - https://stackoverflow.com/questions/61297622/typescript-metadata-reflection-references-other-classes-before-they-are-defined, but I don't think it's closed with a solution to the core problem.
Context
I'm using TypeORM for my interaction with a database. And in this example I defined two entities that need each other (a one-to-one relationship)
I provide a minimal reproduction code in this repo - https://github.com/kelvien/repro-next-circ-metadata/
It seems like you've mentioned this as one of the pitfalls. I'm just wondering if there is an alternate way in solving this issue, and perhaps a longer term solution to this.
Despite installing this plugin, I think I still have the same problem. Help is appreciated but honestly I want to give up.
ColumnTypeUndefinedError: Column type for Exercise#name is not defined and cannot be guessed. Make sure you have turned on an "emitDecoratorMetadata": true option in tsconfig.json. Also make sure you have imported "reflect-metadata" on top of the main entry file in your application (before any entity imported).If you are using JavaScript instead of TypeScript you must explicitly provide a column type.
Exercise.tsx
import {Entity,Column, PrimaryGeneratedColumn} from "typeorm/browser";
@Entity("Exercise")
export class Exercise{
@PrimaryGeneratedColumn()
id!:number;
@Column()
name!: Text;
@Column()
notes:Text;
@Column()
imageJSON:Text;
}
.babelrc
{
"plugins": [
"babel-plugin-transform-typescript-metadata",
["@babel/plugin-proposal-decorators", { "legacy": true }],
["@babel/plugin-proposal-class-properties", { "loose": true }],
],
"presets": [
"@babel/preset-typescript"
]
}
snippet of package.json
...
"devDependencies": {
"@babel/core": "^7.12.9",
"@types/node": "^18.0.0",
"@types/react": "~17.0.21",
"@types/react-native": "~0.66.13",
"babel-plugin-transform-typescript-metadata": "^0.2.2",
"typescript": "~4.3.5"
},
...
Actually the plugin supports only typescript TSTypeReference
AST nodes, but eventually it needs to support all annotations.
Like suggested in this awesome response on SO, we should encode the same logic used in the TypeScript compiler.
Hello,
I have an issue, when I define a type like this:
export type Uuid = string;
The emit type is:
Object
The full case:
export type Uuid = string;
function Decorate() {
return (target: any, prop: string): any => {
const propertyType = Reflect.getMetadata("design:type", target, prop);
console.log('propertyType', propertyType); // emit the Object type
}
}
class Foo {
@Decorate()
public bar!: Uuid;
}
and my babel config:
// babel.config.js
module.exports = {
presets: [
["@babel/preset-env", { "targets": { "node": "current" }, "modules": "commonjs" }],
"@babel/preset-typescript"
],
"plugins": [
"babel-plugin-transform-typescript-metadata",
["@babel/plugin-proposal-decorators", { "legacy": true }],
["@babel/proposal-class-properties", { "loose": true }],
"@babel/proposal-object-rest-spread"
]
};
I don't know if it's a defect coming from my Babel configuration, or a limitation from @babel/preset-typescript
.
I have an issue, when I import some type from package (linked or regular)
problem like: can't resolve "path/to/my/node_modules/module" or "my type" is not exported from 'path/to/my/module'.
Error:
[email protected] start /Users/edelgarat/Documents/projects/babel-ts-decorator-error
webpack[webpack-cli] Compilation finished
asset bundle.js 59 KiB [emitted] (name: main)
runtime modules 1.13 KiB 5 modules
cacheable modules 52 KiB
./src/index.ts 1.95 KiB [built] [code generated]
./node_modules/reflect-metadata/Reflect.js 50 KiB [built] [code generated]ERROR in ./src/index.ts 10:0-73
Module not found: Error: Can't resolve 'test-package/file-with-declaration' in '/Users/edelgarat/Documents/projects/babel-ts-decorator-error/src'webpack 5.6.0 compiled with 1 error in 631 ms
npm ERR! code ELIFECYCLE
npm ERR! errno 1
npm ERR! [email protected] start:webpack
npm ERR! Exit status 1
npm ERR!
npm ERR! Failed at the [email protected] start script.
npm ERR! This is probably not a problem with npm. There is likely additional logging output above.npm ERR! A complete log of this run can be found in:
npm ERR! /Users/edelgarat/.npm/_logs/2020-11-22T18_10_36_504Z-debug.log
Notes:
Entry point file:
import {SomeTypeDeclaration} from "test-package/file-with-declaration";
const someDecorator: PropertyDecorator = (target, propertyKey) => {}
class Test {
@someDecorator
someProperty: SomeTypeDeclaration = 5;
}
Error:
Module not found: Error: Can't resolve 'test-package/file-with-declaration' in '/Users/edelgarat/Documents/projects/babel-ts-decorator-error/src'
Webpack babel rule:
{
test: /\.ts$/,
use: {
loader: "babel-loader",
options: {
presets: [
["@babel/preset-typescript", {}]
],
plugins: [
["@babel/plugin-proposal-decorators", { "legacy": true }],
["@babel/plugin-proposal-class-properties", { "loose" : true }],
"babel-plugin-transform-typescript-metadata",
]
}
}
}
Demo project: https://github.com/edelgarat/babel-plugin-transform-typescript-metadata-import-error
Test stand:
sample:
@decorator()
get value(): string { ... }
set value(value:string) {}
Typescript annotates the getter/setter with design:type: String for the property "value", the plugin detects it as a method and only annotates design:paramtype if you are decorating the settter, if you are decorating the getter no type information are set.
Hey! I saw this code in the readme:
import { MyService } from './MyService';
import { Configuration } from './Configuration';
@Injectable()
class AnotherService {
@Inject()
config: Configuration;
constructor(private service: MyService) {}
}
I am not sure if this is a pseudo or real code with removed imports. The part I am interested about is property injection using decorators. I've been trying to get something similar to work in typescript
, babel 7
and InversifyJS
for the past few hours with no success. Apparently babel decorators implementation doesn't allow setting properties from decorator. Would you mind sharing some info? :)
PS. Thanks for this babel plugin btw!
Hey!
When using this plugin I found that webpack complains about missing exports due to types not being removed by babel when used in inversify decorators. Although everything works fine these warnings can be resolved by using type imports:
import { injectable, inject } from 'inversify';
import type { Service } from './Service';
import { ServiceIdentifier } from './ServiceIdentifier';
@injectable
export class OtherService {
constructor(
@inject(ServiceIdentifier) service: Service
) {}
}
Just a small suggestion to add this to README if you think it fits there as some people might want to get rid of the warnings but don't know how.
Hi Leonardo,
You mentioned in this stackoverflow discussion that you attempt prevent causing ReferenceErrors by using the typeof
operator, but I showed how they can occur anyway and you said "let me check because this seems like a bug". After much head-scratching, I figured out why this is the case.
let x = 8;
console.log(typeof x); // "number"
console.log(typeof y); // "undefined"
console.log(typeof z); // ReferenceError: cannot access variable z before initialization
let z = 9;
If you don't define something at all, the typeof operator will give you "undefined". But in my case the class was defined too late, and that for some reason causes a ReferenceError. I don't know what to use instead of typeof (maybe a try-catch?) but I just wanted to let you know about this weird language kludge and how you could improve your plugin.
When I transpile a simple class, which has a decorated parameter as such
class Compilee {
constructor(@SomeDecorator() param) { }
}
then tsc
will (with emitDecoratorMetadata
) also set __metadata("design:paramtypes", ...)
on the class itself.
This does not happen in this plugin yet.
For a reconstruction, see https://gist.github.com/wtho/0db9f9f4d224ae85d65ad9e54293abdb
Do you think this functionally could be added to this plugin?
I could give it a try, but it would probably create more coupling between the two visitors, I think you should decide how this happens.
I did the work but great if someone took it off my hands
https://github.com/jaredpalmer/razzle/tree/custom-typescript-preset/packages/babel-preset-typescript
I may be barking up the wrong tree but is the library intended to fix the issue where TypeORM will not seem to work with Next.JS.
The error I get is:
/Users/jelling/dev/pw/longboard/next-js/jon/entities/person.ts:1
import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm';
^
SyntaxError: Unexpected token {
I have worked with TypeORM extensively and typically this is because the code is expecting Javascript / the project needs to be run with ts-node. But this happens even when running npm run start
after running npm run build
and all of the TS should be JS at that point.
A zip of my project is attached.
This particular translation causes problems with esbuild and esbuild-based frameworks like vite.
esbuild does not support TypeScript's emitDecoratorMetadata
functionality, so we must use this brilliant Babel plugin to inject the missing metadata before running esbuild.
On the other hand, esbuild can handle TypeScript's parameter decorators perfectly well. Internally, esbuild does the same kind of translation as this Babel plugin + @babel/plugin-proposal-decorators
(in legacy mode), but implemented differently.
But if this Babel plugin translates parameter decorators to function expressions, and the result is fed to esbuild, then esbuild errors out.
Therefore, could you please add an option to disable the translation of parameter decorators?
Thanks!
Hi @leonardfactory,
I noticed that when I add this plugin to my plugin list it breaks the other transformations in the config. I get this error message when it's in there:
C:\Users\Robbie\Code\ginger\src\backend\resolvers\RecipeResolver.ts:1
(function (exports, require, module, __filename, __dirname) { import {
^
SyntaxError: Unexpected token {
This is the same error message I get if I do not use Babel at all (or remove the react-app
preset). If I don't use the plugin, metadata reflection doesn't work (and TypeORM gives me errors) but everything else does. My package.json babel config is this:
"babel": {
"plugins": [
"babel-plugin-transform-typescript-metadata",
"@babel/plugin-proposal-optional-chaining",
"@babel/plugin-proposal-nullish-coalescing-operator"
],
"presets": [
"react-app"
]
}
If you need more information, my project repository is here: https://github.com/RobbieGM/ginger
Thanks for your help!
I fear I cannot edit the .babelrc
file in CRA. :-(
Is there an example of how to use your plugin with babel standalone? Is it just the same as with babelrc? I get the following error if I try to use it:
Error: Invalid plugin specified in Babel options: "babel-plugin-transform-typescript-metadata"
I am creating an online code editor that and I wanted to use your plugin with babel to transpile (Angular) TypeScript but I can't seem to get Angular services working using @Injectable. Any help would be greatly appreciated
When I compile
@ClassDec()
export class Decoratee {
constructor(@ParamDec() param: string) {}
}
with tsc I end up with (I replaced some tslib
calls with what they will be replaced with):
let Decoratee = class Decoratee {
constructor(param) { }
};
Decoratee = tslib_1.__decorate([
ClassDec(),
ParamDec(),
Reflect.metadata("design:paramtypes", [String])
], TokenService);
tslib then will call these three decorators on Decoratee
, starting with the last one (Reflect.metadata
), then alls ParamDec, and finally
ClassDec`.
Using this plugin, I get something like this, with the order being differently:
var _dec, _dec2, _class;
var Decoratee = (
_dec = (0, ClassDec)(),
_dec2 = Reflect.metadata("design:paramtypes", [String]),
_dec(
_class = _dec2(
_class = function Decoratee(param) {}
) || _class
) || _class
);
(0, ParamDec)()(Decoratee, undefined, 0);
Compiling this with babel will in some cases have different results, if libraries/frameworks rely on a consistent order. I do not know, if the order is defined in the metadata proposal, did not find anything so far. I think you mentioned this issue also at the bottom of README.md
. Specifically in the Angular DI, the @Injectable
decorator expects all @Inject
s inside the constructor parameter to already have been applied.
While this plugin is getting us most of the way to working experimental decorators in typescript (thank you!) it doesn't seem to work if the module in tsconfig.json is set to esnext.
I am trying to get this working in a library that uses inversifyJS dependency injection, and export to both umd and es modules outputs. When compiling to commonjs modules it works fine, but with esnext modules, babel starts replacing the decorator functions' this property with undefined:
(!)
thishas been rewritten to
undefinedhttps://rollupjs.org/guide/en#error-this-is-undefined src\Book.js 3: import { typeof as _typeof } from "\0rollupPluginBabelHelpers.js"; 4: 5: var __decorate = this && this.__decorate || function (decorators, target, key, desc) {
The last line above changes to the following after babel finishes:
var __decorate = undefined && undefined.__decorate || function (decorators, target, key, desc) {
which unfortunately breaks everything.
Create react app support babel macros but doesn't support babel plugins out of the box.
Hello guys, I'd like to start saying a big thank you to everyone, for your support and using this project. Given that, and having this project in production, I've started to see some issues between the TS world and the babel world (even small ones), and I'd like to give this project a direction based on real needs, current usage, etc.
Before proceeding with my own thoughts however, I'd like to ask you to tell me some info about your usage, especially:
babel
/ babel-loader
with this plugin instead of pure tsc
/ ts-loader
?I know your time is precious, and I'll understand if you don't find a moment to answer, but I think this will be precious feedback for all the community, not only for this project.
I'd like to see if there is something else I/we can do, and having the bigger picture could allow us to make our job easier, faster and more precise.
Thank you ๐ ,
Leonardo
Been using type-graphql and came across this issue
Here is a sample class which returns a void type for the argument
class Demo {
@Method()
test(@Args(): { destructedProp } : ClassType) {}
}
This sample works
class Demo {
@Method()
test(@Args(): arg : ClassType) {}
}
It seems the param.type
here https://github.com/leonardfactory/babel-plugin-transform-typescript-metadata/blob/master/src/metadata/serializeType.ts#L25 is ObjectPattern
and this results in returning voidZero()
const decorator = (): PropertyDecorator => {
return (target: object, propertyKey: string | symbol) => {
console.log(Reflect.getMetadataKeys(target, propertyKey));
};
};
class Testing2 {
@decorator()
test(): string {
return 'hello';
}
}
using tsc, it would print [ 'design:returntype', 'design:paramtypes', 'design:type' ]
using babel with this plugin, it would print [ 'design:paramtypes' ]
@leonardfactory @wtho
When working with Nestjs and aws sdk, any injections of AWS sdk are not being tranformed properly. I think the issue is somewhere in here. when the condition expression fails for typeof Type === 'undefined' ? Object : Type
it falls back to default type which for example is AWS.S3
and later js has no idea of how to resolve AWS
.
reproduction of the issue is here. The original issue was filed in babel repro as #12150
Happy to work on this, but will need more guidence on what actually needs to be fixed.
type-graphql is a popular TS library for GraphQL. They plan on writing a TS compiler extension to DRY up type info in field definitions.
How hard would it be to support this? Do you have time? Could you provide some pointers on the difficulties and where to begin?
There seems to be some issues around transforming property decorators that has Enum as a type.
Here is what is different:
Given
export function ExampleDecorator(): PropertyDecorator {
return (target, propertyKey): void => {
// trying to get type like this
const type = Reflect.getMetadata('design:type', target, propertyKey);
console.log(type)
};
}
enum DEMO {
PROP_1 = '1',
PROP_2 = 2
}
class A {
@ExampleDecorator()
stringProp: string
@ExampleDecorator()
demoProp: DEMO
}
Here is what is printed to stdout
[Function: String]
{ '2': 'PROP_2', PROP_1: '1', PROP_2: 2 } // not sure why the type of an enum is converted to this
However, running the same code through tsc
, this is what is returned (and I believe is correct)
[Function: String]
[Function: Object]
Reproduction repo:
https://github.com/whimzyLive/incorrect-enum-transform
@leonardfactory do you know something on what might be casuing this difference.
Any chance of supporting the new decorators proposal - i.e. not requiring the legacy
flag for @babel/plugin-proposal-decorators
?
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.