Skip to content

feat(ivy): emit module scope metadata using pure function call - #29598

Closed
JoostK wants to merge 4 commits into
angular:masterfrom
JoostK:ngtsc-extern-module-def
Closed

JoostK wants to merge 4 commits into
angular:masterfrom
JoostK:ngtsc-extern-module-def

Conversation

@JoostK

@JoostK JoostK commented Mar 29, 2019 •

Copy link
Copy Markdown
Member

Prior to this change, all module metadata would be included in the
defineNgModule call that is set as the ngModuleDef field of module
types. Part of the metadata is scope information like declarations,
imports and exports that is used for computing the transitive module
scope in JIT environments, preventing those references from being
tree-shaken for production builds.

This change moves the metadata for scope computations to a pure function
call that patches the scope references onto the module type. Because the
function is marked pure, it may be tree-shaken out during production builds
such that references to declarations and exports are dropped, which in turn
allows for tree-shaken any declaration that is not otherwise referenced.

Fixes #28077, FW-1035


feat(ivy): exclude declarations from injector imports
Prior to this change, a module's imports and exports would be used verbatim
as an injectors' imports. This is detrimental for tree-shaking, as a
module's exports could reference declarations that would then prevent such
declarations from being eligible for tree-shaking.

Since an injector actually only needs NgModule references as its imports,
we may safely filter out any declarations from the list of module exports.
This makes them eligible for tree-shaking once again.


feat(ivy): do not emit empty providers/imports for defineInjector
The defineInjector function specifies its providers and imports array to
be optional, so if no providers/imports are present these keys may be
omitted. This commit updates the compiler to only generate the keys when
necessary.

@ngbot ngbot Bot added this to the needsTriage milestone Mar 29, 2019
@JoostK JoostK self-assigned this Mar 29, 2019
@JoostK
JoostK force-pushed the ngtsc-extern-module-def branch 2 times, most recently from 8ebef16 to 4041f11 Compare March 29, 2019 21:10
JoostK added 4 commits March 30, 2019 15:53
Prior to this change, all module metadata would be included in the
`defineNgModule` call that is set as the `ngModuleDef` field of module
types. Part of the metadata is scope information like declarations,
imports and exports that is used for computing the transitive module
scope in JIT environments, preventing those references from being
tree-shaken for production builds.

This change moves the metadata for scope computations to a pure function
call that patches the scope references onto the module type. Because the
function is marked pure, it may be tree-shaken out during production builds
such that references to declarations and exports are dropped, which in turn
allows for tree-shaken any declaration that is not otherwise referenced.

Fixes angular#28077, FW-1035
Prior to this change, a module's imports and exports would be used verbatim
as an injectors' imports. This is detrimental for tree-shaking, as a
module's exports could reference declarations that would then prevent such
declarations from being eligible for tree-shaking.

Since an injector actually only needs NgModule references as its imports,
we may safely filter out any declarations from the list of module exports.
This makes them eligible for tree-shaking once again.
The defineInjector function specifies its providers and imports array to
be optional, so if no providers/imports are present these keys may be
omitted. This commit updates the compiler to only generate the keys when
necessary.
@JoostK
JoostK force-pushed the ngtsc-extern-module-def branch from f7f5a9a to 9c94276 Compare March 30, 2019 14:54
if (scope !== null) {
const context = getSourceFile(node);
for (const exportRef of analysis.exports) {
if (isNgModule(exportRef.node, scope.compilation)) {

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I am not too certain about using the module scope to figure out if the ref is a module. It does however conveniently provide us with information on both refs from the program, as well as resolved refs from typings files.

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 actually like the approach of filtering out any exports from the scope, instead of filtering and including only known modules. It feels more robust.

.toContain(
'i0.defineInjector({ factory: function TestModule_Factory(t) ' +
'{ return new (t || TestModule)(); }, imports: [[OtherModule, RouterModule.forRoot()],' +
'\n OtherModule,\n RouterModule] });');

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.

Using the ts.Printer, you're going to get whitespace attached to existing nodes.

@JoostK
JoostK marked this pull request as ready for review March 30, 2019 17:32
@JoostK
JoostK requested review from a team March 30, 2019 17:32
@JoostK JoostK added action: review The PR is still awaiting reviews from at least one requested reviewer target: major This PR is targeted for the next major release labels Mar 31, 2019
@JoostK JoostK removed their assignment Mar 31, 2019
@angular angular deleted a comment from JoostK Apr 2, 2019

const jsContents = env.getContents('test.js');
expect(jsContents)
.toContain(

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.

We don't need to match the whole structure of defineInjector, it's enough to assert that it contains imports: [...]. You can also use a regex to ignore the whitespace.

if (scope !== null) {
const context = getSourceFile(node);
for (const exportRef of analysis.exports) {
if (isNgModule(exportRef.node, scope.compilation)) {

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 actually like the approach of filtering out any exports from the scope, instead of filtering and including only known modules. It feels more robust.

.toContain(
'i0.defineInjector({ factory: function TestModule_Factory(t) ' +
'{ return new (t || TestModule)(); }, imports: [[OtherModule, RouterModule.forRoot()],' +
'\n OtherModule,\n RouterModule] });');

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.

Using the ts.Printer, you're going to get whitespace attached to existing nodes.

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

payload-limits change looks fantastic to me (we probably shouldn't require separate reviewers for this file, fyi @ocombe )

@kara kara removed the action: review The PR is still awaiting reviews from at least one requested reviewer label Apr 2, 2019
@kara

kara commented Apr 2, 2019

Copy link
Copy Markdown
Contributor

presubmit

@kara kara added action: merge The PR is ready for merge by the caretaker action: presubmit The PR is in need of a google3 presubmit labels Apr 2, 2019
@alxhub

alxhub commented Apr 2, 2019

Copy link
Copy Markdown
Member

Presubmit

@jasonaden jasonaden closed this in 45c6360 Apr 2, 2019
jasonaden pushed a commit that referenced this pull request Apr 2, 2019
Prior to this change, a module's imports and exports would be used verbatim
as an injectors' imports. This is detrimental for tree-shaking, as a
module's exports could reference declarations that would then prevent such
declarations from being eligible for tree-shaking.

Since an injector actually only needs NgModule references as its imports,
we may safely filter out any declarations from the list of module exports.
This makes them eligible for tree-shaking once again.

PR Close #29598
jasonaden pushed a commit that referenced this pull request Apr 2, 2019
…9598)

The defineInjector function specifies its providers and imports array to
be optional, so if no providers/imports are present these keys may be
omitted. This commit updates the compiler to only generate the keys when
necessary.

PR Close #29598
wKoza pushed a commit to wKoza/angular that referenced this pull request Apr 17, 2019
…ar#29598)

Prior to this change, all module metadata would be included in the
`defineNgModule` call that is set as the `ngModuleDef` field of module
types. Part of the metadata is scope information like declarations,
imports and exports that is used for computing the transitive module
scope in JIT environments, preventing those references from being
tree-shaken for production builds.

This change moves the metadata for scope computations to a pure function
call that patches the scope references onto the module type. Because the
function is marked pure, it may be tree-shaken out during production builds
such that references to declarations and exports are dropped, which in turn
allows for tree-shaken any declaration that is not otherwise referenced.

Fixes angular#28077, FW-1035

PR Close angular#29598
wKoza pushed a commit to wKoza/angular that referenced this pull request Apr 17, 2019
Prior to this change, a module's imports and exports would be used verbatim
as an injectors' imports. This is detrimental for tree-shaking, as a
module's exports could reference declarations that would then prevent such
declarations from being eligible for tree-shaking.

Since an injector actually only needs NgModule references as its imports,
we may safely filter out any declarations from the list of module exports.
This makes them eligible for tree-shaking once again.

PR Close angular#29598
wKoza pushed a commit to wKoza/angular that referenced this pull request Apr 17, 2019
…gular#29598)

The defineInjector function specifies its providers and imports array to
be optional, so if no providers/imports are present these keys may be
omitted. This commit updates the compiler to only generate the keys when
necessary.

PR Close angular#29598
@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
@JoostK
JoostK deleted the ngtsc-extern-module-def branch September 15, 2019 13:05
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 action: presubmit The PR is in need of a google3 presubmit 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.

Ivy compiler output of CommonModule is not optimal for tree-shaking

5 participants