@automapper/classes/transformer-plugin
is part of the public API of @automapper/classes
.
Decorating Classes' members with @AutoMap()
is verbose, even when you're being meticulous about what members are being auto-configured vs custom-configured. In some other cases, the Classes themselves are being generated, and/or from external libraries that the consumers cannot touch.
@automapper/classes/transformer-plugin
is to ease this pain point when using @automapper/classes
Let's look at the following classes
class Profile {
bio: string;
age: number;
}
class User {
firstName: string;
lastName: string;
profile: Profile;
}
Throughout the documentation, we all know that the above code will be compiled to
class Profile {}
class User {}
The requirement for @automapper/classes
to work is to decorate all the members of both classes with @AutoMap
in order for @automapper/classes
to keep track of the metadata of each property on each class.
class Profile {
@AutoMap()
bio: string;
@AutoMap()
age: number;
}
class User {
@AutoMap()
firstName: string;
@AutoMap()
lastName: string;
@AutoMap(() => Profile)
profile: Profile;
}
This will get very verbose very soon.
@automapper/classes/transformer-plugin
runs a before
transformer that affects the AST directly before the Compilation step.
The transformer will
- Look at files that end with
.entity
,.model.ts
,.vm.ts
, and.dto.ts
(this can be changed via transformer plugin options) - Iterate through all the members (
PropertyDeclaration
andGetAccessorDeclaration
) of each class (ClassDeclaration
) that it finds - Store the members in a metadata list that
@automapper/classes
can understand - Add to each class a
static method
and return the metadata list that is later retrieved and stored on the Mapper.
Let's look at the above snippet again
// your code
class Profile {
bio: string;
age: number;
}
class User {
firstName: string;
lastName: string;
profile: Profile;
}
// after "before" transformer runs through your code
class Profile {
bio: string;
age: number;
static __AUTOMAPPER_METADATA_FACTORY__() {
return [
['bio', { type: () => String, depth: 1 }],
['age', { type: () => Number, depth: 1 }],
];
}
}
class User {
firstName: string;
lastName: string;
profile: Profile;
static __AUTOMAPPER_METADATA_FACTORY__() {
return [
['firstName', { type: () => String, depth: 1 }],
['lastName', { type: () => String, depth: 1 }],
['profile', { type: () => Profile, depth: 1 }],
];
}
}
After compilation, the members will be gone, but the static function will stay making it available to be called at runtime. Hence, the metadata will be available. @automapper/classes/transformer-plugin
only adds the minimum amount of code needed to keep track of the metadata.
@automapper/classes/transformer-plugin
utilizes TypeScript Compiler API to traverse and manipulate the Abstract Syntax Tree (AST) to automate the process of decorating the Classes. The Compiler API, while having been around for quite some time, is still marked as not yet stable.
Currently, @automapper/classes/transformer-plugin
will handle most Nullable
(type | null
) and Maybe
(propKey?: type
) cases. However, for complex cases where you have unions with different types (string | number | boolean
or ClassA | ClassB
), please consider decorate the property (field) manually with @AutoMap()
decorator or use forMember()
to provide a more comprehensive mapping configuration.
Matrices (Array of Array type. Eg: string[][]
) should also be handled manually.
As mentioned above, this is utilizing an experimental feature of TypeScript. Hence, you need to modify the build step of your project to use @automapper/classes/transformer-plugin
export interface AutomapperTransformerPluginOptions {
modelFileNameSuffix?: string[];
}
modelFileNameSuffix
is default to ['.entity.ts', '.model.ts', '.vm.ts', '.dto.ts']
If you use ts-loader
or some fork of ts-loader
, you can configure Webpack config to turn on Transformers
// snip
const automapperTransformerPlugin = require('@automapper/classes/transformer-plugin');
const pluginOptions = {
modelFileNameSuffix: [
/*...*/
],
};
module.exports = {
// snip
module: {
rules: [
// snip
{
test: /\.tsx?$/,
loader: 'ts-loader',
options: {
getCustomTransformers: (program) => ({
before: [
automapperTransformerPlugin(program, pluginOptions)
.before,
],
}),
},
},
// snip
],
},
// snip
};
Use rollup-plugin-typescript2
as it has transformers
capability
import automapperTransformerPlugin from '@automapper/classes/transformer-plugin';
import typescript from 'rollup-plugin-typescript2';
const pluginOptions = {
modelFileNameSuffix: [
/*...*/
],
};
export default {
// snip
preserveModules: true, // <-- turn on preserveModules
plugins: [
// snip
typescript({
transformers: [
(service) => ({
before: [
automapperTransformerPlugin(
service.getProgram(),
pluginOptions
).before,
],
}),
],
}),
// snip
],
};
ts-patch patches typescript in order to use transformers in tsconfig.json. See ts-patch's README for how to use this with module bundlers such as webpack or Rollup.
{
"compilerOptions": {
...,
"plugins": [
{
"transform": "@automapper/classes/transformer-plugin",
"import": "tspBefore",
"modelFileNameSuffix": [...]
}
],
...
}
}
nestjs/cli
can enable Transformers by default. To use this plugin with nestjs/cli
, modify your nest-cli.json
{
"collection": "@nestjs/schematics",
"sourceRoot": "src",
"compilerOptions": {
"plugins": ["@automapper/classes/transformer-plugin"]
}
}
or with options
{
"collection": "@nestjs/schematics",
"sourceRoot": "src",
"compilerOptions": {
"plugins": [
{
"name": "@automapper/classes/transformer-plugin",
"options": {
"modelFileNameSuffix": [".dto.ts", ".vm.ts"]
}
}
]
}
}
In Nx workspaces, NestJS applications and buildable libraries are handled with nrwl/node:webpack
and nrwl/js:tsc
executors respectively. Both of these executors have an option called transformers
that you can utilize to provide the plugin.
{
"app": {
"targets": {
"build": {
"executor": "@nrwl/node:webpack",
"options": {
"transformers": [
{
"name": "@automapper/classes/transformer-plugin",
"options": {
"modelFileNameSuffix": [".dto.ts"]
}
}
]
}
}
}
},
"lib": {
"targets": {
"build": {
"executor": "@nrwl/js:tsc",
"options": {
"transformers": [
{
"name": "@automapper/classes/transformer-plugin",
"options": {
"modelFileNameSuffix": [".dto.ts"]
}
}
]
}
}
}
}
}
Angular CLI has sophisticated build process so please take a look at this article and related articles mentioned to come up with your approach of turning on Transformers for Angular projects