babel-plugin-transform-metadata
Strict, optimized and smart reflection metadata generator for classes and functions from flowtype metadata.
- Supports arrows and function expressions
- Metadata provided for array and object-style arguments
- Generics and type arguments support
Examples
Interface as value
Flowtype and typescript reflection does not support type annotations as value keys, so we use some trick with typecast.
In:
import _ from 'babel-plugin-transform-metadata/_'
class A {}
export interface C {
a: A;
}
class MyClass {
constructor(c: C) {}
}
const id = (_: C)
Out:
'use strict';
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
var A = function A() {
_classCallCheck(this, A);
};
var MyClass = function MyClass(c) {
_classCallCheck(this, MyClass);
};
MyClass.displayName = 'MyClass';
MyClass._r = [0, ['C']];
var id = 'C';
Mark function
In:
function fn(a: A) {
function fn2(a: A) {
}
}
Out:
"use strict";
function fn(a) {
function fn2(a) {}
}
fn.displayName = "fn";
fn._r = [1, [A]];
Components metadata
In:
class A {}
interface State {s: A}
function ComponentD(rec: {p: number}, state: State) {
return <div>AA</div>
}
Out:
function A() {}
function ComponentD(rec, state, createVNode) {
return createVNode(2, 'div', null, 'AA');
}
ComponentD.displayName = 'ComponentD';
ComponentD._r1 = [{
s: A
}];
Type parameters
In example below, ISource and IStatus to ids mappings are configured in .babelrc.
Only first type parameter used.
In:
type ResultOf<F> = _ResultOf<*, F>
type _ResultOf<V, F: (...x: any[]) => V> = V
function fn(a: A, b: Class<B>, f: ResultOf<typeof factory>, sA: ISource<A>, saf: IStatus<A | B>) {
function fn2(a: A) {
}
}
Out:
'use strict';
function fn(a, b, f, sA, saf) {
function fn2(a) {}
}
fn.displayName = 'fn';
fn._r3 = 'babel-plugin-transform-metadata/src/__tests__/data/AllFeatures.js';
fn._r2 = 2;
fn._r1 = [A, B, factory, {
_r4: 1,
v: [A]
}, {
_r4: 2,
v: [A, B]
}];
For more examples see ./src/__tests__/data
Metadata
To each function plugin adds following metadata:
type IArg = IFunction | {
_r4: number;
v: IFunction[];
}
interface IFunction {
(...args: any[]): any;
_r1?: IArg[];
_r2?: number;
_r3?: string;
}
.babelrc options
Add before babel-plugin-transform-decorators-legacy and other transformation plugins.
interface IOptions {
onlyExports?: boolean;
addFileName?: boolean;
addDisplayName?: boolean;
typeNameStrategy?: 'typeName' | 'fullPath';
jsxPragma?: string;
markGenerics?: {[id: string]: number};
}
Example .babelrc:
{
"plugins": [
"syntax-flow",
"transform-decorators-legacy",
["transform-metadata", {
"addFilename": true,
"onlyExports": false,
"markGenerics": {"ISource": 1, "IStatus": 2},
"typeNameStrategy": "typeName",
"jsxPragma": "createVNode"
}]
]
}
Restrictions
For interface-based metadata we need to convert types to unique string tokens, something like this:
import type {T} from './types'
function test(t: T) {}
Reflection.defineMetadata(['T.types'], T)
JS module import subsystem is poor and nothing is doing in ES standarts for improving it. It's no clean way to identify imported interface in babel plugin, if import path is relative:
Types are same, but import paths is different:
import type {T} from './types'
import type {T} from '../data/types'
Ideally, set "typeNameStrategy": "fullPath" and always use absolute path for types via name_mapper in .flowconfig:
module.name_mapper='^babel-plugin-transform-metadata/i/\(.*\)' -> '<PROJECT_ROOT>/i/\1'
import type {T} from 'babel-plugin-transform-metadata/i/types'
import type {R} from './internalTypes'
function test(t: T, r: R) {}
Reflection.defineMetadata(['T.crc1', 'R.crc2'], test)
Relative paths supported, but some collisions possible, if types with equal names are defined in different files with equal names:
import type {T} from '../t2/internalTypes'
import type {T} from '../t1/internalTypes'
If "typeNameStrategy" is "fullPath", types always will be placed in separate files to avoid collisions like this:
export type T = {
some: string;
}
function test(t: T) {}
Reflection.defineMetadata(['T'], test)
import type {T} from './t2'
function test2(t: T) {}
Reflection.defineMetadata(['T.t2'], test2)
If "typeNameStrategy" is "typeName", import paths will be ignored. But possible collisions with equal type names in different files.
Credits
babel-plugin-angular2-annotations