Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Closed
jschwarty opened this issue Jan 10, 2017 · 31 comments
Closed

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

jschwarty opened this issue Jan 10, 2017 · 31 comments
Assignees

Comments

@jschwarty
Copy link
Contributor

jschwarty commented Jan 10, 2017

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

@FabioAntunes
Copy link

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

@ddehghan
Copy link

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.

@filipesilva
Copy link
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.

@filipesilva
Copy link
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])
    ));

@jschwarty
Copy link
Contributor Author

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"
      }

@SamVerschueren
Copy link

SamVerschueren commented Jan 14, 2017

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.

@filipesilva
Copy link
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.

@SamVerschueren
Copy link

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.

@SamVerschueren
Copy link

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.

@filipesilva
Copy link
Contributor

filipesilva commented Jan 15, 2017

@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
};

@filipesilva filipesilva self-assigned this Jan 15, 2017
@jschwarty
Copy link
Contributor Author

@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.

@SamVerschueren
Copy link

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

@filipesilva
Copy link
Contributor

Awesome, glad to his this works for you!

@jschwarty
Copy link
Contributor Author

Thank you everyone for explaining the use case scenarios and thank you @filipesilva for working with us to show us a solution!

@filipesilva
Copy link
Contributor

My pleasure ^^. This is a topic very dear to me really.

@jschwarty
Copy link
Contributor Author

@filipesilva This approach no longer works now with beta.31

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

Getting the following error on ng serve

Tried to find bootstrap code, but could not. Specify either statically analyzable bootstrap code or pass in an entryModule to the plugins options.
Error: Tried to find bootstrap code, but could not. Specify either statically analyzable bootstrap code or pass in an entryModule to the plugins options.
    at Object.resolveEntryModuleFromMain (C:\Sandbox\beta31\node_modules\@ngtools\webpack\src\entry_resolver.js:127:11)

@filipesilva filipesilva reopened this Feb 10, 2017
@filipesilva
Copy link
Contributor

Will investigate.

@jschwarty
Copy link
Contributor Author

jschwarty commented Feb 13, 2017

@filipesilva Did you happen to get a chance to look into this? I think it may be this change that broke it 2797a89

Another potential way around it...is there a way to dynamically change angularCompilerOptions based on environments or to pass in different tsconfig.json files to use per environment? If we could do that then we could make use of angularCompilerOptions.entryModule.

@sumitarora sumitarora self-assigned this Feb 15, 2017
@filipesilva
Copy link
Contributor

@jschwarty sorry I haven't yet had time to look into this, but @hansl seems to have an idea of what is happening and we'll try to get a fix into the next release.

@jschwarty
Copy link
Contributor Author

Thank you @filipesilva and @hansl, very much appreciated!

@hansl
Copy link
Contributor

hansl commented Feb 16, 2017

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.

@hansl
Copy link
Contributor

hansl commented Feb 16, 2017

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".

@jschwarty
Copy link
Contributor Author

❤ it! Thank you @hansl and team!

@intellix
Copy link
Contributor

intellix commented Feb 16, 2017

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

@SamVerschueren
Copy link

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

@sumitarora
Copy link
Contributor

Fixed and Merged already: #4754

@veke
Copy link

veke commented Jul 12, 2017

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

@srix55
Copy link

srix55 commented Jul 24, 2018

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?

@veke
Copy link

veke commented Jul 24, 2018

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

@srix55
Copy link

srix55 commented Jul 24, 2018

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.

@angular-automatic-lock-bot
Copy link

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.

@angular-automatic-lock-bot angular-automatic-lock-bot bot locked and limited conversation to collaborators Sep 8, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

10 participants