Skip to content

fix(ivy): don't load factories when using Ivy - #28685

Closed
filipesilva wants to merge 2 commits into
angular:masterfrom
filipesilva:ivy-ngfactories
Closed

filipesilva wants to merge 2 commits into
angular:masterfrom
filipesilva:ivy-ngfactories

Conversation

@filipesilva

@filipesilva filipesilva commented Feb 13, 2019 •

Copy link
Copy Markdown
Contributor

Related to https://github.com/angular/angular/pull/26947/files#r251221208
Related to angular/angular-cli#13524

PR Checklist

Please check if your PR fulfills the following requirements:

PR Type

What kind of change does this PR introduce?

  • Bugfix
  • Feature
  • Code style update (formatting, local variables)
  • Refactoring (no functional changes, no api changes)
  • Build related changes
  • CI related changes
  • Documentation content changes
  • angular.io application / infrastructure changes
  • Other... Please describe:

What is the current behavior?

Issue Number: N/A

What is the new behavior?

Does this PR introduce a breaking change?

  • Yes
  • No

Other information

@filipesilva
filipesilva requested a review from a team February 13, 2019 13:11
filipesilva added a commit to filipesilva/angular-cli that referenced this pull request Feb 13, 2019
@filipesilva
filipesilva requested a review from a team February 13, 2019 13:14
@mary-poppins

Copy link
Copy Markdown

You can preview 2eba24e at https://pr28685-2eba24e.ngbuilds.io/.

@filipesilva

Copy link
Copy Markdown
Contributor Author

Related and probably blocked on angular/angular-cli#13667

@mary-poppins

Copy link
Copy Markdown

You can preview 8168a38 at https://pr28685-8168a38.ngbuilds.io/.

@brandonroberts

Copy link
Copy Markdown
Contributor

Also needs to be removed from here https://github.com/angular/angular/blob/master/aio/tools/examples/shared/boilerplate/ivy/cli/src/tsconfig.app.json

Comment thread aio/package.json Resolved
@filipesilva

Copy link
Copy Markdown
Contributor Author

@brandonroberts thanks for the heads up, that's done now.

@mary-poppins

Copy link
Copy Markdown

You can preview 0ff2b72 at https://pr28685-0ff2b72.ngbuilds.io/.

@mary-poppins

Copy link
Copy Markdown

You can preview 17ca113 at https://pr28685-17ca113.ngbuilds.io/.

@mary-poppins

Copy link
Copy Markdown

You can preview 45462bd at https://pr28685-45462bd.ngbuilds.io/.

@filipesilva filipesilva Feb 18, 2019 •

Copy link
Copy Markdown
Contributor Author

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 calling NgModuleFactoryR3 when 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?

Copy link
Copy Markdown
Contributor

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.

Copy link
Copy Markdown
Contributor

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....

@filipesilva filipesilva Feb 25, 2019 •

Copy link
Copy Markdown
Contributor Author

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)

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/NgModuleFactoryLoader interaction.

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:

    RouterModule.forRoot([{
      path: 'lazy',
      loadChildren: './lazy.module#LazyModule',
    }]),

The VE ones are:

  • without AOT, NgModuleFactoryLoader tries to load lazy.module.js (works now)
  • with AOT, NgModuleFactoryLoader tries to load lazy.module.ngfactory.js (works now)

With Ivy and allowEmptyCodegenFiles: true it should be the same as above:

  • without AOT, NgModuleFactoryLoader tries to load lazy.module.js
    • does not work now, loadFactory is used but there is no .ngfactory.js
  • with AOT, NgModuleFactoryLoader tries to load lazy.module.ngfactory.js (works now)

With Ivy and allowEmptyCodegenFiles: false:

  • if loadChildren: './lazy.module#LazyModule', is replaced with loadChildren: () => import('./lazy/lazy.module').then(m => m.LazyModule),, then NgModuleFactoryLoader is 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, with loadChildren: './lazy.module#LazyModule',:

  • without AOT, NgModuleFactoryLoader tries to load lazy.module.js
    • does not work now, loadFactory is used but there is no .ngfactory.js
  • with AOT, NgModuleFactoryLoader tries to load lazy.module.js
    • does not work now, loadFactory is used but there is no .ngfactory.js

I think the new case is possible to implement.

But supporting it implies there is still a magic import call in the angular/core codebase that must be special cased by tooling, which is used whenever loadChildren has a string argument.

Angular CLI currently special cases the System.import() call within NgModuleFactoryLoader by:

  • calling a private API in compiler-cli that lists lazy routes
  • assembling a map of lazy routes to file names
  • informing the bundler that those file names can be used by the magic import()

All of this adds to the rebuild times and requires maintenance. Not supporting the loadChildren string 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: true is also being used to signal support for the loadChildren string format in Ivy. So while this mode exists, the magic import() must be supported.

Copy link
Copy Markdown
Contributor Author

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.

Copy link
Copy Markdown
Contributor Author

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 allowEmptyCodegenFiles variations in Ivy but think there's a fundamental problem with that approach: allowEmptyCodegenFiles is a compile-time option but using SystemJsNgModuleLoader is runtime behaviour, and I'm not sure there is a way to transfer that information over.

As far as I can tell SystemJsNgModuleLoader is always provided by @angular/router.

The injected Compiler instance that SystemJsNgModuleLoader uses 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/core Compiler.

SystemJsNgModuleLoader checks what kind Compiler to determine whether it should try to load .ngfactory.js files. If it has the @angular/core one, it loads them, otherwise it doesn't.

In Ivy it has the same Compiler in JIT and AOT, so always tries to load .ngfactory.js.

In VE the what determined the existence of .ngfactory.js files was JIT vs AOT. The way that was communicated to the runtime Angular execution was through the used platform: platform-browser-dynamic meant JIT, and platform-browser meant 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.js files should be loaded.

This leads to a couple of choices:

If we want allowEmptyCodegenFiles: true to serve as a switch within Ivy to support loading .ngfactory.js files, then that information must be provided to the runtime.

If we want allowEmptyCodegenFiles: true to generate .ngfactory.js files but not load them, then in Ivy SystemJsNgModuleLoader should not be provided.

If we want to support the loadChildren string format in Ivy, a NgModuleFactoryLoader must be provided that contains an import statement. This import statement must be special-cased by tooling.

Copy link
Copy Markdown
Contributor Author

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 loadChildren import() format in Ivy is that tooling TS configuration becomes simplified.

We can point the tsconfig files property at main.ts and TS will figure out that import() calls include those files as well. This removes the complicated include/exclude setups we have.

@sod sod Mar 5, 2019 •

Copy link
Copy Markdown
Contributor

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:

image

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.

Copy link
Copy Markdown
Contributor Author

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.

@ngbot ngbot Bot added this to the needsTriage milestone Feb 20, 2019
@filipesilva filipesilva mentioned this pull request Feb 21, 2019
3 of 14 tasks
@filipesilva filipesilva added action: review The PR is still awaiting reviews from at least one requested reviewer and removed state: WIP labels Feb 21, 2019

@IgorMinar IgorMinar left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is almost good.. :-)

please see my comments about the issues I think I found. Once those are resolved, the rest looks great.

Comment thread aio/scripts/_payload-limits.json Outdated

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes!!

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

did you check that we have no more of these flags elsewhere in the repo? (unless the test specifically tests the shims?) I usually use git grep for such checks

Copy link
Copy Markdown
Contributor

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.

Copy link
Copy Markdown
Contributor

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....

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The 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 test#NamedModule. This test proves that we are not extracting the factory but simply returning the loaded class instead and that's not right.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The 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 load has created the NgModuleFactory wrapper, and that the moduleType (the original "class" that was dynamically loaded) is the one we expected it to wrap.

@IgorMinar IgorMinar added action: cleanup The PR is in need of cleanup, either due to needing a rebase or in response to comments from reviews target: major This PR is targeted for the next major release and removed action: review The PR is still awaiting reviews from at least one requested reviewer labels Feb 23, 2019
@IgorMinar IgorMinar self-assigned this Feb 23, 2019
@IgorMinar

Copy link
Copy Markdown
Contributor

Discussed this in person with @filipesilva, he's going to do some more exploration of supporting the import() in the legacy (ngc+viewengine) pipeline.

If that's feasible then we'll implement this in a way where in Ivy we'll always require the import() syntax to be used. NgModuleFactoryLoader api would be not work in Ivy and we'd deprecate it and removed in the future in favor of the standard import() api.

@filipesilva

Copy link
Copy Markdown
Contributor Author

@IgorMinar as far as I can tell there are two things stopping users from using import() in loadChildren in View Engine:

With both of those PRs in, the following syntax will work in ViewEngine and Ivy, both for JIT and AOT:

const routes = [{
  path: 'lazy',
  loadChildren: () => import('./lazy/lazy.module').then(m => m.LazyModule)
}];    

Please see angular/angular-cli#13948 for limitations of this transform.

@alexeagle

Copy link
Copy Markdown
Contributor

According to @alxhub we ought to move this ahead and land it. This would give us the option of supporting loadChildren:string in Ivy, which would be good if we find that the migration to dynamic import breaks users. @filipesilva could you rebase and clean up the unneeded version bumps?

@mary-poppins

Copy link
Copy Markdown

You can preview be21c3f at https://pr28685-be21c3f.ngbuilds.io/.

@googlebot

Copy link
Copy Markdown

So there's good news and bad news.

👍 The good news is that everyone that needs to sign a CLA (the pull request submitter and all commit authors) have done so. Everything is all good there.

😕 The bad news is that it appears that one or more commits were authored or co-authored by someone other than the pull request submitter. We need to confirm that all authors are ok with their commits being contributed to this project. Please have them confirm that here in the pull request.

Note to project maintainer: This is a terminal state, meaning the cla/google commit status will not change from this state. It's up to you to confirm consent of all the commit author(s), set the cla label to yes (if enabled on your project), and then merge this pull request when appropriate.

ℹ️ Googlers: Go here for more info.

@googlebot googlebot added cla: no and removed cla: yes labels Apr 9, 2019
@mary-poppins

Copy link
Copy Markdown

You can preview f66bc93 at https://pr28685-f66bc93.ngbuilds.io/.

@googlebot

Copy link
Copy Markdown

CLAs look good, thanks!

ℹ️ Googlers: Go here for more info.

@googlebot googlebot added cla: yes and removed cla: no labels Apr 9, 2019
@mary-poppins

Copy link
Copy Markdown

You can preview 1a31e55 at https://pr28685-1a31e55.ngbuilds.io/.

@mary-poppins

Copy link
Copy Markdown

You can preview 4d37a06 at https://pr28685-4d37a06.ngbuilds.io/.

@filipesilva filipesilva added action: review The PR is still awaiting reviews from at least one requested reviewer and removed action: cleanup The PR is in need of cleanup, either due to needing a rebase or in response to comments from reviews labels Apr 9, 2019
@filipesilva
filipesilva requested a review from alxhub April 9, 2019 11:44
@alxhub
alxhub dismissed IgorMinar’s stale review April 12, 2019 17:39

After lengthy discussion, we feel this change is actually the right one.

@kara kara added action: presubmit The PR is in need of a google3 presubmit and removed action: review The PR is still awaiting reviews from at least one requested reviewer labels Apr 12, 2019
@kara

kara commented Apr 12, 2019

Copy link
Copy Markdown
Contributor

new presubmit

@alxhub alxhub added action: merge The PR is ready for merge by the caretaker and removed action: presubmit The PR is in need of a google3 presubmit labels Apr 12, 2019
@alxhub alxhub closed this in bc6c671 Apr 12, 2019
alxhub pushed a commit that referenced this pull request Apr 12, 2019
wKoza pushed a commit to wKoza/angular that referenced this pull request Apr 17, 2019
@angular-automatic-lock-bot

Copy link
Copy Markdown

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 14, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

action: merge The PR is ready for merge by the caretaker cla: yes target: major This PR is targeted for the next major release

Projects

None yet

Development

Successfully merging this pull request may close these issues.

10 participants