mirror of
https://github.com/angular/angular
synced 2026-05-24 09:28:37 +00:00
fix(compiler-cli): handle deferred blocks with shared dependencies correctly (#59926)
When the compiler analyzes the defer blocks in a component, it generates two sets of dependencies: ones specific for each block and others from all the deferred blocks within the component. The logic that combines all the defer block dependencies wasn't de-duplicating them which resulted in us producing `setClassMetadataAsync` calls where the callback can have multiple parameters with the same name. This was a problem both in full and partial compilation, but the latter was more visible, because Babel throws an error in such cases. These changes add some logic to de-duplicate the dependencies so that we produce valid code. Fixes #59922. PR Close #59926
This commit is contained in:
parent
b0266bda4a
commit
5cd26a9420
7 changed files with 200 additions and 3 deletions
|
|
@ -1888,22 +1888,32 @@ export class ComponentDecoratorHandler
|
|||
private resolveAllDeferredDependencies(
|
||||
resolution: Readonly<ComponentResolutionData>,
|
||||
): R3DeferPerComponentDependency[] {
|
||||
const seenDeps = new Set<ClassDeclaration>();
|
||||
const deferrableTypes: R3DeferPerComponentDependency[] = [];
|
||||
// Go over all dependencies of all defer blocks and update the value of
|
||||
// the `isDeferrable` flag and the `importPath` to reflect the current
|
||||
// state after visiting all components during the `resolve` phase.
|
||||
for (const [_, deps] of resolution.deferPerBlockDependencies) {
|
||||
for (const deferBlockDep of deps) {
|
||||
const importDecl =
|
||||
resolution.deferrableDeclToImportDecl.get(deferBlockDep.declaration.node) ?? null;
|
||||
const node = deferBlockDep.declaration.node;
|
||||
const importDecl = resolution.deferrableDeclToImportDecl.get(node) ?? null;
|
||||
if (importDecl !== null && this.deferredSymbolTracker.canDefer(importDecl)) {
|
||||
deferBlockDep.isDeferrable = true;
|
||||
deferBlockDep.importPath = (importDecl.moduleSpecifier as ts.StringLiteral).text;
|
||||
deferBlockDep.isDefaultImport = isDefaultImport(importDecl);
|
||||
deferrableTypes.push(deferBlockDep as R3DeferPerComponentDependency);
|
||||
|
||||
// The same dependency may be used across multiple deferred blocks. De-duplicate it
|
||||
// because it can throw off other logic further down the compilation pipeline.
|
||||
// Note that the logic above needs to run even if the dependency is seen before,
|
||||
// because the object literals are different between each block.
|
||||
if (!seenDeps.has(node)) {
|
||||
seenDeps.add(node);
|
||||
deferrableTypes.push(deferBlockDep as R3DeferPerComponentDependency);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return deferrableTypes;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1122,3 +1122,99 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDE
|
|||
****************************************************************************************************/
|
||||
export {};
|
||||
|
||||
/****************************************************************************************************
|
||||
* PARTIAL FILE: deferred_with_duplicate_external_dep_lazy.js
|
||||
****************************************************************************************************/
|
||||
import { Directive } from '@angular/core';
|
||||
import * as i0 from "@angular/core";
|
||||
export class DuplicateLazyDep {
|
||||
}
|
||||
DuplicateLazyDep.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: DuplicateLazyDep, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
||||
DuplicateLazyDep.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "0.0.0-PLACEHOLDER", type: DuplicateLazyDep, isStandalone: true, selector: "duplicate-lazy-dep", ngImport: i0 });
|
||||
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: DuplicateLazyDep, decorators: [{
|
||||
type: Directive,
|
||||
args: [{ selector: 'duplicate-lazy-dep' }]
|
||||
}] });
|
||||
|
||||
/****************************************************************************************************
|
||||
* PARTIAL FILE: deferred_with_duplicate_external_dep_lazy.d.ts
|
||||
****************************************************************************************************/
|
||||
import * as i0 from "@angular/core";
|
||||
export declare class DuplicateLazyDep {
|
||||
static ɵfac: i0.ɵɵFactoryDeclaration<DuplicateLazyDep, never>;
|
||||
static ɵdir: i0.ɵɵDirectiveDeclaration<DuplicateLazyDep, "duplicate-lazy-dep", never, {}, {}, never, never, true, never>;
|
||||
}
|
||||
|
||||
/****************************************************************************************************
|
||||
* PARTIAL FILE: deferred_with_duplicate_external_dep_other.js
|
||||
****************************************************************************************************/
|
||||
import { Directive } from '@angular/core';
|
||||
import * as i0 from "@angular/core";
|
||||
export class OtherLazyDep {
|
||||
}
|
||||
OtherLazyDep.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: OtherLazyDep, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
||||
OtherLazyDep.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "0.0.0-PLACEHOLDER", type: OtherLazyDep, isStandalone: true, selector: "other-lazy-dep", ngImport: i0 });
|
||||
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: OtherLazyDep, decorators: [{
|
||||
type: Directive,
|
||||
args: [{ selector: 'other-lazy-dep' }]
|
||||
}] });
|
||||
|
||||
/****************************************************************************************************
|
||||
* PARTIAL FILE: deferred_with_duplicate_external_dep_other.d.ts
|
||||
****************************************************************************************************/
|
||||
import * as i0 from "@angular/core";
|
||||
export declare class OtherLazyDep {
|
||||
static ɵfac: i0.ɵɵFactoryDeclaration<OtherLazyDep, never>;
|
||||
static ɵdir: i0.ɵɵDirectiveDeclaration<OtherLazyDep, "other-lazy-dep", never, {}, {}, never, never, true, never>;
|
||||
}
|
||||
|
||||
/****************************************************************************************************
|
||||
* PARTIAL FILE: deferred_with_duplicate_external_dep.js
|
||||
****************************************************************************************************/
|
||||
import { Component } from '@angular/core';
|
||||
import * as i0 from "@angular/core";
|
||||
export class MyApp {
|
||||
}
|
||||
MyApp.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyApp, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
||||
MyApp.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "0.0.0-PLACEHOLDER", type: MyApp, isStandalone: true, selector: "ng-component", ngImport: i0, template: `
|
||||
@defer {
|
||||
<duplicate-lazy-dep/>
|
||||
}
|
||||
|
||||
@defer {
|
||||
<duplicate-lazy-dep/>
|
||||
}
|
||||
|
||||
@defer {
|
||||
<other-lazy-dep/>
|
||||
}
|
||||
`, isInline: true, deferBlockDependencies: [() => [import("./deferred_with_duplicate_external_dep_lazy").then(m => m.DuplicateLazyDep)], () => [import("./deferred_with_duplicate_external_dep_lazy").then(m => m.DuplicateLazyDep)], () => [import("./deferred_with_duplicate_external_dep_other").then(m => m.OtherLazyDep)]] });
|
||||
i0.ɵɵngDeclareClassMetadataAsync({ minVersion: "18.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyApp, resolveDeferredDeps: () => [import("./deferred_with_duplicate_external_dep_lazy").then(m => m.DuplicateLazyDep), import("./deferred_with_duplicate_external_dep_other").then(m => m.OtherLazyDep)], resolveMetadata: (DuplicateLazyDep, OtherLazyDep) => ({ decorators: [{
|
||||
type: Component,
|
||||
args: [{
|
||||
template: `
|
||||
@defer {
|
||||
<duplicate-lazy-dep/>
|
||||
}
|
||||
|
||||
@defer {
|
||||
<duplicate-lazy-dep/>
|
||||
}
|
||||
|
||||
@defer {
|
||||
<other-lazy-dep/>
|
||||
}
|
||||
`,
|
||||
imports: [DuplicateLazyDep, OtherLazyDep],
|
||||
}]
|
||||
}], ctorParameters: null, propDecorators: null }) });
|
||||
|
||||
/****************************************************************************************************
|
||||
* PARTIAL FILE: deferred_with_duplicate_external_dep.d.ts
|
||||
****************************************************************************************************/
|
||||
import * as i0 from "@angular/core";
|
||||
export declare class MyApp {
|
||||
static ɵfac: i0.ɵɵFactoryDeclaration<MyApp, never>;
|
||||
static ɵcmp: i0.ɵɵComponentDeclaration<MyApp, "ng-component", never, {}, {}, never, never, true, never>;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -290,6 +290,25 @@
|
|||
"failureMessage": "Incorrect template"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "should handle a component with deferred blocks that share the same dependency",
|
||||
"inputFiles": [
|
||||
"deferred_with_duplicate_external_dep.ts",
|
||||
"deferred_with_duplicate_external_dep_lazy.ts",
|
||||
"deferred_with_duplicate_external_dep_other.ts"
|
||||
],
|
||||
"expectations": [
|
||||
{
|
||||
"files": [
|
||||
{
|
||||
"expected": "deferred_with_duplicate_external_dep_template.js",
|
||||
"generated": "deferred_with_duplicate_external_dep.js"
|
||||
}
|
||||
],
|
||||
"failureMessage": "Incorrect template"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,21 @@
|
|||
import {Component} from '@angular/core';
|
||||
import {DuplicateLazyDep} from './deferred_with_duplicate_external_dep_lazy';
|
||||
import {OtherLazyDep} from './deferred_with_duplicate_external_dep_other';
|
||||
|
||||
@Component({
|
||||
template: `
|
||||
@defer {
|
||||
<duplicate-lazy-dep/>
|
||||
}
|
||||
|
||||
@defer {
|
||||
<duplicate-lazy-dep/>
|
||||
}
|
||||
|
||||
@defer {
|
||||
<other-lazy-dep/>
|
||||
}
|
||||
`,
|
||||
imports: [DuplicateLazyDep, OtherLazyDep],
|
||||
})
|
||||
export class MyApp {}
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
import {Directive} from '@angular/core';
|
||||
|
||||
@Directive({selector: 'duplicate-lazy-dep'})
|
||||
export class DuplicateLazyDep {}
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
import {Directive} from '@angular/core';
|
||||
|
||||
@Directive({selector: 'other-lazy-dep'})
|
||||
export class OtherLazyDep {}
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
const MyApp_Defer_1_DepsFn = () => [import("./deferred_with_duplicate_external_dep_lazy").then(m => m.DuplicateLazyDep)];
|
||||
// NOTE: in linked tests there is one more loader here, because linked compilation doesn't have the ability to de-dupe identical functions.
|
||||
…
|
||||
const MyApp_Defer_7_DepsFn = () => [import("./deferred_with_duplicate_external_dep_other").then(m => m.OtherLazyDep)];
|
||||
|
||||
…
|
||||
|
||||
$r3$.ɵɵdefineComponent({
|
||||
…
|
||||
template: function MyApp_Template(rf, ctx) {
|
||||
if (rf & 1) {
|
||||
$r3$.ɵɵtemplate(0, MyApp_Defer_0_Template, 1, 0);
|
||||
$r3$.ɵɵdefer(1, 0, MyApp_Defer_1_DepsFn);
|
||||
$r3$.ɵɵdeferOnIdle();
|
||||
$r3$.ɵɵtemplate(3, MyApp_Defer_3_Template, 1, 0);
|
||||
// NOTE: does not check the function name, because linked compilation doesn't have the ability to de-dupe identical functions.
|
||||
$r3$.ɵɵdefer(4, 3, …);
|
||||
$r3$.ɵɵdeferOnIdle();
|
||||
$r3$.ɵɵtemplate(6, MyApp_Defer_6_Template, 1, 0);
|
||||
$r3$.ɵɵdefer(7, 6, MyApp_Defer_7_DepsFn);
|
||||
$r3$.ɵɵdeferOnIdle();
|
||||
}
|
||||
},
|
||||
encapsulation: 2
|
||||
});
|
||||
|
||||
…
|
||||
|
||||
(() => {
|
||||
(typeof ngDevMode === "undefined" || ngDevMode) && $r3$.ɵsetClassMetadataAsync(MyApp, () => [
|
||||
import("./deferred_with_duplicate_external_dep_lazy").then(m => m.DuplicateLazyDep),
|
||||
import("./deferred_with_duplicate_external_dep_other").then(m => m.OtherLazyDep)
|
||||
], (DuplicateLazyDep, OtherLazyDep) => {
|
||||
$r3$.ɵsetClassMetadata(MyApp, [{
|
||||
type: Component,
|
||||
args: [{
|
||||
template: …,
|
||||
// NOTE: there's a ... after the `imports`, because linked compilation produces a trailing comma while full compilation doesn't.
|
||||
imports: [DuplicateLazyDep, OtherLazyDep]…
|
||||
}]
|
||||
}], null, null);
|
||||
});
|
||||
})();
|
||||
Loading…
Reference in a new issue