feat(ivy): emit module scope metadata using pure function call (#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 #28077, FW-1035

PR Close #29598
This commit is contained in:
JoostK
2019-03-29 21:31:22 +01:00
committed by Jason Aden
parent 6b39c9cf32
commit 45c6360e5a
11 changed files with 214 additions and 38 deletions

View File

@ -487,16 +487,15 @@ export function renderDefinitions(
const name = compiledClass.declaration.name;
const translate = (stmt: Statement) =>
translateStatement(stmt, imports, NOOP_DEFAULT_IMPORT_RECORDER);
const definitions =
compiledClass.compilation
.map(
c => c.statements.map(statement => translate(statement))
.concat(translate(createAssignmentStatement(name, c.name, c.initializer)))
.map(
statement =>
printer.printNode(ts.EmitHint.Unspecified, statement, sourceFile))
.join('\n'))
.join('\n');
const print = (stmt: Statement) =>
printer.printNode(ts.EmitHint.Unspecified, translate(stmt), sourceFile);
const definitions = compiledClass.compilation
.map(
c => [createAssignmentStatement(name, c.name, c.initializer)]
.concat(c.statements)
.map(print)
.join('\n'))
.join('\n');
return definitions;
}

View File

@ -151,16 +151,17 @@ describe('Renderer', () => {
moduleWithProvidersAnalyses);
const addDefinitionsSpy = renderer.addDefinitions as jasmine.Spy;
expect(addDefinitionsSpy.calls.first().args[2])
.toEqual(`/*@__PURE__*/ ɵngcc0.ɵsetClassMetadata(A, [{
type: Component,
args: [{ selector: 'a', template: '{{ person!.name }}' }]
}], null, null);
A.ngComponentDef = ɵngcc0.ɵdefineComponent({ type: A, selectors: [["a"]], factory: function A_Factory(t) { return new (t || A)(); }, consts: 1, vars: 1, template: function A_Template(rf, ctx) { if (rf & 1) {
.toEqual(
`A.ngComponentDef = ɵngcc0.ɵdefineComponent({ type: A, selectors: [["a"]], factory: function A_Factory(t) { return new (t || A)(); }, consts: 1, vars: 1, template: function A_Template(rf, ctx) { if (rf & 1) {
ɵngcc0.ɵtext(0);
} if (rf & 2) {
ɵngcc0.ɵselect(0);
ɵngcc0.ɵtextBinding(0, ɵngcc0.ɵinterpolation1("", ctx.person.name, ""));
} }, encapsulation: 2 });`);
} }, encapsulation: 2 });
/*@__PURE__*/ ɵngcc0.ɵsetClassMetadata(A, [{
type: Component,
args: [{ selector: 'a', template: '{{ person!.name }}' }]
}], null, null);`);
});
@ -195,11 +196,12 @@ A.ngComponentDef = ɵngcc0.ɵdefineComponent({ type: A, selectors: [["a"]], fact
decorators: [jasmine.objectContaining({name: 'Directive'})],
}));
expect(addDefinitionsSpy.calls.first().args[2])
.toEqual(`/*@__PURE__*/ ɵngcc0.ɵsetClassMetadata(A, [{
.toEqual(
`A.ngDirectiveDef = ɵngcc0.ɵdefineDirective({ type: A, selectors: [["", "a", ""]], factory: function A_Factory(t) { return new (t || A)(); } });
/*@__PURE__*/ ɵngcc0.ɵsetClassMetadata(A, [{
type: Directive,
args: [{ selector: '[a]' }]
}], null, { foo: [] });
A.ngDirectiveDef = ɵngcc0.ɵdefineDirective({ type: A, selectors: [["", "a", ""]], factory: function A_Factory(t) { return new (t || A)(); } });`);
}], null, { foo: [] });`);
});
it('should call removeDecorators with the source code, a map of class decorators that have been analyzed',