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:
@ -442,10 +442,9 @@ describe('ngtsc behavioral tests', () => {
|
||||
env.driveMain();
|
||||
|
||||
const jsContents = env.getContents('test.js');
|
||||
expect(jsContents).toContain('i0.ɵdefineNgModule({ type: TestModule, bootstrap: [TestCmp] });');
|
||||
expect(jsContents)
|
||||
.toContain(
|
||||
'i0.ɵdefineNgModule({ type: TestModule, bootstrap: [TestCmp], ' +
|
||||
'declarations: [TestCmp] })');
|
||||
.toContain('/*@__PURE__*/ i0.ɵsetNgModuleScope(TestModule, { declarations: [TestCmp] });');
|
||||
|
||||
const dtsContents = env.getContents('test.d.ts');
|
||||
expect(dtsContents)
|
||||
@ -457,6 +456,22 @@ describe('ngtsc behavioral tests', () => {
|
||||
expect(dtsContents).not.toContain('__decorate');
|
||||
});
|
||||
|
||||
it('should not emit a setNgModuleScope call when no scope metadata is present', () => {
|
||||
env.tsconfig();
|
||||
env.write('test.ts', `
|
||||
import {NgModule} from '@angular/core';
|
||||
|
||||
@NgModule({})
|
||||
export class TestModule {}
|
||||
`);
|
||||
|
||||
env.driveMain();
|
||||
|
||||
const jsContents = env.getContents('test.js');
|
||||
expect(jsContents).toContain('i0.ɵdefineNgModule({ type: TestModule });');
|
||||
expect(jsContents).not.toContain('ɵsetNgModuleScope(TestModule,');
|
||||
});
|
||||
|
||||
it('should compile NgModules with services without errors', () => {
|
||||
env.tsconfig();
|
||||
env.write('test.ts', `
|
||||
@ -484,7 +499,7 @@ describe('ngtsc behavioral tests', () => {
|
||||
env.driveMain();
|
||||
|
||||
const jsContents = env.getContents('test.js');
|
||||
expect(jsContents).toContain('i0.ɵdefineNgModule({ type: TestModule,');
|
||||
expect(jsContents).toContain('i0.ɵdefineNgModule({ type: TestModule });');
|
||||
expect(jsContents)
|
||||
.toContain(
|
||||
`TestModule.ngInjectorDef = i0.defineInjector({ factory: ` +
|
||||
@ -525,7 +540,7 @@ describe('ngtsc behavioral tests', () => {
|
||||
env.driveMain();
|
||||
|
||||
const jsContents = env.getContents('test.js');
|
||||
expect(jsContents).toContain('i0.ɵdefineNgModule({ type: TestModule,');
|
||||
expect(jsContents).toContain('i0.ɵdefineNgModule({ type: TestModule });');
|
||||
expect(jsContents)
|
||||
.toContain(
|
||||
`TestModule.ngInjectorDef = i0.defineInjector({ factory: ` +
|
||||
@ -570,7 +585,7 @@ describe('ngtsc behavioral tests', () => {
|
||||
env.driveMain();
|
||||
|
||||
const jsContents = env.getContents('test.js');
|
||||
expect(jsContents).toContain('i0.ɵdefineNgModule({ type: TestModule,');
|
||||
expect(jsContents).toContain('i0.ɵdefineNgModule({ type: TestModule });');
|
||||
expect(jsContents)
|
||||
.toContain(
|
||||
`TestModule.ngInjectorDef = i0.defineInjector({ factory: ` +
|
||||
|
@ -21,6 +21,32 @@ describe('ngtsc module scopes', () => {
|
||||
|
||||
describe('diagnostics', () => {
|
||||
describe('imports', () => {
|
||||
it('should emit imports in a pure function call', () => {
|
||||
env.tsconfig();
|
||||
env.write('test.ts', `
|
||||
import {NgModule} from '@angular/core';
|
||||
|
||||
@NgModule({})
|
||||
export class OtherModule {}
|
||||
|
||||
@NgModule({imports: [OtherModule]})
|
||||
export class TestModule {}
|
||||
`);
|
||||
|
||||
env.driveMain();
|
||||
|
||||
const jsContents = env.getContents('test.js');
|
||||
expect(jsContents).toContain('i0.ɵdefineNgModule({ type: TestModule });');
|
||||
expect(jsContents)
|
||||
.toContain(
|
||||
'/*@__PURE__*/ i0.ɵsetNgModuleScope(TestModule, { imports: [OtherModule] });');
|
||||
|
||||
const dtsContents = env.getContents('test.d.ts');
|
||||
expect(dtsContents)
|
||||
.toContain(
|
||||
'static ngModuleDef: i0.ɵNgModuleDefWithMeta<TestModule, never, [typeof OtherModule], never>');
|
||||
});
|
||||
|
||||
it('should produce an error when an invalid class is imported', () => {
|
||||
env.write('test.ts', `
|
||||
import {NgModule} from '@angular/core';
|
||||
@ -57,6 +83,32 @@ describe('ngtsc module scopes', () => {
|
||||
});
|
||||
|
||||
describe('exports', () => {
|
||||
it('should emit exports in a pure function call', () => {
|
||||
env.tsconfig();
|
||||
env.write('test.ts', `
|
||||
import {NgModule} from '@angular/core';
|
||||
|
||||
@NgModule({})
|
||||
export class OtherModule {}
|
||||
|
||||
@NgModule({exports: [OtherModule]})
|
||||
export class TestModule {}
|
||||
`);
|
||||
|
||||
env.driveMain();
|
||||
|
||||
const jsContents = env.getContents('test.js');
|
||||
expect(jsContents).toContain('i0.ɵdefineNgModule({ type: TestModule });');
|
||||
expect(jsContents)
|
||||
.toContain(
|
||||
'/*@__PURE__*/ i0.ɵsetNgModuleScope(TestModule, { exports: [OtherModule] });');
|
||||
|
||||
const dtsContents = env.getContents('test.d.ts');
|
||||
expect(dtsContents)
|
||||
.toContain(
|
||||
'static ngModuleDef: i0.ɵNgModuleDefWithMeta<TestModule, never, never, [typeof OtherModule]>');
|
||||
});
|
||||
|
||||
it('should produce an error when a non-NgModule class is exported', () => {
|
||||
env.write('test.ts', `
|
||||
import {NgModule} from '@angular/core';
|
||||
|
Reference in New Issue
Block a user