Transformer Plugin
@automapper/classes/transformer-plugin
is part of the public API of @automapper/classes
.
Problem
Decorating Classes' members with @AutoMap()
is verbose, even when we'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
How it works
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 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.ts
,.model.ts
,.vm.ts
, and.dto.ts
(this can be changed via transformer plugin options) - Iterate through all the members (
PropertyDeclaration
) of each class (ClassDeclaration
) that it finds - Store the members in a list that
@automapper/classes
can understand - Add to each class a
static method
and return the list.
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.
Limitations
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.
Usage
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
Ignore a property
The plugin will automatically construct the metadata for all properties that aren't decorated with @AutoMap
. However, there are also cases where you neither want @AutoMap
nor having the plugin processing a property (eg: you want to take the control 100% with manual configuration). To ignore a property completely, you can use a JSDoc tag @autoMapIgnore
on a property
class User {
firstName!: string;
lastName!: string;
profile!: Profile;
/**
* @autoMapIgnore
*/
ignoreMe!: string;
}
Options
export interface AutomapperTransformerPluginOptions {
modelFileNameSuffix?: string[];
}
modelFileNameSuffix
is default to ['.entity.ts', '.model.ts', '.vm.ts', '.dto.ts']
Webpack
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
};
Rollup
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
],
};
ttypescript
ttypescript
patches typescript
in order to use transformers in tsconfig.json
. See ttypescript's README for how to use this with module bundlers such as webpack or Rollup.
{
"compilerOptions": {
...,
"plugins": [
{
"transform": "@automapper/classes/transformer-plugin",
"modelFileNameSuffix": [...]
}
],
...
}
}
NestJS CLI
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"]
}
}
]
}
}
NestJS with Nx
Nx v12.8 adds support for TypeScript Compiler plugins via an option called tsPlugins
(now transformers
) in their @nrwl/node:webpack
executor, which is what @nrwl/nest
is using for building the application.
Read more about the usage here: Nx 12.8 Blog post
Pre 12.8
NestJS in Nx workspace utilizes nrwl/node:build
executor (formerly, builder) which allows you to pass in a custom Webpack config. However, to turn on Transformer, there's still this open issue in which you can find multiple solutions/workarounds as of the moment.
Angular
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