Skip to content

Commit 6795ccc

Browse files
crisbetodylhunn
authored andcommitted
fix(compiler): account for type-only imports in defer blocks (#52343)
Fixes that `@defer` blocks didn't account for type-only imports which could cause the import to be considered as not deferrable. PR Close #52343
1 parent 96ad3bf commit 6795ccc

File tree

2 files changed

+235
-4
lines changed

2 files changed

+235
-4
lines changed

packages/compiler-cli/src/ngtsc/imports/src/deferred_symbol_tracker.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,12 +44,19 @@ export class DeferredSymbolTracker {
4444
throw new Error(`Provided import declaration doesn't have any symbols.`);
4545
}
4646

47+
// If the entire import is a type-only import, none of the symbols can be eager.
48+
if (importDecl.importClause.isTypeOnly) {
49+
return symbolMap;
50+
}
51+
4752
if (importDecl.importClause.namedBindings !== undefined) {
4853
const bindings = importDecl.importClause.namedBindings;
4954
if (ts.isNamedImports(bindings)) {
5055
// Case 1: `import {a, b as B} from 'a'`
5156
for (const element of bindings.elements) {
52-
symbolMap.set(element.name.text, AssumeEager);
57+
if (!element.isTypeOnly) {
58+
symbolMap.set(element.name.text, AssumeEager);
59+
}
5360
}
5461
} else {
5562
// Case 2: `import X from 'a'`

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

Lines changed: 227 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9107,6 +9107,230 @@ function allTests(os: string) {
91079107
// via dynamic imports and an original import can be removed.
91089108
expect(jsContents).not.toContain('import { CmpA }');
91099109
});
9110+
9111+
it('should drop imports when one is deferrable and the rest are type-only imports', () => {
9112+
env.write('cmp-a.ts', `
9113+
import { Component } from '@angular/core';
9114+
9115+
export class Foo {}
9116+
9117+
@Component({
9118+
standalone: true,
9119+
selector: 'cmp-a',
9120+
template: 'CmpA!'
9121+
})
9122+
export class CmpA {}
9123+
`);
9124+
9125+
env.write('/test.ts', `
9126+
import { Component } from '@angular/core';
9127+
import { CmpA, type Foo } from './cmp-a';
9128+
9129+
export const foo: Foo = {};
9130+
9131+
@Component({
9132+
selector: 'test-cmp',
9133+
standalone: true,
9134+
imports: [CmpA],
9135+
template: \`
9136+
@defer {
9137+
<cmp-a />
9138+
}
9139+
\`,
9140+
})
9141+
export class TestCmp {}
9142+
`);
9143+
9144+
env.driveMain();
9145+
9146+
const jsContents = env.getContents('test.js');
9147+
9148+
expect(jsContents).toContain('ɵɵdefer(1, 0, TestCmp_Defer_1_DepsFn)');
9149+
expect(jsContents).toContain('() => [import("./cmp-a").then(m => m.CmpA)]');
9150+
expect(jsContents).not.toContain('import { CmpA }');
9151+
});
9152+
9153+
it('should drop multiple imports to the same file when one is deferrable and the other has a single type-only element',
9154+
() => {
9155+
env.write('cmp-a.ts', `
9156+
import { Component } from '@angular/core';
9157+
9158+
export class Foo {}
9159+
9160+
@Component({
9161+
standalone: true,
9162+
selector: 'cmp-a',
9163+
template: 'CmpA!'
9164+
})
9165+
export class CmpA {}
9166+
`);
9167+
9168+
env.write('/test.ts', `
9169+
import { Component } from '@angular/core';
9170+
import { CmpA } from './cmp-a';
9171+
import { type Foo } from './cmp-a';
9172+
9173+
export const foo: Foo = {};
9174+
9175+
@Component({
9176+
selector: 'test-cmp',
9177+
standalone: true,
9178+
imports: [CmpA],
9179+
template: \`
9180+
@defer {
9181+
<cmp-a />
9182+
}
9183+
\`,
9184+
})
9185+
export class TestCmp {}
9186+
`);
9187+
9188+
env.driveMain();
9189+
9190+
const jsContents = env.getContents('test.js');
9191+
9192+
expect(jsContents).toContain('ɵɵdefer(1, 0, TestCmp_Defer_1_DepsFn)');
9193+
expect(jsContents).toContain('() => [import("./cmp-a").then(m => m.CmpA)]');
9194+
expect(jsContents).not.toContain('import { CmpA }');
9195+
});
9196+
9197+
it('should drop multiple imports to the same file when one is deferrable and the other is type-only at the declaration level',
9198+
() => {
9199+
env.write('cmp-a.ts', `
9200+
import { Component } from '@angular/core';
9201+
9202+
export class Foo {}
9203+
9204+
@Component({
9205+
standalone: true,
9206+
selector: 'cmp-a',
9207+
template: 'CmpA!'
9208+
})
9209+
export class CmpA {}
9210+
`);
9211+
9212+
env.write('/test.ts', `
9213+
import { Component } from '@angular/core';
9214+
import { CmpA } from './cmp-a';
9215+
import type { Foo, CmpA as CmpAlias } from './cmp-a';
9216+
9217+
export const foo: Foo|CmpAlias = {};
9218+
9219+
@Component({
9220+
selector: 'test-cmp',
9221+
standalone: true,
9222+
imports: [CmpA],
9223+
template: \`
9224+
@defer {
9225+
<cmp-a />
9226+
}
9227+
\`,
9228+
})
9229+
export class TestCmp {}
9230+
`);
9231+
9232+
env.driveMain();
9233+
9234+
const jsContents = env.getContents('test.js');
9235+
9236+
expect(jsContents).toContain('ɵɵdefer(1, 0, TestCmp_Defer_1_DepsFn)');
9237+
expect(jsContents).toContain('() => [import("./cmp-a").then(m => m.CmpA)]');
9238+
expect(jsContents).not.toContain('import { CmpA }');
9239+
});
9240+
9241+
it('should drop multiple imports to the same file when one is deferrable and the other is a type-only import of all symbols',
9242+
() => {
9243+
env.write('cmp-a.ts', `
9244+
import { Component } from '@angular/core';
9245+
9246+
export class Foo {}
9247+
9248+
@Component({
9249+
standalone: true,
9250+
selector: 'cmp-a',
9251+
template: 'CmpA!'
9252+
})
9253+
export class CmpA {}
9254+
`);
9255+
9256+
env.write('/test.ts', `
9257+
import { Component } from '@angular/core';
9258+
import { CmpA } from './cmp-a';
9259+
import type * as allCmpA from './cmp-a';
9260+
9261+
export const foo: allCmpA.Foo|allCmpA.CmpA = {};
9262+
9263+
@Component({
9264+
selector: 'test-cmp',
9265+
standalone: true,
9266+
imports: [CmpA],
9267+
template: \`
9268+
@defer {
9269+
<cmp-a />
9270+
}
9271+
\`,
9272+
})
9273+
export class TestCmp {}
9274+
`);
9275+
9276+
env.driveMain();
9277+
9278+
const jsContents = env.getContents('test.js');
9279+
9280+
expect(jsContents).toContain('ɵɵdefer(1, 0, TestCmp_Defer_1_DepsFn)');
9281+
expect(jsContents).toContain('() => [import("./cmp-a").then(m => m.CmpA)]');
9282+
expect(jsContents).not.toContain('import { CmpA }');
9283+
});
9284+
9285+
it('should drop multiple imports of deferrable symbols from the same file', () => {
9286+
env.write('cmps.ts', `
9287+
import { Component } from '@angular/core';
9288+
9289+
@Component({
9290+
standalone: true,
9291+
selector: 'cmp-a',
9292+
template: 'CmpA!'
9293+
})
9294+
export class CmpA {}
9295+
9296+
@Component({
9297+
standalone: true,
9298+
selector: 'cmp-b',
9299+
template: 'CmpB!'
9300+
})
9301+
export class CmpB {}
9302+
`);
9303+
9304+
env.write('/test.ts', `
9305+
import { Component } from '@angular/core';
9306+
import { CmpA } from './cmps';
9307+
import { CmpB } from './cmps';
9308+
9309+
@Component({
9310+
selector: 'test-cmp',
9311+
standalone: true,
9312+
imports: [CmpA, CmpB],
9313+
template: \`
9314+
@defer {
9315+
<cmp-a />
9316+
<cmp-b />
9317+
}
9318+
\`,
9319+
})
9320+
export class TestCmp {}
9321+
`);
9322+
9323+
env.driveMain();
9324+
9325+
const jsContents = env.getContents('test.js');
9326+
9327+
expect(jsContents).toContain('ɵɵdefer(1, 0, TestCmp_Defer_1_DepsFn)');
9328+
expect(jsContents)
9329+
.toContain(
9330+
'() => [import("./cmps").then(m => m.CmpA), import("./cmps").then(m => m.CmpB)]');
9331+
expect(jsContents).not.toContain('import { CmpA }');
9332+
expect(jsContents).not.toContain('import { CmpB }');
9333+
});
91109334
});
91119335

91129336
it('should detect pipe used in the `when` trigger as an eager dependency', () => {
@@ -9327,7 +9551,7 @@ function allTests(os: string) {
93279551
}));
93289552
env.write(`test.ts`, `
93299553
import {Component} from '@angular/core';
9330-
9554+
93319555
@Component({
93329556
template: '...',
93339557
})
@@ -9350,7 +9574,7 @@ function allTests(os: string) {
93509574
}));
93519575
env.write(`test.ts`, `
93529576
import {Component} from '@angular/core';
9353-
9577+
93549578
@Component({
93559579
standalone: true,
93569580
template: '...',
@@ -9368,7 +9592,7 @@ function allTests(os: string) {
93689592
() => {
93699593
env.write(`test.ts`, `
93709594
import {Component} from '@angular/core';
9371-
9595+
93729596
@Component({
93739597
template: '...',
93749598
})

0 commit comments

Comments
 (0)