angular / angular-cli

CLI tool for Angular
https://cli.angular.io
MIT License
26.76k stars 11.98k forks source link

Lazy loading is not working together with 'import *' syntax #2496

Closed areijngoudt closed 7 years ago

areijngoudt commented 8 years ago

Please provide us with the following information:

OS?

Mac OSX El Capitan

Versions.

angular-cli: 1.0.0-beta.16 node: 5.7.1 os: darwin x64

Repro steps.

Was this an app that wasn't created using the CLI? What change did you do on your code? etc. The app was not created using the CLI. When upgrading to Angular 2 I choose to use the CLI as serving and building tool

I have this route in my app.routing.ts:

const ROUTES: Routes = [
    { path: '', redirectTo: 'analyse', pathMatch: 'full' },
    { path: 'annotate', component: AnnotateComponent },
    { path: 'fraud', component: FraudComponent },
    { path: 'info',  loadChildren: 'app/info/info.module#InfoModule'  },
];```

the InfoModule should be loaded lazily when navigating to it's path. But even in de --prod build there's no seperate bundle. The InfoModule is still part of the main.bundle.js.

Note that I'm still importing the InfoModule in the app.module.ts otherwise the info route does not work at all.
neilhem commented 8 years ago

You should not import InfoModule nowhere else. Only reference in routes' loadChildren

areijngoudt commented 8 years ago

When ommitting the import in app.modules, an error occurrs that route info is unknown. I'm using the angular-cli webpack version btw.

wrosb commented 8 years ago

If you import your InfoModule into app.module.ts then, you will not be using LazyLoad. Your module will be compile as main part of your app.

Could you please post your info.routing.ts?

areijngoudt commented 8 years ago

info.routing.ts:

import { ModuleWithProviders }  from '@angular/core';
import { RouterModule } from '@angular/router';

import { InfoComponent } from './info.component';

const ROUTES = [
  { path: '', component: InfoComponent}
];
export const routing: ModuleWithProviders = RouterModule.forChild(ROUTES);
wrosb commented 8 years ago

I could not find anything unusual in your code. I recreated a sample with your code and works fine for me. Your ROUTES const should has a type def, but even without a type def, routing works fine in my test.

Try this, just as best practice: import { ModuleWithProviders } from '@angular/core'; import { RouterModule, Routes } from '@angular/router'; import { InfoComponent } from './info.component';

const ROUTES: Routes = [ { path: '', component: InfoComponent} ]; export const routing: ModuleWithProviders = RouterModule.forChild(ROUTES);

I'm also using angular-cli@beta16, but over ubuntu 16. Maybe you should try a clean project and test it.

Richard87 commented 8 years ago

Hi all, I have the same problem. I have all the routes "inlined" in a DashboardModule file that I wish to lazyload... When I removed the import in my AppModule, my app crashed with a EXCEPTION: Uncaught (in promise): Error: Cannot match any routes: 'dashboard' error message... If I add DashboardModule to my @NgModule's imports, it works as expected.

Also, ng serve --prod creates 2 sets of main.*.bundle.js and styles.*.bundle.js as expected when DashboardModule is not imported

My AppModule:

@NgModule({
    imports:      [
        BrowserModule,
        HttpModule,
        CommonModule,
        FormsModule,
        RouterModule.forRoot([
            { path: '', component: LandingpageModule},
            { path: 'dashboard', loadChildren: `app/dashboard/dashboard.module#DashboardModule`}
        ]),
        LandingpageModule,
    ],
    bootstrap:    [AppComponent],
    providers:    [
        LoggedInGuard,
        authConfigProvider,
        JwtHelper,
        AuthHttp,
        AuthService
    ],
    declarations: [AppComponent],
})
export class AppModule {}

And my DashboardModule:

@NgModule({
    imports: [
        FormsModule,
        RouterModule.forChild([{
            path: 'dashboard',
            component: DashboardComponent,
            children: [
                { path: '', component: HomeComponent,canActivate: [LoggedInGuard]},
                { path: 'profile', component: ProfileComponent, canActivate: [LoggedInGuard] },
                { path: 'billing', component: BillingComponent, canActivate: [LoggedInGuard, AdminGuard] },
                { path: 'administration', component: AdministrationComponent, canActivate: [LoggedInGuard, AdminGuard] },
            ],
            canActivate: [LoggedInGuard]
        }]),
        CommonModule,
    ],
    providers: [
        AdminGuard
    ],
    declarations: [
        HomeComponent, StatusPanelComponent,DashboardComponent,
        ProfileComponent,AdministrationComponent,BillingComponent
    ]
})
export class DashboardModule {}
neilhem commented 8 years ago

@Richard87 you should set root path in DashboardModule as path: ''. So it will be

RouterModule.forChild([{
  path: '',
  component: DashboardComponent,
  children: [
Richard87 commented 8 years ago

@neilhem Thanks! The routing works, but I still don't get lazy loading? (There are no reference to DashboardModule in AppModule except for the loadChildren string)...

Also, it doesn't seem like ng serve --prod produces 2 bundle js files (I might have been wrong earlier, it looks like 1 of them where gzipped...)

                                              Asset       Size  Chunks             Chunk Names
                       assets/fonts/FontAwesome.otf     125 kB          [emitted]
                main.ef31a7b0feb1135ed298.bundle.js     1.2 MB    0, 2  [emitted]  main
                                          inline.js    1.39 kB       2  [emitted]  inline
           styles.667c85c265538fc61bbe.bundle.js.gz    26.6 kB          [emitted]
             main.ef31a7b0feb1135ed298.bundle.js.gz     285 kB          [emitted]
                                         index.html  542 bytes          [emitted]
                                  assets/.npmignore    0 bytes          [emitted]
               assets/fonts/fontawesome-webfont.eot    76.5 kB          [emitted]
               assets/fonts/fontawesome-webfont.svg     392 kB          [emitted]
               assets/fonts/fontawesome-webfont.ttf     153 kB          [emitted]
              assets/fonts/fontawesome-webfont.woff    90.4 kB          [emitted]
             assets/fonts/fontawesome-webfont.woff2    71.9 kB          [emitted]
              styles.667c85c265538fc61bbe.bundle.js     162 kB    1, 2  [emitted]  styles

Any tips on how to troubleshoot?

neilhem commented 8 years ago

You should not import files from other lazy modules in your lazy module

Richard87 commented 8 years ago

What do you mean? Like my AuthService or AuthHttp?

Also, I only have 1 lazy module, DashboardModule. my other 2 modules, AppModule and LandingpageModule doesnt import anything from DashboardModule or its components...

neilhem commented 8 years ago

Double check that global & local versions of angular-cli is the latest.

Richard87 commented 8 years ago

Thanks, should work:

C:\Users\richa\Prosjekt\phono-frontend>ng --version
angular-cli: 1.0.0-beta.16
node: 6.3.0
os: win32 x64

C:\Users\richa\Prosjekt\phono-frontend>node_modules\.bin\ng --version
angular-cli: 1.0.0-beta.16
node: 6.3.0
os: win32 x64

C:\Users\richa\Prosjekt\phono-frontend>cd ..

C:\Users\richa\Prosjekt>ng --version
angular-cli: 1.0.0-beta.16
node: 6.3.0
os: win32 x64
Richard87 commented 8 years ago

Hmm, I think there are some strange "residual" files after I run ng serve --prod

I was trying to clean up the project, so I deleted all *.ngfactory.ts-files, aaand angular stopped finding my dashboard module again (didn't complain about route or anything, just couldn't find the module).

Then I added DashboardModule to AppModules imports, and ofcourse it works again.. BUT when I removed DashboardModule's import (both inside @NgModule and javascript import {} from ... it still works.

So my conclusion is that I am importing DashboardModule in some old files, and that's why it doesnt work, but when I clean out the build files, why doesn't angular find my DashboardModule?

I can't reproduce the error where angular can't find dashboard module, so I have literary no idea whats going on..

aaronleestic commented 8 years ago

Same issue with 1.0.0-beta.16

import { NgModule }           from '@angular/core';
import { CommonModule }       from '@angular/common';
import { RouterModule } from '@angular/router';
import { DocumentsPage } from "./documents";

@NgModule({
  imports: [
    CommonModule,
    RouterModule.forChild([{ path: '', component: DocumentsPage}])
  ],
  declarations: [ DocumentsPage ],
})

export class DocumentsModule { }

and in my RouterModule.forRoot([...]): { path: 'documents', loadChildren: '/documents.module#DocumentsModule' }

When I replace loadChildren with component: fooCmp, DocumentsPage is not included in the main.bundle.js. Otherwise, using loadChildren includes the component in main.bundle.js

aaronleestic commented 8 years ago

figured out the culprit; import * as moment from 'moment' anywhere in the eagerly-loaded module will prevent lazy loading, and instead loadChildren modules will be included in the main bundle. I suspect importing d3 and other libraries will have the same issue.

The work around is to define the library in angular-cli.json' scripts definitions, and add a declare var moment: any in the typescript code, instead of an import.

However, workaround fails if other imported libraries has that import * as moment in its source code. For example, ng2-bootstrap's datepicker module : /

Workaround to that workaround is to fork valor's ng2-bootstrap datepicker...

Richard87 commented 8 years ago

So in other words, if there is a import * from xxx anywhere we are f****?

What about the 100+ that exist in my node_modules folder? image

EDIT Is it solvable (by angular-cli or webpack), or must all modules specify their imports?

aaronleestic commented 8 years ago

I did not yet investigate, but I made a repo that can reproduce this issue: https://github.com/aaronleeucla/angular-cli-lazy-load-bug-repro/blob/master/src/app/app-routing.module.ts

Running ng serve on this project, you can see that when either DatepickerModule is imported into NgModule or when moment() is used in the eagerly loaded MainPage component, lazy loading fails silently.

filipesilva commented 8 years ago

Great work figuring out the cause @aaronleeucla! This definitely should not happen and is a bug.

alexciesielski commented 8 years ago

Can confirm, @aaronleeucla 's workaround makes angular-cli create multiple bundles again :+1:

sasxa commented 8 years ago

For nested routing setup (Gist here) like this:

export const eagerAppRoutes: Routes = [
  { path: '', children: [
      { path: 'home' },
      { path: '', 
        children: [

          { path: '',  /* loadChildren: './viewer/viewer.module#ViewerModule', */ 
            children: [

              { path: '_',
                children: [

                  { path: '', /* loadChildren: '../editor/editor.module#EditorModule', */
                    children: [

                      { path: '', component: EditorComponent },
                      { path: ':model', component: EditorComponent },],},]},]},]},
      { path: '**', redirectTo: 'home' },
    ]}];

only one chunk will be created (with ViewerModule), but EditorModule route will be ignored. There will be runtime errors cannot find module './editor/editor.module#EditorModule'.

If I add this to the app-routing.module.ts, (a route _ will never be matched here) both chunks will be created and routing will work:

{ path: '', canLoad: [AuthGuard], loadChildren: './viewer/viewer.module#ViewerModule' },
{ path: '_', canLoad: [AuthGuard], loadChildren: './editor/editor.module#EditorModule' }

Also note that loadChildren url in Viewer module should be ../editor/etc since this module is in sibling folder to the viewer module, but it does work with ./editor/etc (just one dot) if I include link in both places.

I'm using cli beta.17; angular 2.1.0; router 3.1.0

To recap: For nested lazy loaded routes (Gist here) only one chunk is created if I don't add both route definitions to the root; if I do, both chunks are generated and routing works as expected.

wheredoesyourmindgo commented 7 years ago

Thanks a lot @aaronleeucla (may you get struck with good fortune today). I couldn't figure out why Webpack was no longer generating separate chunk.js files for my lazy routes. Your solution worked for me. Moving import * as moment ... components into lazy loaded modules/routes (thus removing them from the eager ones) fixed this.

JiriBalcar commented 7 years ago

Any progress with this issue @filipesilva? It has label "priority: 1 (urgent)" almost one month. It is blocking me to update my project to newest angular-cli version.

EmmanuelDemey commented 7 years ago

I have the same problem with angular-cli beta18.

With beta16, with an application using momentjs (and import * as moment from ''), I did not have any issues.

@aaronleeucla Which version of angular-cli are you using ?

Manu

daco commented 7 years ago

For what's worth, I've the same problem too using angular-cli beta18

Mihai-B commented 7 years ago

Is this related only to importing moment.js ? Because I am using angular-cli 18 and everything seems to work fine regarding lazy loading

daco commented 7 years ago

@Mihai-B Sorry, yes it is related to importing moment.js.

a5hik commented 7 years ago

This is really breaking everything.. I'm facing the exact same issue. A Fix for this is much appreciated.

I even tried with angular-cli: 1.0.0-beta.19-3 .. but the issue still exists.

Looks like this issue was introduced in beta.18

serhiisol commented 7 years ago

@a5hik

Looks like this issue was introduced in beta.18

I also tried with beta.17 and even 16 and have the same issue =)

VinayLeaf commented 7 years ago

Help plz, trying to lazy load one module getting same issue

Error: Cannot match any routes. URL Segment: 'admin'

i am using:

angular-cli: 1.0.0-beta.19-3 node: 6.7.0 os: win32 x64

"ng2-bootstrap": "^1.1.16",

package.json

"dependencies": { "@angular/common": "2.1.2", "@angular/compiler": "2.1.2", "@angular/core": "2.1.2", "@angular/forms": "2.1.2", "@angular/http": "2.1.2", "@angular/platform-browser": "2.1.2", "@angular/platform-browser-dynamic": "2.1.2", "@angular/router": "~3.1.0", "bootstrap": "^3.3.7", "core-js": "^2.4.1", "ng2-bootstrap": "^1.1.16", "ng2-sidebar": "^1.6.2", "rxjs": "5.0.0-beta.12", "ts-helpers": "^1.1.1", "zone.js": "^0.6.23" }, "devDependencies": { "@types/jasmine": "^2.2.30", "@types/node": "^6.0.42", "angular-cli": "1.0.0-beta.19-3", "codelyzer": "1.0.0-beta.1", "jasmine-core": "2.4.1", "jasmine-spec-reporter": "2.5.0", "karma": "1.2.0", "karma-chrome-launcher": "^2.0.0", "karma-cli": "^1.0.1", "karma-jasmine": "^1.0.2", "karma-remap-istanbul": "^0.2.1", "protractor": "4.0.9", "ts-node": "1.2.1", "tslint": "3.13.0", "typescript": "~2.0.3", "webdriver-manager": "10.2.5" }

achimha commented 7 years ago

You will have to wait for beta 20, lazy loading isn't really working yet.

alexciesielski commented 7 years ago

@achimha What do you mean? Of course it's working, even with 3rd party libs using the workaround mentioned above.

@VinayLeaf post your question with code on stackoverflow and I can help you there

achimha commented 7 years ago

The workaround means modifying the library and if the library depends on a library which depends on a library etc. the workaround isn't really workable.

However, beta 20 is probably out very soon and then things should be much better.

serhiisol commented 7 years ago

agree with @ciesielskico, it's really working with workaround. Tried with latest cli

VinayLeaf commented 7 years ago

Can anyone tell me that how to use that workaround???

On 11-Nov-2016 3:45 pm, "Serhii Sol" notifications@github.com wrote:

agree with @ciesielskico https://github.com/ciesielskico, it's really working with workaround. Tried with latest cli

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/angular/angular-cli/issues/2496#issuecomment-259926017, or mute the thread https://github.com/notifications/unsubscribe-auth/AQo1nUyqcETorUxa08j3-wesrSvDsn4fks5q9EBWgaJpZM4KNcjN .

serhiisol commented 7 years ago

@VinayLeaf All you need to do is to avoid wildcard imports e.g.:

import * as moment from 'moment'

instead you need to include import into angular-cli.json e.g.:

"scripts": [
"../node_modules/moment/moment.js"
]

and then add typing for moment e.g.:

declare var moment: any;

And that's pretty it. I hope that will help you :)

PS. also you can try to import it as polyfill, but declaration of variable still should be added somewhere in types

VinayLeaf commented 7 years ago

Need to declare var instead of import * as momment from 'moment'?? Every where??

On 11-Nov-2016 3:51 pm, "Serhii Sol" notifications@github.com wrote:

@VinayLeaf https://github.com/VinayLeaf All you need to do is to avoid wildcard imports e.g.:

import * as moment from 'moment'

instead you need to include import into angular-cli.json e.g.:

"scripts": ["../node_modules/moment/moment.js" ]

and then add typing for moment e.g.:

declare var moment: any;

And that's pretty it. I hope that will help you :)

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/angular/angular-cli/issues/2496#issuecomment-259926983, or mute the thread https://github.com/notifications/unsubscribe-auth/AQo1nYYjRRUqXfsSGuhD3q8jyjpjcwmTks5q9EGfgaJpZM4KNcjN .

serhiisol commented 7 years ago

@VinayLeaf only once in typings somewhere (where's your types) and then use moment as global.

VinayLeaf commented 7 years ago

@serhiisol i found 2 occurrences of import * as moment from 'moment' in following location

node_modules\awesome-typescript-loader\issues\product-designer\src\components\products node_modules\awesome-typescript-loader\issues\product-designer\src\constants

than i have replaced import * as moment from 'moment' to import * from 'moment'

i have included following code in angular-cli.json

"scripts": [ "../node_modules/moment/moment.js" ]

than i have declared declare var moment: any; in typings.d.ts

still i am getting following error

error_handler.js:47 EXCEPTION: Uncaught (in promise): Error: Cannot match any routes. URL Segment: 'admin'

am i doing something wrong?

if you want to look at my file how they looks: than got this link http://stackoverflow.com/questions/40531095/lazy-loading-not-working-with-angular-cli-webpack

VinayLeaf commented 7 years ago

@serhiisol i found that if i am not using ng2-bootstrap module than my lazy loading is working fine. and i also looked for import * as moment from 'moment' but i foundvar moment = require('moment'); i also tried to user workaround but still its not working.

VinayLeaf commented 7 years ago

@serhiisol i found solution i was doing mistake in my code, its my bad.

look at here: http://stackoverflow.com/questions/40531095/lazy-loading-not-working-with-angular-cli-webpack

slubowsky commented 7 years ago

Anybody get the workaround working with AOT? (I'm using 1.0.0-beta.20-4) Without AOT it just shows warning "Cannot find namespace 'moment'." for each component file that I use moment in but app still runs, With AOT I get

ERROR in ./src/app/app.module.ngfactory.ts
Module build failed: Error: <my component that uses moment>): Cannot find namespace 'moment'.)
.....

And app doesnt start.

monojack commented 7 years ago

I've just read this thread up to the first comment mentioning momentjs. I remembered that I had this similar issue once, declaring moment in angular-cli.json scripts fixed it.

I don't know if this solution has been provided, as I said, I haven't read the entire thing. If not, you could try that.

a5hik commented 7 years ago

angular-cli: 1.0.0-beta.20-4 node: 7.0.0 os: darwin x64

The lazy loading still not working.. with aot I was able to generate the map files for all the modules. But only getting one main chunk created.. no module chunks. So everything gets loaded when the app loads with one big main chunk.

a5hik commented 7 years ago

Adding simply the below Fixed the issue..

"../node_modules/moment/moment.js" in angular-cli.json

But this should be a workaround.. and not the actual Fix.

Sanderand commented 7 years ago

Lazy loading is still not working for me...

angular-cli: 1.0.0-beta.21
node: 6.9.1
os: win32 x64
aaronleestic commented 7 years ago

PSA: My previously suggested workaround (declare var moment instead of importing moment) should also be applied to code in lazy modules as well. Per my issue reference above, I found that if one were to use import * as moment from 'momemt in a lazy module, that module will then duplicate the code from all other lazy chunks. bad!

However, even if you don't import moment, if one of the dependency in a lazy module uses such import, such as ng2-bootstrap's datepicker, the module will still duplicate. sad!

aaronleestic commented 7 years ago

@Gillespie59 I've been running beta.22 / angular 2.2.3 and this bug still appears.

Correction, when I say it appears, I mean in the form of lazy chunks code being duplicated, per #3410 . I have not reproducing this bug by having import moment the eager module.

cladera commented 7 years ago

Just upgraded to beta.22 from beta.18. Lazy loading is still not working but instead of not generating the chunks now my application does not run at all.

As my project comes from several angualr-cli upgrades I created a brand new project to be sure the error is not related to my code. When I try to navigate to a lazy route I get this error:

error_handler.js:47EXCEPTION: Uncaught (in promise): Error: Cannot find module 'app/my-route/my-route.module'.
Error: Cannot find module 'app/my-route/my-route.module'.
    at webpackEmptyContext (http://localhost:4200/main.bundle.js:41:8)
    at SystemJsNgModuleLoader.loadAndCompile (http://localhost:4200/vendor.bundle.js:51252:40)
    at SystemJsNgModuleLoader.load (http://localhost:4200/vendor.bundle.js:51244:60)
    at RouterConfigLoader.loadModuleFactory (http://localhost:4200/vendor.bundle.js:66070:128)
    at RouterConfigLoader.load (http://localhost:4200/vendor.bundle.js:66061:81)
    at MergeMapSubscriber.project (http://localhost:4200/vendor.bundle.js:68848:111)
    at MergeMapSubscriber._tryNext (http://localhost:4200/vendor.bundle.js:66195:27)
    at MergeMapSubscriber._next (http://localhost:4200/vendor.bundle.js:66185:18)
    at MergeMapSubscriber.Subscriber.next (http://localhost:4200/vendor.bundle.js:41971:18)
    at ScalarObservable._subscribe (http://localhost:4200/vendor.bundle.js:68111:24)
sclausen commented 7 years ago

I also struggled with loadChildren a whole workday and discovered in the source code, that using backticks ` will not work, because it's not ts.SyntaxKind.StringLiteral. You have to use double- \" or singlequotes \' for enclosing the loadChildren path. Maybe some of you stepped into the same pitfall than me.

cladera commented 7 years ago

@sclausen I am already using single quotes. I've tested with double quotes and I get the same error...