yeomanyeoman-generatormulti-layer

Efficient and simplest way to insert template based content into generated files with Yeoman generator


Suppose I already have some files generated by a generator and want to create some sub-generators that inserts contents into these files based on some content's template.

The goal is to create a generator of a multilayer architecture composed by 3 layers (for Angular2 app written in typescript):

For each layer, the main generator have to generate all files composing it: a module file, interfaces files, ... The main 3 files generated in this process looks like this:

hero.applicatif.ts:

import { Injectable } from '@angular/core';
import { IHeroApplicatif } from './hero.applicatif.interface';
import { HeroMetier } from '../metier/hero.metier';
@Injectable()
export class HeroApplicatif implements IHeroApplicatif {
    constructor(private heroMetier: HeroMetier) {}
}

hero.metier.ts:

import { Injectable } from '@angular/core';
import { IHeroMetier } from './hero.metier.interface';
import { HeroBusinessDelegate } from '../business-delegate/hero.business-delegate';
@Injectable()
export class HeroMetier implements IHeroMetier {
    constructor(private heroBusinessDelegate: HeroBusinessDelegate) {}
}

hero.business-delegate.ts:

import { Injectable } from '@angular/core';
import { Http, Headers } from '@angular/http';
import { IHeroBusinessDelegate } from './hero.business-delegate.interface';
@Injectable()
export class HeroBusinessDelegate implements IHeroBusinessDelegate {
    constructor(private http: Http) {}
}

Generating these files based on templates doesn't pose problem. But I want sub-generators that prompt the user to input a method name, it's return type and parameters so the sub-generator have to modify each previously generated files to inserts codes that, by default, for each layer, pass the call to the next layer.

Suppose the sub-generator have prompt the user to input a method called getHero, the contents of the 3 files have to be modified like this:

hero.applicatif.ts:

import { Injectable } from '@angular/core';
import { IHeroApplicatif } from './hero.applicatif.interface';
import { HeroMetier } from '../metier/hero.metier';
@Injectable()
export class HeroApplicatif implements IHeroApplicatif {
    constructor(private heroMetier: HeroMetier) {}
    getHero(id:number): Promise<any> {
        return this.heroMetier.getHero(id);
    }
}

hero.metier.ts:

import { Injectable } from '@angular/core';
import { IHeroMetier } from './hero.metier.interface';
import { HeroBusinessDelegate } from '../business-delegate/hero.business-delegate';
@Injectable()
export class HeroMetier implements IHeroMetier {
    constructor(private heroBusinessDelegate: HeroBusinessDelegate) {}
    getHero(id:number): Promise<any> {
        return this.heroBusinessDelegate.getHero(id);        
    }
}

hero.business-delegate.ts:

import { Injectable } from '@angular/core';
import { Http, Headers } from '@angular/http';
import { IHeroBusinessDelegate } from './hero.business-delegate.interface';
@Injectable()
export class HeroBusinessDelegate implements IHeroBusinessDelegate {
    constructor(private http: Http) {}
    getHero(id:number): Promise<any> {
        return this.http.get(...).toPromise();
    }
}

What is the simplest, safe, up to date way to do that?


Solution

  • To edit code files content in a safe way that won't mess up with un-predicted changes by the end user, the safest way is to modify a file Abstract Syntax Tree.

    There's multiple AST parser available for Node. The two most popular being Esprima and Acorn. There's also a few tools built on those parser to modify AST more easily.

    I wrote such a tool a while back if you want to check it out https://github.com/SBoudrias/AST-query - it might works for your use case.