Skip to content

Commit 005ad65

Browse files
fix(core): prevent omission of deferred pipes in full compilation (angular#60571)
This prevents a bug where pipes would be excluded from defer dependency generation. PR Close angular#60571
1 parent cbd6ec8 commit 005ad65

File tree

2 files changed

+50
-23
lines changed

2 files changed

+50
-23
lines changed

packages/compiler-cli/src/ngtsc/annotations/component/src/handler.ts

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1254,21 +1254,27 @@ export class ComponentDecoratorHandler
12541254
}
12551255
}
12561256

1257-
// Set up the R3TargetBinder, as well as a 'directives' array and a 'pipes' map that are
1258-
// later fed to the TemplateDefinitionBuilder.
1257+
// Set up the R3TargetBinder.
12591258
const binder = createTargetBinder(dependencies);
1260-
const pipes = extractPipes(dependencies);
12611259

12621260
let allDependencies = dependencies;
12631261
let deferBlockBinder = binder;
12641262

12651263
// If there are any explicitly deferred dependencies (via `@Component.deferredImports`),
1266-
// re-compute the list of dependencies and create a new binder for defer blocks.
1264+
// re-compute the list of dependencies and create a new binder for defer blocks. This
1265+
// is because we have deferred dependencies that are not in the standard imports list
1266+
// and need to be referenced later when determining what dependencies need to be in a
1267+
// defer function / instruction call. Otherwise they end up treated as a standard
1268+
// import, which is wrong.
12671269
if (explicitlyDeferredDependencies.length > 0) {
12681270
allDependencies = [...explicitlyDeferredDependencies, ...dependencies];
12691271
deferBlockBinder = createTargetBinder(allDependencies);
12701272
}
12711273

1274+
// Set up the pipes map that is later used to determine which dependencies are used in
1275+
// the template.
1276+
const pipes = extractPipes(allDependencies);
1277+
12721278
// Next, the component template AST is bound using the R3TargetBinder. This produces a
12731279
// BoundTarget, which is similar to a ts.TypeChecker.
12741280
const bound = binder.bind({template: metadata.template.nodes});
@@ -1313,10 +1319,10 @@ export class ComponentDecoratorHandler
13131319
// including all defer blocks.
13141320
const wholeTemplateUsed = new Set<ClassDeclaration>(eagerlyUsed);
13151321
for (const bound of deferBlocks.values()) {
1316-
for (const dir of bound.getEagerlyUsedDirectives()) {
1322+
for (const dir of bound.getUsedDirectives()) {
13171323
wholeTemplateUsed.add(dir.ref.node);
13181324
}
1319-
for (const name of bound.getEagerlyUsedPipes()) {
1325+
for (const name of bound.getUsedPipes()) {
13201326
if (!pipes.has(name)) {
13211327
continue;
13221328
}

packages/compiler-cli/test/ngtsc/defer_spec.ts

Lines changed: 38 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -884,7 +884,6 @@ runInEachFileSystem(() => {
884884
'deferred-a.ts',
885885
`
886886
import {Component} from '@angular/core';
887-
888887
@Component({
889888
standalone: true,
890889
selector: 'deferred-cmp-a',
@@ -899,7 +898,6 @@ runInEachFileSystem(() => {
899898
'deferred-b.ts',
900899
`
901900
import {Component} from '@angular/core';
902-
903901
@Component({
904902
standalone: true,
905903
selector: 'deferred-cmp-b',
@@ -910,58 +908,81 @@ runInEachFileSystem(() => {
910908
`,
911909
);
912910

911+
env.write(
912+
'pipe-a.ts',
913+
`
914+
import {Pipe} from '@angular/core';
915+
@Pipe({
916+
name: 'pipea',
917+
})
918+
export class PipeA {
919+
}
920+
`,
921+
);
922+
913923
env.write(
914924
'test.ts',
915925
`
916926
import {Component} from '@angular/core';
917927
import {DeferredCmpA} from './deferred-a';
918928
import {DeferredCmpB} from './deferred-b';
919-
929+
import {PipeA} from './pipe-a';
920930
@Component({
921931
standalone: true,
922932
// @ts-ignore
923-
deferredImports: [DeferredCmpA, DeferredCmpB],
933+
deferredImports: [DeferredCmpA, DeferredCmpB, PipeA],
924934
template: \`
925-
@defer {
926-
<deferred-cmp-a />
927-
}
928-
@defer {
929-
<deferred-cmp-b />
935+
@for (item of items; track item) {
936+
@if (true) {
937+
@defer {
938+
{{ 'Hi!' | pipea }}
939+
<deferred-cmp-a />
940+
}
941+
@defer {
942+
<deferred-cmp-b />
943+
}
944+
}
930945
}
931946
\`,
932947
})
933948
export class AppCmp {
949+
items = [1,2,3];
934950
}
935951
`,
936952
);
937953

938954
env.driveMain();
939955
const jsContents = env.getContents('test.js');
940956

941-
// Expect that all deferrableImports become dynamic imports.
957+
// Expect that all deferrableImports in local compilation mode
958+
// are located in a single function (since we can't detect in
959+
// the local mode which components belong to which block).
942960
expect(jsContents).toContain(
943-
'const AppCmp_Defer_1_DepsFn = () => [' +
944-
'import("./deferred-a").then(m => m.DeferredCmpA)];',
961+
'const AppCmp_For_1_Conditional_0_Defer_1_DepsFn = () => [' +
962+
'import("./deferred-a").then(m => m.DeferredCmpA), ' +
963+
'import("./pipe-a").then(m => m.PipeA)];',
945964
);
946965
expect(jsContents).toContain(
947-
'const AppCmp_Defer_4_DepsFn = () => [' +
966+
'const AppCmp_For_1_Conditional_0_Defer_4_DepsFn = () => [' +
948967
'import("./deferred-b").then(m => m.DeferredCmpB)];',
949968
);
950969

951970
// Make sure there are no eager imports present in the output.
952971
expect(jsContents).not.toContain(`from './deferred-a'`);
953972
expect(jsContents).not.toContain(`from './deferred-b'`);
973+
expect(jsContents).not.toContain(`from './pipe-a'`);
954974

955-
// Defer instructions have different dependency functions in full mode.
956-
expect(jsContents).toContain('ɵɵdefer(1, 0, AppCmp_Defer_1_DepsFn);');
957-
expect(jsContents).toContain('ɵɵdefer(4, 3, AppCmp_Defer_4_DepsFn);');
975+
// There's 2 separate defer instructions due to the two separate defer blocks
976+
expect(jsContents).toContain('ɵɵdefer(1, 0, AppCmp_For_1_Conditional_0_Defer_1_DepsFn);');
977+
expect(jsContents).toContain('ɵɵdefer(4, 3, AppCmp_For_1_Conditional_0_Defer_4_DepsFn);');
958978

959979
// Expect `ɵsetClassMetadataAsync` to contain dynamic imports too.
960980
expect(jsContents).toContain(
961981
'ɵsetClassMetadataAsync(AppCmp, () => [' +
962982
'import("./deferred-a").then(m => m.DeferredCmpA), ' +
983+
'import("./pipe-a").then(m => m.PipeA), ' +
963984
'import("./deferred-b").then(m => m.DeferredCmpB)], ' +
964-
'(DeferredCmpA, DeferredCmpB) => {',
985+
'(DeferredCmpA, PipeA, DeferredCmpB) => {',
965986
);
966987
});
967988

0 commit comments

Comments
 (0)