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

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

Closed
areijngoudt opened this issue Oct 4, 2016 · 85 comments · Fixed by #4153
Closed

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

areijngoudt opened this issue Oct 4, 2016 · 85 comments · Fixed by #4153
Labels
P1 Impacts a large percentage of users; if a workaround exists it is partial or overly painful type: bug/fix type: faq

Comments

@areijngoudt
Copy link

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
Copy link
Contributor

neilhem commented Oct 4, 2016

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

@areijngoudt
Copy link
Author

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
Copy link

wrosb commented Oct 5, 2016

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
Copy link
Author

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
Copy link

wrosb commented Oct 5, 2016

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
Copy link

Richard87 commented Oct 6, 2016

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
Copy link
Contributor

neilhem commented Oct 6, 2016

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

RouterModule.forChild([{
  path: '',
  component: DashboardComponent,
  children: [

@Richard87
Copy link

@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
Copy link
Contributor

neilhem commented Oct 6, 2016

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

@Richard87
Copy link

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
Copy link
Contributor

neilhem commented Oct 6, 2016

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

@Richard87
Copy link

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
Copy link

Richard87 commented Oct 6, 2016

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
Copy link

aaronleestic commented Oct 6, 2016

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
Copy link

aaronleestic commented Oct 7, 2016

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
Copy link

Richard87 commented Oct 7, 2016

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
Copy link

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 filipesilva added type: bug/fix command: build P1 Impacts a large percentage of users; if a workaround exists it is partial or overly painful labels Oct 10, 2016
@filipesilva
Copy link
Contributor

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

@alexciesielski
Copy link

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

@sasxa
Copy link

sasxa commented Oct 16, 2016

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
Copy link

wheredoesyourmindgo commented Oct 30, 2016

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
Copy link

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
Copy link
Contributor

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

@rodneyjoyce
Copy link

Yes, I did the exact same thing.

filipesilva added a commit to filipesilva/angular-cli that referenced this issue Jan 21, 2017
filipesilva added a commit to filipesilva/angular-cli that referenced this issue Jan 21, 2017
filipesilva added a commit to filipesilva/angular-cli that referenced this issue Jan 21, 2017
filipesilva added a commit to filipesilva/angular-cli that referenced this issue Jan 21, 2017
@filipesilva
Copy link
Contributor

#4153 should fix this problem.

@jwuliger
Copy link

@born2net Are you using the current master directly with something like npm link angular-cli or traditionally installing/using the CLI? Thanks!

@born2net
Copy link

ha sorry I installed via npm, I figured it was latest as I saw .26, I guess its its not published yet? if so I will wait, np

@jwuliger
Copy link

@born2net You can do what I have been doing to use the latest changes in my project.

Check out: https://github.com/angular/angular-cli#development-hints-for-hacking-on-angular-cli

I am setting up to test this new change myself.

@born2net
Copy link

born2net commented Jan 22, 2017

tried it but I could not get the symlink to work. prob cause I am on windows, will wait for npm relase to check, tx!

@rodneyjoyce
Copy link

FYI, here is another TimeAgo filter if you are looking to swap Moment for DateFNS and need a pipe for the template (including localisation). This replaces the angular2-moment pipes which are not currently compatible with chunking
Usage: <div>Date with TimeAgo DateFNS Pipe : {{this.exampleDateTime | timeAgo : true }}</div>

import { Pipe, PipeTransform } from '@angular/core';
import * as distanceInWordsToNow from 'date-fns/distance_in_words_to_now'
import * as esLocale from 'date-fns/locale/es/index.js'

@Pipe({
    name: 'timeAgo'
})
export class TimeAgoPipe implements PipeTransform {
    transform(value: Date | string | number, ...args: any[]): any {
        if (!value) return '';
        return distanceInWordsToNow(value, { addSuffix: args[0], locale: esLocale });
    }
}

@born2net
Copy link

born2net commented Feb 1, 2017

seems like .28 fixed chunks, but AOT broken so can't 100% test yet.

@born2net
Copy link

born2net commented Feb 2, 2017

looks like build .28-3 still does not generate any chunks for lazy loading
so .28 worked with chunks (had diff issues) and 28-3 looks good (no crashes) but no chunks
just want to make sure devs are aware of this
regards

Sean

@JiriBalcar
Copy link

@born2net for me it does and I have lot of import * as moment in my code. Try to update @ngtools/webpack to latest version.

@born2net
Copy link

born2net commented Feb 2, 2017

ha ok... let me try

@artaommahe
Copy link

artaommahe commented Feb 3, 2017

@filipesilva i'm trying to migrate to @ngtools/webpack usage (without ng-cli) instead of ATL and with #4153 changes (1.2.7 lib vesrion) there is no lazy routes chunks on build. As i investigated this check never passes

if (!result.resource.endsWith(path.join('@angular/core/src/linker'))) {

Does not see any @angular/core/src/linker resource value here. If i comment it out, bundles created but with a lot of overhead as i understand (included all imported modules despite they are included in main bundle).
Tried 1.2.4/3 - does not work too, no chunks at all.
Cleared all export default/import * from code.

everything i added to webpack config related to @ngtools/webpack

        {
          test: /\.ts$/,
          use: [
            '@ngtools/webpack',
          ]
        },
...
    plugins: [
      new AotPlugin({
        tsConfigPath: 'tsconfig.json',
        entryModule: path.resolve(CONFIG.APP_PATH, 'app-module', 'module#AppModule'),
        skipCodeGeneration: true, // for dev mode
      }),

@angular/compiler-cli version 2.4.6

Update: added new issue with this bug #4431

MRHarrison pushed a commit to MRHarrison/angular-cli that referenced this issue Feb 9, 2017
@kossov
Copy link

kossov commented Feb 21, 2017

try this:
app.module

import { QuickStatsModule } from './quick-stats/quick-stats.module';
    
const appRoutes: Routes = [
        { path: 'quick-stats', loadChildren: () => QuickStatsModule },
];

imports: [
    BrowserModule,
    HttpModule,
    RouterModule.forRoot(appRoutes)
]

quick-stats.module

const ROUTES = [
    { path: '', component: QuickStatsComponent },
];

@NgModule({
    imports: [
        CommonModule,
        RouterModule.forChild(ROUTES)
    ],
    declarations: [
            QuickStatsComponent,
        ],
        exports: [QuickStatsComponent]
})

This worked out for me.
Source

@kuncevic
Copy link

kuncevic commented Apr 20, 2017

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;

How would you do lazy loading in such case, say you have lazy loaded component that rely on moment, you do load moment with a scripts like above + "lazy": true https://github.com/angular/angular-cli/wiki/stories-global-scripts so you got moment.bundle.js produced. Then how would moment.bundle.js figure out when to get loaded?

I tried to do something like that with powerbi-client and have an issue #6018

@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 7, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
P1 Impacts a large percentage of users; if a workaround exists it is partial or overly painful type: bug/fix type: faq
Projects
None yet
Development

Successfully merging a pull request may close this issue.