Repository navigation
fix(ivy): don't load factories when using Ivy #28685
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -10,6 +10,7 @@ import {Compiler, SystemJsNgModuleLoader} from '@angular/core'; | |
| import {global} from '@angular/core/src/util/global'; | ||
| import {async} from '@angular/core/testing'; | ||
| import {afterEach, beforeEach, describe, expect, it} from '@angular/core/testing/src/testing_internal'; | ||
| import {modifiedInIvy, onlyInIvy} from '@angular/private/testing'; | ||
|
|
||
| function mockSystem(modules: {[module: string]: any}) { | ||
| return { | ||
|
|
@@ -20,9 +21,9 @@ function mockSystem(modules: {[module: string]: any}) { | |
| }; | ||
| } | ||
|
|
||
| { | ||
| describe('SystemJsNgModuleLoader', () => { | ||
| let oldSystem: any = null; | ||
| describe('SystemJsNgModuleLoader', () => { | ||
| let oldSystem: any = null; | ||
| modifiedInIvy('only loads ngfactory shims in View Engine').describe('(View Engine)', () => { | ||
| beforeEach(() => { | ||
| oldSystem = global['System']; | ||
| global['System'] = mockSystem({ | ||
|
|
@@ -54,4 +55,26 @@ function mockSystem(modules: {[module: string]: any}) { | |
| }); | ||
| })); | ||
| }); | ||
| } | ||
|
|
||
| onlyInIvy('loads modules directly in Ivy').describe('(Ivy)', () => { | ||
| beforeEach(() => { | ||
| oldSystem = global['System']; | ||
| global['System'] = mockSystem({ | ||
| 'test': {'default': 'test module', 'NamedModule': 'test NamedModule'}, | ||
| }); | ||
| }); | ||
| afterEach(() => { global['System'] = oldSystem; }); | ||
|
|
||
| it('loads a default module', async(() => { | ||
| const loader = new SystemJsNgModuleLoader(new Compiler()); | ||
| loader.load('test').then( | ||
| contents => { expect(contents.moduleType).toBe('test module' as any); }); | ||
| })); | ||
| it('loads a named module', async(() => { | ||
| const loader = new SystemJsNgModuleLoader(new Compiler()); | ||
| loader.load('test#NamedModule').then(contents => { | ||
| expect(contents.moduleType).toBe('test NamedModule' as any); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this is not right.. the loaded type should have been of type NgModuleFactory extracted out of the ES module and class loaded via
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not sure if the code here has changed since this comment, but the test looks correct to me. We're testing that |
||
| }); | ||
| })); | ||
| }); | ||
| }); | ||
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure this is the best way to address lazy loaded Ivy modules.
This way makes it use
this.loadAndCompile, which is just callingNgModuleFactoryR3when in Ivy mode.Other approaches include making a separate Ivy only SystemJsNgModuleLoader , or sending in an option in
SystemJsNgModuleLoaderConfig.@IgorMinar / @kara / @alxhub can you advise?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this change is not right.
You should either enable factory shim generation OR not use NgModuleFactoryLoader.
But nobody should disable (or not enable) shimming and still try to use NgModuleFactoryLoader.
The reasoning being that if you don't want shims, then you should just use
import()to lazy load code and not use our custom loaders that increase the complexity of the code.Projects that want to be able to switch back and forth between the legacy and ivy pipelines should enable shims and use NgModuleFactoryLoader until they permanently switch over to Ivy.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think that the only other option we should entertain is that in ivy we would load the path and extract the factory from the NgModule loaded via
this.loadFactory(path)- but I'm not sure if we can do that reliably.. maybe we can??? in that case one could switch back and forth between ivy and legacy without having to enable factory shimming....Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe that could be done. But it would also require tooling (like the CLI, or others) to understand that there is another difference in behaviour for the
loadChildren/NgModuleFactoryLoaderinteraction.Let's list the existing interactions for clarity though. I also tested all of these with the latest snapshots and without any of the changes in this PR.
Assuming the following
loadChildren:The VE ones are:
lazy.module.js(works now)lazy.module.ngfactory.js(works now)With Ivy and
allowEmptyCodegenFiles: trueit should be the same as above:lazy.module.jsloadFactoryis used but there is no.ngfactory.jslazy.module.ngfactory.js(works now)With Ivy and
allowEmptyCodegenFiles: false:loadChildren: './lazy.module#LazyModule',is replaced withloadChildren: () => import('./lazy/lazy.module').then(m => m.LazyModule),, thenNgModuleFactoryLoaderis not used at all (works now).All of these interactions work with no changes today, with the exception of jit+ivy+
allowEmptyCodegenFiles: true(which should be fixed).The novel one you are proposing is Ivy and
allowEmptyCodegenFiles: false, withloadChildren: './lazy.module#LazyModule',:lazy.module.jsloadFactoryis used but there is no.ngfactory.jslazy.module.jsloadFactoryis used but there is no.ngfactory.jsI think the new case is possible to implement.
But supporting it implies there is still a magic
importcall in the angular/core codebase that must be special cased by tooling, which is used wheneverloadChildrenhas a string argument.Angular CLI currently special cases the
System.import()call withinNgModuleFactoryLoaderby:compiler-clithat lists lazy routesimport()All of this adds to the rebuild times and requires maintenance. Not supporting the
loadChildrenstring format would thus make builds faster, require less maintenance on our code, and require no special casing on third party tooling. So it sounds like a better long-term strategy to not have it.The spanner in this approach is that
allowEmptyCodegenFiles: trueis also being used to signal support for theloadChildrenstring format in Ivy. So while this mode exists, the magicimport()must be supported.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added tests for all these Ivy lazy load variations in angular/angular-cli#13774.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am looking at ways to support the
allowEmptyCodegenFilesvariations in Ivy but think there's a fundamental problem with that approach:allowEmptyCodegenFilesis a compile-time option but usingSystemJsNgModuleLoaderis runtime behaviour, and I'm not sure there is a way to transfer that information over.As far as I can tell
SystemJsNgModuleLoaderis always provided by@angular/router.The injected
Compilerinstance thatSystemJsNgModuleLoaderuses is provided by@angular/core. In VE, it is overriden in@angular/platform-browser-dynamic, but not in Ivy. So Ivy always uses the@angular/coreCompiler.SystemJsNgModuleLoaderchecks what kindCompilerto determine whether it should try to load.ngfactory.jsfiles. If it has the@angular/coreone, it loads them, otherwise it doesn't.In Ivy it has the same
Compilerin JIT and AOT, so always tries to load.ngfactory.js.In VE the what determined the existence of
.ngfactory.jsfiles was JIT vs AOT. The way that was communicated to the runtime Angular execution was through the used platform:platform-browser-dynamicmeant JIT, andplatform-browsermeant AOT.But in Ivy what determines the existence of factories is not just JIT vs AOT, but rather an additional AOT-only compile-time option:
allowEmptyCodegenFiles: true. There is currently no way to communicate this option to the runtime, thus it cannot determine if.ngfactory.jsfiles should be loaded.This leads to a couple of choices:
If we want
allowEmptyCodegenFiles: trueto serve as a switch within Ivy to support loading.ngfactory.jsfiles, then that information must be provided to the runtime.If we want
allowEmptyCodegenFiles: trueto generate.ngfactory.jsfiles but not load them, then in IvySystemJsNgModuleLoadershould not be provided.If we want to support the
loadChildrenstring format in Ivy, aNgModuleFactoryLoadermust be provided that contains animportstatement. Thisimportstatement must be special-cased by tooling.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Another advantage of only supporting the
loadChildrenimport()format in Ivy is that tooling TS configuration becomes simplified.We can point the
tsconfigfilesproperty atmain.tsand TS will figure out thatimport()calls include those files as well. This removes the complicatedinclude/excludesetups we have.Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So much this. Looking at the flamechart on recompile in our project with ivy i'm so confused:
Isn't one of the ideas behind ivy that it compiles file2file and doesn't need the knowledge of it's surroundings? And lazy loading is a solved problem in webpack and very fast.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@sod I'm not really sure what you're trying to highlight in that flamechart. I don't see anything about lazy routes there.