mirror of
https://github.com/angular/angular.git
synced 2026-09-14 13:54:52 +08:00
refactor(compiler): deduplicate explicitly deferred types to prevent syntax errors
When `@Component.deferredImports` maps blocks to arrays of dependencies, an explicitly
deferred dependency might be defined in multiple blocks (e.g. `block1: [CmpA], block2: [CmpA]`).
Previously, these were appended to the `explicitlyDeferredTypes` array without deduplication.
When generating the `setClassMetadataAsync` wrapper for development mode, the compiler
used this array to generate callback parameters for dynamic imports. This resulted in
duplicate parameter names in the callback signature `(CmpA, CmpA) => { ... }`, which
causes an `Uncaught SyntaxError` when V8 parses the module in strict mode.
This commit deduplicates `explicitlyDeferredTypes` in the `ngtsc` component handler, and
adds a secondary deduplication check in the `r3_class_metadata_compiler` generator to
ensure duplicate parameter names are never emitted.
This commit is contained in:
committed by
Matthew Beck
parent
ad22e89d4d
commit
4c55a36a87
@@ -1005,7 +1005,13 @@ export class ComponentDecoratorHandler implements DecoratorHandler<
|
||||
const blockDeps = this.collectExplicitlyDeferredSymbols(node, initializer);
|
||||
explicitlyDeferredTypesByBlock.set(blockName, blockDeps);
|
||||
explicitlyDeferredTypes ??= [];
|
||||
explicitlyDeferredTypes.push(...blockDeps);
|
||||
for (const dep of blockDeps) {
|
||||
if (
|
||||
!explicitlyDeferredTypes.some((existing) => existing.symbolName === dep.symbolName)
|
||||
) {
|
||||
explicitlyDeferredTypes.push(dep);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2774,6 +2774,56 @@ runInEachFileSystem(() => {
|
||||
const diags = env.driveDiagnostics();
|
||||
expect(diags.length).toBe(0);
|
||||
});
|
||||
|
||||
it('should not duplicate explicitly deferred dependencies in generated setClassMetadataAsync', () => {
|
||||
env.write(
|
||||
'dirs.ts',
|
||||
`
|
||||
import { Directive } from '@angular/core';
|
||||
@Directive({ selector: '[dirA]' })
|
||||
export class DirA {}
|
||||
`,
|
||||
);
|
||||
|
||||
env.write(
|
||||
'/test.ts',
|
||||
`
|
||||
import { Component } from '@angular/core';
|
||||
import { DirA } from './dirs';
|
||||
|
||||
@Component({
|
||||
selector: 'test-cmp',
|
||||
// @ts-ignore
|
||||
deferredImports: {
|
||||
blockA: [DirA],
|
||||
blockB: [DirA],
|
||||
},
|
||||
template: \`
|
||||
@defer (name blockA) {
|
||||
<div dirA></div>
|
||||
}
|
||||
@defer (name blockB) {
|
||||
<div dirA></div>
|
||||
}
|
||||
\`,
|
||||
})
|
||||
export class TestCmp {}
|
||||
`,
|
||||
);
|
||||
|
||||
env.driveMain();
|
||||
const jsContents = env.getContents('test.js');
|
||||
|
||||
// Make sure we do not generate a duplicate parameter like (DirA, DirA)
|
||||
expect(jsContents).not.toContain('DirA, DirA');
|
||||
|
||||
// Ensure that setClassMetadataAsync has exactly one parameter for DirA
|
||||
expect(cleanNewLines(jsContents)).toContain(
|
||||
'i0.ɵsetClassMetadataAsync(TestCmp, ' +
|
||||
'() => [/* @ts-ignore */ import("./dirs").then(m => m.DirA)], ' +
|
||||
'DirA => { i0.ɵsetClassMetadata(TestCmp',
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -85,10 +85,16 @@ export function compileComponentClassMetadata(
|
||||
return compileClassMetadata(metadata);
|
||||
}
|
||||
|
||||
const uniqueDeps = new Map<string, R3DeferPerComponentDependency>();
|
||||
for (const dep of dependencies) {
|
||||
uniqueDeps.set(dep.symbolName, dep);
|
||||
}
|
||||
const dedupedDependencies = Array.from(uniqueDeps.values());
|
||||
|
||||
return internalCompileSetClassMetadataAsync(
|
||||
metadata,
|
||||
dependencies.map((dep) => new o.FnParam(dep.symbolName, o.DYNAMIC_TYPE)),
|
||||
compileComponentMetadataAsyncResolver(dependencies),
|
||||
dedupedDependencies.map((dep) => new o.FnParam(dep.symbolName, o.DYNAMIC_TYPE)),
|
||||
compileComponentMetadataAsyncResolver(dedupedDependencies),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user