flaviait / ng2-jspm-template

A template for a quick development workflow with angular 2 and jspm
MIT License
14 stars 3 forks source link

Add a lazy loading module example #37

Open DorianGrey opened 7 years ago

DorianGrey commented 7 years ago

See here for a description on how to lazy load modules with the default router.

In general, lazy loading appears rather easy, especially with SystemJS. However, from what I've seen up to now, this only holds true for development mode, since live-transpilation is possible in that case. For production mode, a bundle created from the target module has to be loaded and thus its creation has to be set up accordingly. Suppose it will end up like with something like webpack's code splitting ability, with the only difference of having to set this up manually.

We should provide at least two examples:

{ path: "heroes", loadChildren: "app/hero/hero.module#HeroModule" }

(see the docs)

{ 
  path: "heroes",
  loadChildren: () => SystemJS.import("app/hero/hero.module").then(module => module.HeroModule)
}

(Not in the docs, but it's definitely possible)

Note: The first version seems to end up usingSystem.import within the current loading implementation in angular. I'm not sure if loading via an alias works properly in that case, thus, the second version should be included as well (using an alias for loading would be useful when providing multiple bundles).

svi3c commented 7 years ago

Hey @DorianGrey,

in general I like the idea of lazy loading. But I think atm it would break the hot module replacement. Since we don't have direct imports within the modules, the hot reloader would have to track which imports are done dynamically/lazy here. See https://github.com/capaj/systemjs-hot-reloader/issues/97.

DorianGrey commented 7 years ago

Yes, that would be unlikely. I'll give it a try anyway - maybe it's possible to work around this issue using the direct import.

€dit: Ok, I've had a look at it:

See this branch.

Tbh, with the current state of how modules can be loaded lazily and how these are treated within SystemJS and systemjs-hot-reloader (esp. how they are "linked" to each other), I'm not sure if we can get this working. Sad story ...

svi3c commented 7 years ago

What about importing all lazy modules statically inside main.dev.ts (either directly or indirectly)? Maybe this way we can get around this issue. In production the code would be loaded lazily with multiple disconnected dependency graphs. And in dev mode we would directly bundle it when bundling main.dev.ts. Since the main.dev.ts would be the entry point of a single connected dependency graph again, it's __reload callback should be called again, right?

DorianGrey commented 7 years ago

Hm, yes, that works. I've added

import "lazyTestModule";

to main.dev.ts, and a proper link is created. I don't really like this, but it seems to be the only possible workaround for now. I'll go this way when setting up the lazy loading examples.

DorianGrey commented 7 years ago

Current state is visible in this commit.

Update: Still in progress - setting up the production mode for this currently raises various problems, esp. since SystemJS was not included in the prod build step until now. There are still problems with the dependencies - it's not detected that they are already loaded, thus, an attempt is made to pick them up:

core.umd.js:3462 EXCEPTION: Uncaught (in promise): Error: (SystemJS) Unexpected token <
    SyntaxError: Unexpected token <
    Evaluating http://localhost:9988/plugin-typescript/plugin.js
    Error loading http://localhost:9988/plugin-typescript/plugin.js
    Error loading http://localhost:9988/@angular/router/index.d.ts!http://localhost:9988/plugin-typescript/plugin.js as "@angular/router/index.d.ts!plugin-typescript/plugin.js" from http://localhost:9988/lazyTest.bundle.js

Update: With a more-than-unlikely workaround for overwriting a SystemJS map entry (did not work with the regular stuff, dunno why), loading the lazy module is initiated, but fails with:

core.umd.js:3468 Error: Uncaught (in promise): TypeError: Cannot read property 'annotations' of undefined
    at resolvePromise (zone.js:418)
    at zone.js:395
    at ZoneDelegate.invoke (zone.js:192)
    at Object.onInvoke (core.umd.js:6242)
    at ZoneDelegate.invoke (zone.js:191)
    at Zone.run (zone.js:85)
    at zone.js:451
    at ZoneDelegate.invokeTask (zone.js:225)
    at Object.onInvokeTask (core.umd.js:6233)
    at ZoneDelegate.invokeTask (zone.js:224)ErrorHandler.handleError @ core.umd.js:3468
zone.js:344 Unhandled Promise rejection: Cannot read property 'annotations' of undefined ; Zone: angular ; Task: Promise.then ; Value: TypeError: Cannot read property 'annotations' of undefined(…) TypeError: Cannot read property 'annotations' of undefined
    at ReflectionCapabilities.annotations (http://localhost:9988/bundle.js:49810:41)
    at Reflector.annotations (http://localhost:9988/bundle.js:50016:56)
    at NgModuleResolver.resolve (http://localhost:9988/bundle.js:16564:52)
    at CompileMetadataResolver.getNgModuleMetadata (http://localhost:9988/bundle.js:16813:55)
    at RuntimeCompiler._compileComponents (http://localhost:9988/bundle.js:19486:55)
    at RuntimeCompiler._compileModuleAndComponents (http://localhost:9988/bundle.js:19421:45)
    at RuntimeCompiler.compileModuleAsync (http://localhost:9988/bundle.js:19412:29)
    at ModuleBoundCompiler.compileModuleAsync (http://localhost:9988/bundle.js:19723:39)
    at MergeMapSubscriber.project (http://localhost:9988/bundle.js:23160:130)
    at MergeMapSubscriber._tryNext (http://localhost:9988/bundle.js:22364:31)consoleError @ zone.js:344

Seems that the module itself has to be loaded after the corresponding bundle was loaded. However, that would require a loading strategy which is different from the dev mode. Dev atm.:

SystemJS.import("lazyTestModule").then(module => module.LazyTestModule)

Required for prod atm.:

SystemJS.import("lazyTestModule").then(() =>
      SystemJS.import("src/app/+lazyTest").then((module: any) => module.LazyTestModule)
    )

€dit: Maybe it is possible to define to load this module from a bundle with specialized config entry in production mode. That might also eliminate the need for the "workaround" I mentioned above.

Moving on ...

DorianGrey commented 7 years ago

Move to https://github.com/flaviait/ng2-jspm-template/pull/38.