Skip to content

Commit 755d2d5

Browse files
karamhevery
authored andcommitted
refactor(ivy): remove unnecessary fac wrapper (angular#34076)
For injectables, we currently generate a factory function in the injectable def (prov) that delegates to the factory function in the factory def (fac). It looks something like this: ``` factory: function(t) { return Svc.fac(t); } ``` The extra wrapper function is unnecessary since the args for the factory functions are the same. This commit changes the compiler to generate this instead: ``` factory: Svc.fac ``` Because we are generating less code for each injectable, we should see some modest code size savings. AIO's main bundle is about 1 KB smaller. PR Close angular#34076
1 parent 02958c0 commit 755d2d5

File tree

7 files changed

+68
-24
lines changed

7 files changed

+68
-24
lines changed

aio/scripts/_payload-limits.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
"master": {
1313
"uncompressed": {
1414
"runtime-es2015": 2987,
15-
"main-es2015": 463671,
15+
"main-es2015": 462449,
1616
"polyfills-es2015": 52503
1717
}
1818
}

integration/_payload-limits.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
"master": {
44
"uncompressed": {
55
"runtime-es2015": 1485,
6-
"main-es2015": 142186,
6+
"main-es2015": 141476,
77
"polyfills-es2015": 36808
88
}
99
}
@@ -21,7 +21,7 @@
2121
"master": {
2222
"uncompressed": {
2323
"runtime-es2015": 1485,
24-
"main-es2015": 148320,
24+
"main-es2015": 147610,
2525
"polyfills-es2015": 36808
2626
}
2727
}

packages/compiler-cli/ngcc/src/analysis/decoration_analyzer.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -94,16 +94,18 @@ export class DecorationAnalyzer {
9494
new DirectiveDecoratorHandler(
9595
this.reflectionHost, this.evaluator, this.fullRegistry, NOOP_DEFAULT_IMPORT_RECORDER,
9696
this.isCore, /* annotateForClosureCompiler */ false),
97+
// Pipe handler must be before injectable handler in list so pipe factories are printed
98+
// before injectable factories (so injectable factories can delegate to them)
99+
new PipeDecoratorHandler(
100+
this.reflectionHost, this.evaluator, this.metaRegistry, NOOP_DEFAULT_IMPORT_RECORDER,
101+
this.isCore),
97102
new InjectableDecoratorHandler(
98103
this.reflectionHost, NOOP_DEFAULT_IMPORT_RECORDER, this.isCore,
99104
/* strictCtorDeps */ false, /* errorOnDuplicateProv */ false),
100105
new NgModuleDecoratorHandler(
101106
this.reflectionHost, this.evaluator, this.fullMetaReader, this.fullRegistry,
102107
this.scopeRegistry, this.referencesRegistry, this.isCore, /* routeAnalyzer */ null,
103108
this.refEmitter, NOOP_DEFAULT_IMPORT_RECORDER, /* annotateForClosureCompiler */ false),
104-
new PipeDecoratorHandler(
105-
this.reflectionHost, this.evaluator, this.metaRegistry, NOOP_DEFAULT_IMPORT_RECORDER,
106-
this.isCore),
107109
];
108110
migrations: Migration[] = [
109111
new UndecoratedParentMigration(),

packages/compiler-cli/ngcc/test/integration/ngcc_spec.ts

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -205,13 +205,43 @@ runInEachFileSystem(() => {
205205
expect(countOccurrences(after, 'ɵfac')).toEqual(1);
206206
});
207207

208+
// This is necessary to ensure XPipeDef.fac is defined when delegated from injectable def
209+
it('should always generate factory def (fac) before injectable def (prov)', () => {
210+
compileIntoFlatEs5Package('test-package', {
211+
'/index.ts': `
212+
import {Injectable, Pipe, PipeTransform} from '@angular/core';
213+
214+
@Injectable()
215+
@Pipe({
216+
name: 'myTestPipe'
217+
})
218+
export class TestClass implements PipeTransform {
219+
transform(value: any) { return value; }
220+
}
221+
`,
222+
});
223+
224+
mainNgcc({
225+
basePath: '/node_modules',
226+
targetEntryPointPath: 'test-package',
227+
propertiesToConsider: ['main'],
228+
});
229+
230+
const jsContents = fs.readFile(_(`/node_modules/test-package/index.js`));
231+
expect(jsContents)
232+
.toContain(
233+
`TestClass.ɵfac = function TestClass_Factory(t) { return new (t || TestClass)(); };\n` +
234+
`TestClass.ɵpipe = ɵngcc0.ɵɵdefinePipe({ name: "myTestPipe", type: TestClass, pure: true });\n` +
235+
`TestClass.ɵprov = ɵngcc0.ɵɵdefineInjectable({`);
236+
});
237+
208238
it('should add generic type for ModuleWithProviders and generate exports for private modules',
209239
() => {
210240
compileIntoApf('test-package', {
211241
'/index.ts': `
212242
import {ModuleWithProviders} from '@angular/core';
213243
import {InternalFooModule} from './internal';
214-
244+
215245
export class FooModule {
216246
static forRoot(): ModuleWithProviders {
217247
return {
@@ -222,7 +252,7 @@ runInEachFileSystem(() => {
222252
`,
223253
'/internal.ts': `
224254
import {NgModule} from '@angular/core';
225-
255+
226256
@NgModule()
227257
export class InternalFooModule {}
228258
`,

packages/compiler-cli/src/ngtsc/program.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -629,15 +629,17 @@ export class NgtscProgram implements api.Program {
629629
new DirectiveDecoratorHandler(
630630
this.reflector, evaluator, metaRegistry, this.defaultImportTracker, this.isCore,
631631
this.closureCompilerEnabled),
632+
// Pipe handler must be before injectable handler in list so pipe factories are printed
633+
// before injectable factories (so injectable factories can delegate to them)
634+
new PipeDecoratorHandler(
635+
this.reflector, evaluator, metaRegistry, this.defaultImportTracker, this.isCore),
632636
new InjectableDecoratorHandler(
633637
this.reflector, this.defaultImportTracker, this.isCore,
634638
this.options.strictInjectionParameters || false),
635639
new NgModuleDecoratorHandler(
636640
this.reflector, evaluator, this.metaReader, metaRegistry, scopeRegistry,
637641
referencesRegistry, this.isCore, this.routeAnalyzer, this.refEmitter,
638642
this.defaultImportTracker, this.closureCompilerEnabled, this.options.i18nInLocale),
639-
new PipeDecoratorHandler(
640-
this.reflector, evaluator, metaRegistry, this.defaultImportTracker, this.isCore),
641643
];
642644

643645
return new IvyCompilation(

packages/compiler-cli/test/compliance/r3_view_compiler_di_spec.ts

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -90,9 +90,7 @@ describe('compiler compliance: dependency injection', () => {
9090
const def = `
9191
MyService.ɵprov = $r3$.ɵɵdefineInjectable({
9292
token: MyService,
93-
factory: function(t) {
94-
return MyService.ɵfac(t);
95-
},
93+
factory: MyService.ɵfac,
9694
providedIn: null
9795
});
9896
`;
@@ -342,16 +340,22 @@ describe('compiler compliance: dependency injection', () => {
342340
const result = compile(files, angularFiles);
343341
const source = result.source;
344342

345-
const MyPipeFactory = `
346-
MyPipe.ɵfac = function MyPipe_Factory(t) { return new (t || MyPipe)($r3$.ɵɵdirectiveInject(Service)); };
343+
// The prov definition must be last so MyPipe.fac is defined
344+
const MyPipeDefs = `
345+
MyPipe.ɵfac = function MyPipe_Factory(t) { return new (t || MyPipe)(i0.ɵɵdirectiveInject(Service)); };
346+
MyPipe.ɵpipe = i0.ɵɵdefinePipe({ name: "myPipe", type: MyPipe, pure: true });
347+
MyPipe.ɵprov = i0.ɵɵdefineInjectable({ token: MyPipe, factory: MyPipe.ɵfac, providedIn: null });
347348
`;
348349

349-
const MyOtherPipeFactory = `
350+
// The prov definition must be last so MyOtherPipe.fac is defined
351+
const MyOtherPipeDefs = `
350352
MyOtherPipe.ɵfac = function MyOtherPipe_Factory(t) { return new (t || MyOtherPipe)($r3$.ɵɵdirectiveInject(Service)); };
353+
MyOtherPipe.ɵpipe = i0.ɵɵdefinePipe({ name: "myOtherPipe", type: MyOtherPipe, pure: true });
354+
MyOtherPipe.ɵprov = i0.ɵɵdefineInjectable({ token: MyOtherPipe, factory: MyOtherPipe.ɵfac, providedIn: null });
351355
`;
352356

353-
expectEmit(source, MyPipeFactory, 'Invalid pipe factory function');
354-
expectEmit(source, MyOtherPipeFactory, 'Invalid pipe factory function');
357+
expectEmit(source, MyPipeDefs, 'Invalid pipe factory function');
358+
expectEmit(source, MyOtherPipeDefs, 'Invalid pipe factory function');
355359
expect(source.match(/MyPipe\.ɵfac =/g) !.length).toBe(1);
356360
expect(source.match(/MyOtherPipe\.ɵfac =/g) !.length).toBe(1);
357361
});

packages/compiler/src/injectable_compiler_2.ts

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,8 @@ export function compileInjectable(meta: R3InjectableMetadata): InjectableDef {
6868
} else if (useClassOnSelf) {
6969
result = compileFactoryFunction(factoryMeta);
7070
} else {
71-
result = delegateToFactory(meta.useClass);
71+
result = delegateToFactory(
72+
meta.type as o.WrappedNodeExpr<any>, meta.useClass as o.WrappedNodeExpr<any>);
7273
}
7374
} else if (meta.useFactory !== undefined) {
7475
if (meta.userDeps !== undefined) {
@@ -99,7 +100,8 @@ export function compileInjectable(meta: R3InjectableMetadata): InjectableDef {
99100
expression: o.importExpr(Identifiers.inject).callFn([meta.useExisting]),
100101
});
101102
} else {
102-
result = delegateToFactory(meta.internalType);
103+
result = delegateToFactory(
104+
meta.type as o.WrappedNodeExpr<any>, meta.internalType as o.WrappedNodeExpr<any>);
103105
}
104106

105107
const token = meta.internalType;
@@ -117,11 +119,15 @@ export function compileInjectable(meta: R3InjectableMetadata): InjectableDef {
117119
};
118120
}
119121

120-
function delegateToFactory(type: o.Expression) {
122+
function delegateToFactory(type: o.WrappedNodeExpr<any>, internalType: o.WrappedNodeExpr<any>) {
121123
return {
122124
statements: [],
123-
// () => type.ɵfac(t)
124-
factory: o.fn([new o.FnParam('t', o.DYNAMIC_TYPE)], [new o.ReturnStatement(type.callMethod(
125-
'ɵfac', [o.variable('t')]))])
125+
// If types are the same, we can generate `factory: type.ɵfac`
126+
// If types are different, we have to generate a wrapper function to ensure
127+
// the internal type has been resolved (`factory: function(t) { return type.ɵfac(t); }`)
128+
factory: type.node === internalType.node ?
129+
internalType.prop('ɵfac') :
130+
o.fn([new o.FnParam('t', o.DYNAMIC_TYPE)], [new o.ReturnStatement(internalType.callMethod(
131+
'ɵfac', [o.variable('t')]))])
126132
};
127133
}

0 commit comments

Comments
 (0)