@ngtools/webpack feature request: support code splitting by third parties
See original GitHub issueThis is a feature request.
I’d like to start discussion around @ngtools/webpack
supporting code splitting by third party libs (including ui-router-ng2
).
Currently the code splitting support in @ngtools/webpack
has a soft dependency on @angular/router
.
My understanding is that:
@ngtools/webpack
loader invokes__NGTOOLS_PRIVATE_API_2.listLazyRoutes()
listLazyRoutes
finds lazy routes:- References the DI token
ROUTES
found in@angular/router
- statically analyzes the application.
- checks each
NgModule
for providedROUTES
- Checks each route for
loadChildren
and adds to the list of lazy routes. - returns lazy routes
- References the DI token
- processes lazy routes, redirecting imports to
.ngfactory.ts
and doing webpack magic
Goal
I think the goal should be to eliminate the dependency on @angular/router
and provide a router agnostic mechanism for third parties to supply code splitting information.
Today, UI-Router supports code splitting using @ngtools/webpack
by importing the ROUTES
token from @angular/router
, then providing the ui-router states as that DI token, i.e., { provide: ROUTES, useValue: module.states, multi: true }
. However, this forces ui-router to have a dependency on @angular/router
. It also forces ui-router to emulate the structure of @angular/router
by adding a loadChildren
property.
Proposal 1
Make the DI token configurable by @ngtools/webpack
.
Do not reference the token from @angular/router
in ngtools-impl.ts
, but pass the token in. UI-Router users could configure this somehow to use the STATES
token, for example.
This proposal is rather limited in its usefulness. Only one library could support lazy loading + code splitting in a given app.
Proposal 2
Move and/or rename the DI token out of @angular/router
Perhaps the DI token could be moved to @angular/compiler-cli
, @angular/common
, or @angular/core
. This eliminates the need to depend on @angular/router
. Additionally, move the token to its own ES6 module. Today, importing the ROUTES
token from @angular/router
brings in unrelated symbols from @angular/router
into the ui-router bundle even using rollup.
This proposal still conceptually ties lazy loading to the @angular/router
. All terminology and code analysis assumes that lazy loading code processes @angular/router
routes. Third parties have to emulate the router’s structure.
Proposal 3
Introduce a routing-agnostic token such as ANALYZE_FOR_LAZY_LOAD
.
Either @angular/router
or third parties should provide lazy load and code splitting information using this token. I propose instead of providing an array of specifically structured objects such as ROUTES
or STATES
, that only the lazy load information need be provided, i.e.:
{
provide: ANALYZE_FOR_LAZY_LOAD,
useValue: [
'./child1/child1.module#Child1Module',
'./child2/child2.module#Child2Module'
]
}
The @angular/router
would then provide the module’s lazy load information by doing something like:
const lazyRoutes = ROUTES.map(x => x.loadChildren).filter(identity);
const provider = { provide: ANALYZE_FOR_LAZY_LOAD, useValue: lazyRoutes, multi: true };
The knowledge about the lazy load declaration object (i.e., what the loadChildren
property means on a route) is moved from @angular/compiler-cli
to the library that owns that structure (i.e., the @angular/router
).
Of these three proposals, this one is my favorite as it separates concerns nicely and provides a clear mechanism for lib authors to use.
Issue Analytics
- State:
- Created 7 years ago
- Reactions:37
- Comments:14 (7 by maintainers)
@wardbell there are a few concerns to consider:
For module lazy loading, you need to: 1a) lazy load the sources (
System.import('./lazymodule')
) 1b) For JIT, get theNgModule
and compile it 1c) For AoT, get theNgModuleFactory
Instantiate the lazy module as an
NgModuleRef
. 2a) Pass the parent injector when creating (ngModuleFactory.create(parentInjector)
) Remember, we have a tree of injectors. When created, the NgModule will have a child injector associated with it.2b) Now you have to use the child injector from the NgModule to inject services defined by the lazy module.
2c) You also have to use the child injector to access the lazy module’s
ComponentFactory
when creating components from the lazy NgModule.It’s been a while since I wrote this code, but this is my vague recollection: Both @ngtools/webpack code splitting and also
ngc
lazy module analysis and compilation is done in the same way. 3a) Provide an array of objects using theROUTES
token (from@angular/router
) 3b) Make sure each object in the array has aloadChildren
function (if it should lazy load) 3c) In theloadChildren
function, return a promise for the NgModule or NgModuleFactory, (or a string when using@ngtools/webpack
loader’s magic encantation)This allows
ngc
to statically analyze the dependency tree, finding child modules vialoadChildren
and then statically analyzing the child module recursively.Just wanted to mention that there is some further conversation and workarounds in this thread: https://github.com/angular/angular/issues/18093.