fix(ivy): generate type references to a default import (#29146)
This commit refactors and expands ngtsc's support for generating imports of values from imports of types (this is used for example when importing a class referenced in a type annotation in a constructor). Previously, this logic handled "import {Foo} from" and "import * as foo from" style imports, but failed on imports of default values ("import Foo from"). This commit moves the type-to-value logic to a separate file and expands it to cover the default import case. Doing this also required augmenting the ImportManager to track default as well as non-default import generation. The APIs were made a little cleaner at the same time. PR Close #29146
This commit is contained in:

committed by
Kara Erickson

parent
37c5a26421
commit
b6f6b1178f
@ -23,9 +23,17 @@ export class EsmRenderer extends Renderer {
|
||||
/**
|
||||
* Add the imports at the top of the file
|
||||
*/
|
||||
addImports(output: MagicString, imports: {name: string; as: string;}[]): void {
|
||||
addImports(output: MagicString, imports: {
|
||||
specifier: string; qualifier: string; isDefault: boolean
|
||||
}[]): void {
|
||||
// The imports get inserted at the very top of the file.
|
||||
imports.forEach(i => { output.appendLeft(0, `import * as ${i.as} from '${i.name}';\n`); });
|
||||
imports.forEach(i => {
|
||||
if (!i.isDefault) {
|
||||
output.appendLeft(0, `import * as ${i.qualifier} from '${i.specifier}';\n`);
|
||||
} else {
|
||||
output.appendLeft(0, `import ${i.qualifier} from '${i.specifier}';\n`);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
addExports(output: MagicString, entryPointBasePath: string, exports: {
|
||||
|
@ -245,7 +245,9 @@ export abstract class Renderer {
|
||||
|
||||
protected abstract addConstants(output: MagicString, constants: string, file: ts.SourceFile):
|
||||
void;
|
||||
protected abstract addImports(output: MagicString, imports: {name: string, as: string}[]): void;
|
||||
protected abstract addImports(
|
||||
output: MagicString,
|
||||
imports: {specifier: string, qualifier: string, isDefault: boolean}[]): void;
|
||||
protected abstract addExports(output: MagicString, entryPointBasePath: string, exports: {
|
||||
identifier: string,
|
||||
from: string
|
||||
|
@ -115,13 +115,24 @@ describe('Esm2015Renderer', () => {
|
||||
it('should insert the given imports at the start of the source file', () => {
|
||||
const {renderer} = setup(PROGRAM);
|
||||
const output = new MagicString(PROGRAM.contents);
|
||||
renderer.addImports(
|
||||
output, [{name: '@angular/core', as: 'i0'}, {name: '@angular/common', as: 'i1'}]);
|
||||
renderer.addImports(output, [
|
||||
{specifier: '@angular/core', qualifier: 'i0', isDefault: false},
|
||||
{specifier: '@angular/common', qualifier: 'i1', isDefault: false}
|
||||
]);
|
||||
expect(output.toString()).toContain(`import * as i0 from '@angular/core';
|
||||
import * as i1 from '@angular/common';
|
||||
|
||||
/* A copyright notice */`);
|
||||
});
|
||||
|
||||
it('should insert a default import at the start of the source file', () => {
|
||||
const {renderer} = setup(PROGRAM);
|
||||
const output = new MagicString(PROGRAM.contents);
|
||||
renderer.addImports(output, [
|
||||
{specifier: 'test', qualifier: 'i0', isDefault: true},
|
||||
]);
|
||||
expect(output.toString()).toContain(`import i0 from 'test';`);
|
||||
});
|
||||
});
|
||||
|
||||
describe('addExports', () => {
|
||||
|
@ -152,13 +152,24 @@ describe('Esm5Renderer', () => {
|
||||
it('should insert the given imports at the start of the source file', () => {
|
||||
const {renderer} = setup(PROGRAM);
|
||||
const output = new MagicString(PROGRAM.contents);
|
||||
renderer.addImports(
|
||||
output, [{name: '@angular/core', as: 'i0'}, {name: '@angular/common', as: 'i1'}]);
|
||||
renderer.addImports(output, [
|
||||
{specifier: '@angular/core', qualifier: 'i0', isDefault: false},
|
||||
{specifier: '@angular/common', qualifier: 'i1', isDefault: false}
|
||||
]);
|
||||
expect(output.toString()).toContain(`import * as i0 from '@angular/core';
|
||||
import * as i1 from '@angular/common';
|
||||
|
||||
/* A copyright notice */`);
|
||||
});
|
||||
|
||||
it('should insert a default import at the start of the source file', () => {
|
||||
const {renderer} = setup(PROGRAM);
|
||||
const output = new MagicString(PROGRAM.contents);
|
||||
renderer.addImports(output, [
|
||||
{specifier: 'test', qualifier: 'i0', isDefault: true},
|
||||
]);
|
||||
expect(output.toString()).toContain(`import i0 from 'test';`);
|
||||
});
|
||||
});
|
||||
|
||||
describe('addExports', () => {
|
||||
|
@ -23,7 +23,8 @@ class TestRenderer extends Renderer {
|
||||
constructor(host: Esm2015ReflectionHost, isCore: boolean, bundle: EntryPointBundle) {
|
||||
super(host, isCore, bundle, '/src', '/dist');
|
||||
}
|
||||
addImports(output: MagicString, imports: {name: string, as: string}[]) {
|
||||
addImports(
|
||||
output: MagicString, imports: {specifier: string, qualifier: string, isDefault: boolean}[]) {
|
||||
output.prepend('\n// ADD IMPORTS\n');
|
||||
}
|
||||
addExports(output: MagicString, baseEntryPointPath: string, exports: {
|
||||
@ -171,7 +172,7 @@ A.ngComponentDef = ɵngcc0.ɵdefineComponent({ type: A, selectors: [["a"]], fact
|
||||
const addImportsSpy = renderer.addImports as jasmine.Spy;
|
||||
expect(addImportsSpy.calls.first().args[0].toString()).toEqual(RENDERED_CONTENTS);
|
||||
expect(addImportsSpy.calls.first().args[1]).toEqual([
|
||||
{name: '@angular/core', as: 'ɵngcc0'}
|
||||
{specifier: '@angular/core', qualifier: 'ɵngcc0', isDefault: false}
|
||||
]);
|
||||
});
|
||||
|
||||
@ -287,7 +288,9 @@ A.ngDirectiveDef = ɵngcc0.ɵdefineDirective({ type: A, selectors: [["", "a", ""
|
||||
expect(addDefinitionsSpy.calls.first().args[2])
|
||||
.toContain(`/*@__PURE__*/ ɵngcc0.setClassMetadata(`);
|
||||
const addImportsSpy = renderer.addImports as jasmine.Spy;
|
||||
expect(addImportsSpy.calls.first().args[1]).toEqual([{name: './r3_symbols', as: 'ɵngcc0'}]);
|
||||
expect(addImportsSpy.calls.first().args[1]).toEqual([
|
||||
{specifier: './r3_symbols', qualifier: 'ɵngcc0', isDefault: false}
|
||||
]);
|
||||
});
|
||||
|
||||
it('should render no imports in FESM bundles', () => {
|
||||
@ -502,9 +505,9 @@ A.ngDirectiveDef = ɵngcc0.ɵdefineDirective({ type: A, selectors: [["", "a", ""
|
||||
export declare function withProviders8(): (MyModuleWithProviders)&{ngModule:SomeModule};`);
|
||||
|
||||
expect(renderer.addImports).toHaveBeenCalledWith(jasmine.any(MagicString), [
|
||||
{name: './module', as: 'ɵngcc0'},
|
||||
{name: '@angular/core', as: 'ɵngcc1'},
|
||||
{name: 'some-library', as: 'ɵngcc2'},
|
||||
{specifier: './module', qualifier: 'ɵngcc0', isDefault: false},
|
||||
{specifier: '@angular/core', qualifier: 'ɵngcc1', isDefault: false},
|
||||
{specifier: 'some-library', qualifier: 'ɵngcc2', isDefault: false},
|
||||
]);
|
||||
|
||||
|
||||
|
Reference in New Issue
Block a user