Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How not to include mock services in Angular2 production bundle

When building an angular 2 app for production we use

ng build --prod --w --aot 

But our mock services also get bundled and minified probably because we have

import {XMockService} from "./xxx-mock.service";

and that prevents the tree shaking from dropping the unused service. Here is our simplified app.module.ts which conditionally lazy-loads the mock service when environment.mock=true

I would imagine this is a common scenario but I can't find any answers for it.

app.module.ts:

import {BrowserModule} from "@angular/platform-browser";
import {NgModule} from "@angular/core";
import {FormsModule} from "@angular/forms";
import {HttpModule} from "@angular/http";
import {AppComponent} from "./app.component";
import {environment} from "../environments/environment";
import {XService} from "./xxx.service";
import {XMockService} from "./xxx-mock.service";

let importedModules: Array<any> = [
  XService
];

if (environment.mock) {
  importedModules.push(
    {provide: XService, useClass: XMockService}
  );
} else {
  importedModules.push(
    XService
  );
}

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    FormsModule,
    HttpModule
  ],
  providers: importedModules,
  bootstrap: [AppComponent]
})

export class AppModule {}
like image 434
David Dehghan Avatar asked Dec 29 '16 22:12

David Dehghan


2 Answers

So according to one of the main contributors the solution for your problem would be something like:

// main.ts
import './polyfills.ts';

import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { enableProdMode } from '@angular/core';
import { environment, AppModule } from './environments/environment';

if (environment.production) {
  enableProdMode();
}

platformBrowserDynamic().bootstrapModule(AppModule);

// environments/environment.ts
export { DevModule as AppModule } from '../app/dev.module';

export const environment = {
  production: false
};
// environments/environment.prod.ts
export { ProdModule as AppModule } from '../app/prod.module';

export const environment = {
  production: true
};

So it's very similar to my previous answer the difference is that you will be using different environment files, instead of different main files.

Previous Answer

It was recently opened an issue on Angular-cli, requesting a feature that would allow you to that. How? By switching your webpack entry point. Right now Angular-cli will always use the same entry point src/main.ts, the idea behind this feature is that you can specify a different entry point, so you would have something like src/main.dev.ts:

import './polyfills.ts';

import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { enableProdMode } from '@angular/core';
import { environment } from './environments/environment';
import { AppDevModule } from './app/app.dev.module';

if (environment.production) {
  enableProdMode();
}

platformBrowserDynamic().bootstrapModule(AppDevModule);

And then you would import your mocks on AppDevModule:

import {BrowserModule} from "@angular/platform-browser";
import {NgModule} from "@angular/core";
import {FormsModule} from "@angular/forms";
import {HttpModule} from "@angular/http";
import {AppComponent} from "./app.component";
import {environment} from "../environments/environment";
import {XMockService} from "./xxx-mock.service";

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    FormsModule,
    HttpModule
  ],
  providers: [XMockService],
  bootstrap: [AppComponent]
})

export class AppDevModule {}

The only downside of doing things this way, is that you would have to keep your AppModule and AppDevModule synced.

Which you could solve by creating a third file, something like AppImportsModule:

import {BrowserModule} from "@angular/platform-browser";
import {FormsModule} from "@angular/forms";
import {HttpModule} from "@angular/http";
import {AppComponent} from "./app.component";
import {environment} from "../environments/environment";

export {AppComponent} //we also export this for simplicity

export const declarations = [
  AppComponent
];

export const imports = [
  BrowserModule,
  FormsModule,
  HttpModule
];

And then on your AppModule.ts or AppDevModule.ts you would only need to import your providers:

import {NgModule} from "@angular/core";
import {declarations, imports, AppComponent} from "./app.imports.module";
import {XMockService} from "./xxx-mock.service";

@NgModule({
  declarations, // with es6 this is the same as "declarations: declarations"
  imports, //same as above
  providers: [XMockService],
  bootstrap: [AppComponent]
})

export class AppDevModule {}

So until that feature isn't implemented, I have no better solution to solve your problem.

like image 143
Fabio Antunes Avatar answered Oct 31 '22 19:10

Fabio Antunes


The chosen answer no longer works. For Angular CLI 6, as recommended, we'll have to treat dev & prod as separate projects sharing the same codebase.

Solution summary

  • Have separate projects for each env. (As simple as a few lines in angular.json)
  • Have as many AppModules as the number of envs with separate main.ts files for each
  • Have separate tsconfig files for each env
  • Tell angular about the changes through angular.json

Details

Make a copy of AppModule and name it DevModule. (DevModule for dev & AppModule for prod). DevModule will be the one with the mock services.

Have separate main.ts files for each. Say prod-main.ts for prod & dev-main.ts for dev. The only difference being that the prod-main loads AppModule while dev-main loads DevModule.

Duplicate the tsconfig.app.json file into tsconfig.prod.json & tsconfig.dev.json. In the exclude section of tsconfig.prod.json, type in "dev-main.ts", "app/dev-module.ts", "**/mock-*.ts". Type in the other dev modules too (the ones that load the mocks).

Repeat for dev. For tsconfig.dev.json, exclude prod-main.ts, app/app-module.ts. Make relevant changes to tsconfig.spec.json files too, if necessary (with similar excludes).

Update angular.json files to reflect the new tsconfig files.

// angular.json. Only the relevant parts are given below.
// Essentially, separate tsconfig files for 2 projects - dev & prod
{
  "newProjectRoot": "projects",
  "projects": {
    "my-prod-proj": {
      "architect": {
        "build": {
          "options": {
            "main": "src/main-prod.ts",
            "tsConfig": "src/tsconfig.app.json",
          },
        },
        "test": {
          "options": {
            "tsConfig": "src/tsconfig.prod.spec.json",
          }
        },
        "lint": {
          "options": {
            "tsConfig": [
              "src/tsconfig.prod.json",
              "src/tsconfig.prod.spec.json"
            ],
          }
        }
      }
    },
    "my-dev-proj": {
      "architect": {
        "build": {
          "options": {
            "main": "src/dev-main.ts",
            "tsConfig": "src/tsconfig.dev.json",
          }
        },
        "test": {
          "options": {
            "tsConfig": "src/tsconfig.dev.spec.json",
          }
        },
        "lint": {
          "options": {
            "tsConfig": [
              "src/tsconfig.dev.json",
              "src/tsconfig.dev.spec.json"
            ],
          }
        }
      }
    },
  },
  "defaultProject": "my-prod-proj",
}

Run the projects by name

ng serve my-dev-proj // dev with mocks
ng serve             // prod (set as default in angular.json)
same goes for the build command
like image 31
Srikanth Avatar answered Oct 31 '22 18:10

Srikanth