Skip to content

feat(serve): Add -main flag for entry point #3940

Closed

Description

@jschwarty

Feature requested

Would like to be able to change the main entry point script to use via a flag when running ng serve (as well as ng build).

ng serve -main main2.ts -e=someOtherEnvironment

Reason

While leveraging environment constants are handy to conditionally run code in your main.ts script, they don't get used by webpack (or AOT). So if you have some environment code that you don't want to include in your production build and you do the following in your main.ts:

. . .
if (environment.someOtherEnvironment) {
  myBootstrap(); // This function has imports that bring in other non-production code
} else {
  platformBrowserDynamic().bootstrapModule(AppModule);
}

Webpack will follow that function and its imports and will add that code to the bundles even when you ng serve or ng build.

Having the ability to target different main scripts would allow for ng serve and ng build to be used to build the appropriate code.

Then we could create different main files:
main.ts

. . .
platformBrowserDynamic().bootstrapModule(AppModule);

main.other.ts

. . .
myBootstrap();

And achieve optimal bundling of our app code based on our intended environment.

Notes

I'd be more than happy to add this feature, just want to confirm that it is something that people would feel would be worthy.

@hansl @robwormald

Activity

  1. FabioAntunes commented on Jan 11, 2017

    @FabioAntunes

    I just came across this question on stackverflow, that seems to be a perfect example why this might be useful

  2. ddehghan commented on Jan 12, 2017

    @ddehghan

    Hi Everyone - I wrote the SO question. The scenario is pretty simple. We dont want to ship all our test code for the mock services in the production build. It should be a pretty common use case.

  3. filipesilva commented on Jan 13, 2017

    @filipesilva
    Contributor

    Would you not be able to do your conditional imports from inside the environment files instead? And then expose whatever you need to main.ts (or any other file).

    // environment.ts
    import { MockX } from '../mocks/mock-x.ts';
    
    export const environment = {
      production: false,
      extraProviders: [ MockX ]
    };
    
    // environment.prod.ts
    import { RealX } from '../real/real-x.ts';
    
    export const environment = {
      production: false,
      extraProviders: [ RealX ]
    };
    
    // app.module.ts
    import { environment } from '../environments/environment';
    //...
      imports: [
        BrowserModule,
        FormsModule,
        HttpModule,
        ...environment.extraProviders
      ],
    //...
    

    You're essentially asking for a split point where a file would be used to conditionally include static ES imports (and other code). In the scenario presented, the split point would be main.ts instead of environment.ts.

    Unless I'm missing something, the only important thing is that somewhere this split point exists.

  4. filipesilva commented on Jan 13, 2017

    @filipesilva
    Contributor

    That being said, the environments functionality really does not discriminate. It's an absolute replacement. This works:

          "environments": {
            "source": "main.ts",
            "dev": "main.ts",
            "prod": "main.2.ts"
          }
    

    I wouldn't recommend it overall because if some big change in the functionality is required this usecase won't be kept in mind. But currently, as far as webpack is concerned it's just a file replacement.

        extraPlugins.push(new webpack.NormalModuleReplacementPlugin(
          // This plugin is responsible for swapping the environment files.
          // Since it takes a RegExp as first parameter, we need to escape the path.
          // See https://webpack.github.io/docs/list-of-plugins.html#normalmodulereplacementplugin
          new RegExp(path.resolve(appRoot, appConfig.environments['source'])
            .replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, '\\$&')),
          path.resolve(appRoot, appConfig.environments[environment])
        ));
    
  5. jschwarty commented on Jan 14, 2017

    @jschwarty
    ContributorAuthor

    We still really need a way to run a different main.ts entry script. While the following does let us pull that off:

          "environments": {
            "source": "main.ts",
            "dev": "main.ts",
            "prod": "main.2.ts"
          }
    

    ...it complicates the ability to keep leveraging the environment constant in our applications.

    Another option here (instead of or in addition to the ng serve flag for -main) could be to have the "main" property in the angular-cli.json config change to "mains" and work just like "environments":

          "mains": {
            "source": "main.ts",
            "dev": "main.ts",
            "prod": "main.prod.ts"
          }
    
  6. SamVerschueren commented on Jan 14, 2017

    @SamVerschueren

    Another option here (instead of or in addition to the ng serve flag for -main) could be to have the "main" property in the angular-cli.json config change to "mains" and work just like "environments"

    That is exactly what I had in mind. It would be awesome to have something like that! But why use mains ? Can't we expand main to be a string (like it is now) and allow it to be an environmental object? Or use entrypoint as name could do as well. Just throwing out some ideas.

  7. filipesilva commented on Jan 14, 2017

    @filipesilva
    Contributor

    I'm still having some difficulty seeing why it's better to switch main.ts instead of environment.ts. To me it seems that one shouldn't be swapping the entry point because you still want the same app, you just want to have a different environment.

    It's true that it's perfectly doable to add in main swapping but if there's a better abstraction (and I think the env files are one) then it's better to use that. Swapping main might be common for webpack users but not so much for other build stacks. If you're using the quickstart, for instance, using a different main involves changing code in a couple of places.

    I know there have been a couple of theoretical examples in this issue already, but could you also give me a complete one (all the different file contents from a newly generated project) that illustrates the difference between swapping main.ts or environment.ts? That way I could run it and see if the env files really are insufficient.

  8. SamVerschueren commented on Jan 14, 2017

    @SamVerschueren

    I for instance swap my main file because in development I want the ngrx devtools but I don't want them in my production application. I'm on mobile now but will show my code examples when on the computer.

  9. SamVerschueren commented on Jan 15, 2017

    @SamVerschueren

    I'm on my computer so I can share some code.

    Development

    main.dev.ts

    import './polyfills';
    
    import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
    
    import { environment } from './environments/environment';
    import { DevModule } from './app/dev.module';
    
    platformBrowserDynamic().bootstrapModule(DevModule);

    dev.module.ts

    import { NgModule, Component } from '@angular/core';
    import { StoreModule } from '@ngrx/store';
    import { StoreLogMonitorModule, useLogMonitor } from '@ngrx/store-log-monitor';
    import { StoreDevtoolsModule } from '@ngrx/store-devtools';
    import { StoreUndoModule } from 'ngrx-undo';
    
    import { AppModule } from './app/app.module';
    import { rootReducer } from './shared/index';
    
    @Component({
    	selector: 'application-wrapper',
    	template: `
    		<sd-app></sd-app>
    		<ngrx-store-log-monitor toggleCommand="ctrl-t" positionCommand="ctrl-m"></ngrx-store-log-monitor>
    	`
    })
    export class ApplicationWrapperComponent { }
    
    @NgModule({
    	imports: [
    		StoreModule.provideStore(rootReducer),
    		StoreDevtoolsModule.instrumentStore({
    			monitor: useLogMonitor({
    				visible: false,
    				position: 'right'
    			})
    		}),
    		StoreUndoModule.interceptStore({
    			bufferSize: 20
    		}),
    		StoreLogMonitorModule,
    		AppModule
    	],
    	declarations: [ApplicationWrapperComponent],
    	bootstrap: [ApplicationWrapperComponent]
    })
    export class DevModule {}

    Production

    main.prod.ts

    import './polyfills';
    
    import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
    import { enableProdMode } from '@angular/core';
    
    import { environment } from './environments/environment';
    import { ProdModule } from './app/prod.module';
    
    enableProdMode();
    
    platformBrowserDynamic().bootstrapModule(ProdModule);

    prod.module.ts

    import { NgModule, Component } from '@angular/core';
    import { StoreModule } from '@ngrx/store';
    import { StoreUndoModule } from 'ngrx-undo';
    
    import { AppModule } from './app/app.module';
    import { rootReducer } from './shared/index';
    
    @Component({
    	selector: 'application-wrapper',
    	template: ` <sd-app></sd-app>`
    })
    export class ApplicationWrapperComponent {}
    
    @NgModule({
    	imports: [
    		StoreModule.provideStore(rootReducer),
    		StoreUndoModule.interceptStore({
    			bufferSize: 20
    		}),
    		AppModule
    	],
    	declarations: [ApplicationWrapperComponent],
    	bootstrap: [ApplicationWrapperComponent]
    })
    export class MainModule {}

    As you can see, the production module does not bootstrap and show the ngrx devtools, but the development module does. If you have any feedback on to how it can be achieved without different main files, I would be happy to hear that. But for now, this is my use case why I need something like this setting or flag.

  10. filipesilva commented on Jan 15, 2017

    @filipesilva
    Contributor

    @SamVerschueren would this not work?

    Essentially exporting Dev/Prodmodule as AppModule from within the environment file, and loading it in main.ts.

    // 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: false
    };
    
  11. self-assigned this
    on Jan 15, 2017
  12. jschwarty commented on Jan 16, 2017

    @jschwarty
    ContributorAuthor

    @filipesilva I like this solution. Works well to solve the "module to bootstrap" puzzle and the ability to do the rest of the conditional stuff in the environment files.

  13. SamVerschueren commented on Jan 16, 2017

    @SamVerschueren

    Cool, never thought about it like that. That would definitely work. Thanks!

  14. 11 remaining items

  15. hansl commented on Feb 16, 2017

    @hansl
    Contributor

    Okay, after discussion with @jschwarty (thanks for taking time for this), we came to the conclusion that the best way to support his use case while also supporting other use cases would be to finally have support for other apps in the config.

    As a simple fix, I suggest that we add an --app argument to build, serve, test and e2e (anything else?) that will pick the app data from the index passed in (0 by default). This can be done real quick without too much implication.

  16. hansl commented on Feb 16, 2017

    @hansl
    Contributor

    And after discussing with the team, we're going to also support naming your app, so that --app=someName will use the first app in the array to have "name": "someName".

  17. jschwarty commented on Feb 16, 2017

    @jschwarty
    ContributorAuthor

    ❤ it! Thank you @hansl and team!

  18. intellix commented on Feb 16, 2017

    @intellix
    Contributor

    Will this perhaps help when it comes to having multiple locale bundles? #2201

    At the moment for multiple locales, people are creating multiple bundles and then redirecting via a server like:

    $ for lang in es en fr; do \
      ng build --output-path=dist/$lang \
                --aot \
                -prod \
                --bh /$lang/ \
                --i18n-file=src/i18n/messages.$lang.xlf \
                --i18n-format=xlf \
                --locale=$lang; \
    done
  19. SamVerschueren commented on Feb 24, 2017

    @SamVerschueren

    It's still working for me though. Running @angular/cli#1.0.0-beta.32.3.

  20. sumitarora commented on Mar 27, 2017

    @sumitarora
    Contributor

    Fixed and Merged already: #4754

  21. veke commented on Jul 12, 2017

    @veke

    @hansl
    This app separation and using different entry points is not working in --aot mode
    #6189

  22. srix55 commented on Jul 24, 2018

    @srix55

    I tried creating multiple projects for dev & prod. Looks like the projects cannot have a single code base. It keeps throwing " can't be part of 2 module declarations" error even though the modules of prod & dev are kept separate. I don't want to have copies of the same code in dev & prod projects. I came down this path only because I wanted the mock files to be excluded from production build... Am I missing something here?

  23. veke commented on Jul 24, 2018

    @veke

    @srix55 take a look of this: #8031 (comment)
    I used that method to exclude mocks and mock services from production code.

  24. srix55 commented on Jul 24, 2018

    @srix55

    Separating mocks from production code
    Thanks a lot @veke . Can't thank you enough. Adding @filipesilva 's comment here for future reference.

    The problem: Mock code used for testing get bundled into production.
    Solution: Using the concept of multiple projects in angular.json, have separate tsconfig files for dev & production. The respective tsconfig files exclude the files & modules of the other config. That is, the production tsconfig excludes dev modules & mocks.

    @filipesilva 's response to this -
    The error ... is part of the declarations of 2 modules error is a AOT error where you cannot have a component being part of the declaration of two NgModules.

    This error happens in your setup because you have two apps that share all the TS files. The files that each app contains are defined in src/tsconfig.app.json. You use the same tsconfig for both, and that tsconfig only excludes test.ts and *.spec.ts files.

    The solution is to create separate tsconfig for each app, excluding the files specific to other apps.

    rename src/tsconfig.app.json to src/tsconfig.test.json, copy src/tsconfig.test.json to src/tsconfig.test2.json.
    add "main2.ts", "app/app.test.module.ts", to the exclude array of src/tsconfig.test.json
    add "main.ts", "app/app.module.ts", to the exclude array of src/tsconfig.test2.json
    update apps in .angular-cli.json to use these tsconfigs (test uses tsconfig.test.json and test2 uses tsconfig.test2.json.
    
  25. angular-automatic-lock-bot commented on Sep 8, 2019

    @angular-automatic-lock-bot

    This issue has been automatically locked due to inactivity.
    Please file a new issue if you are encountering a similar or related problem.

    Read more about our automatic conversation locking policy.

    This action has been performed automatically by a bot.

  26. locked and limited conversation to collaborators on Sep 8, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions