Skip to content

Commit

Permalink
feat(angular-output-target): generate single component angular modules (
Browse files Browse the repository at this point in the history
  • Loading branch information
sean-perkins authored Mar 3, 2023
1 parent f20a778 commit ba1f285
Show file tree
Hide file tree
Showing 6 changed files with 145 additions and 1 deletion.
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { generateAngularModuleForComponent } from '../src/generate-angular-modules';

describe('generateAngularModuleForComponent()', () => {
it('should generate an Angular module for each component', () => {
const modules = generateAngularModuleForComponent('my-component');

expect(modules).toEqual(`@NgModule({
declarations: [MyComponent],
exports: [MyComponent]
})
export class MyComponentModule { }`);
});
});
79 changes: 79 additions & 0 deletions packages/angular-output-target/__tests__/output-angular.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,83 @@ describe('generateProxies', () => {
finalText.includes(`import { Components } from '../../angular-output-target/dist/types/components';`)
).toBeTruthy();
});

describe('when includeSingleComponentAngularModules is true', () => {
it('should throw an error if includeImportCustomElements is false', () => {
const outputTarget: OutputTargetAngular = {
directivesProxyFile: '../component-library-angular/src/proxies.ts',
includeSingleComponentAngularModules: true,
includeImportCustomElements: false,
} as OutputTargetAngular;

expect(() => {
generateProxies(components, pkgData, outputTarget, rootDir);
}).toThrow(
new Error(
'Generating single component Angular modules requires the "includeImportCustomElements" option to be set to true.'
)
);
});

it('should throw an error if includeImportCustomElements is undefined', () => {
const outputTarget: OutputTargetAngular = {
directivesProxyFile: '../component-library-angular/src/proxies.ts',
includeSingleComponentAngularModules: true,
} as OutputTargetAngular;

expect(() => {
generateProxies(components, pkgData, outputTarget, rootDir);
}).toThrow(
new Error(
'Generating single component Angular modules requires the "includeImportCustomElements" option to be set to true.'
)
);
});

it('should include an Angular module for each component', () => {
const outputTarget: OutputTargetAngular = {
directivesProxyFile: '../component-library-angular/src/proxies.ts',
includeImportCustomElements: true,
includeSingleComponentAngularModules: true,
componentCorePackage: '@ionic/core',
};

components.push({
tagName: 'my-component',
componentClassName: 'MyComponent',
properties: [],
virtualProperties: [],
events: [],
methods: [],
} as unknown as ComponentCompilerMeta);

const finalText = generateProxies(components, pkgData, outputTarget, rootDir);

expect(finalText.includes('export class MyComponentModule')).toBeTruthy();
});
});

describe('when includeSingleComponentAngularModules is false', () => {
it('should not include an Angular module for each component', () => {
const outputTarget: OutputTargetAngular = {
directivesProxyFile: '../component-library-angular/src/proxies.ts',
includeImportCustomElements: true,
includeSingleComponentAngularModules: false,
componentCorePackage: '@ionic/core',
};

components.push({
tagName: 'my-component',
componentClassName: 'MyComponent',
properties: [],
virtualProperties: [],
events: [],
methods: [],
} as unknown as ComponentCompilerMeta);

const finalText = generateProxies(components, pkgData, outputTarget, rootDir);

expect(finalText.includes('export class MyComponentModule')).toBeFalsy();
});
});
});
20 changes: 20 additions & 0 deletions packages/angular-output-target/src/generate-angular-modules.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { dashToPascalCase } from './utils';

/**
* Creates an Angular module declaration for a component wrapper.
* @param componentTagName The tag name of the Stencil component.
* @returns The Angular module declaration as a string.
*/
export const generateAngularModuleForComponent = (componentTagName: string) => {
const tagNameAsPascal = dashToPascalCase(componentTagName);
const componentClassName = `${tagNameAsPascal}`;
const moduleClassName = `${tagNameAsPascal}Module`;

const moduleDefinition = `@NgModule({
declarations: [${componentClassName}],
exports: [${componentClassName}]
})
export class ${moduleClassName} { }`;

return moduleDefinition;
};
22 changes: 21 additions & 1 deletion packages/angular-output-target/src/output-angular.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
import { createAngularComponentDefinition, createComponentTypeDefinition } from './generate-angular-component';
import { generateAngularDirectivesFile } from './generate-angular-directives-file';
import generateValueAccessors from './generate-value-accessors';
import { generateAngularModuleForComponent } from './generate-angular-modules';

export async function angularDirectiveProxyOutput(
compilerCtx: CompilerCtx,
Expand Down Expand Up @@ -66,6 +67,7 @@ export function generateProxies(
const distTypesDir = path.dirname(pkgData.types);
const dtsFilePath = path.join(rootDir, distTypesDir, GENERATED_DTS);
const componentsTypeFile = relativeImport(outputTarget.directivesProxyFile, dtsFilePath, '.d.ts');
const includeSingleComponentAngularModules = outputTarget.includeSingleComponentAngularModules ?? false;

/**
* The collection of named imports from @angular/core.
Expand All @@ -84,6 +86,10 @@ export function generateProxies(
*/
const componentLibImports = ['ProxyCmp', 'proxyOutputs'];

if (includeSingleComponentAngularModules) {
angularCoreImports.push('NgModule');
}

const imports = `/* tslint:disable */
/* auto-generated angular directive proxies */
${createImportStatement(angularCoreImports, '@angular/core')}
Expand Down Expand Up @@ -129,6 +135,15 @@ ${createImportStatement(componentLibImports, './angular-component-lib/utils')}\n
sourceImports = cmpImports.join('\n');
}

if (includeSingleComponentAngularModules) {
// Generating Angular modules is only supported in the dist-custom-elements build
if (!outputTarget.includeImportCustomElements) {
throw new Error(
'Generating single component Angular modules requires the "includeImportCustomElements" option to be set to true.'
);
}
}

const proxyFileOutput = [];

const filterInternalProps = (prop: { name: string; internal: boolean }) => !prop.internal;
Expand Down Expand Up @@ -166,7 +181,8 @@ ${createImportStatement(componentLibImports, './angular-component-lib/utils')}\n
/**
* For each component, we need to generate:
* 1. The @Component decorated class
* 2. The component interface (using declaration merging for types).
* 2. Optionally the @NgModule decorated class (if includeSingleComponentAngularModules is true)
* 3. The component interface (using declaration merging for types).
*/
const componentDefinition = createAngularComponentDefinition(
cmpMeta.tagName,
Expand All @@ -175,6 +191,7 @@ ${createImportStatement(componentLibImports, './angular-component-lib/utils')}\n
methods,
includeImportCustomElements
);
const moduleDefinition = generateAngularModuleForComponent(cmpMeta.tagName);
const componentTypeDefinition = createComponentTypeDefinition(
tagNameAsPascal,
cmpMeta.events,
Expand All @@ -184,6 +201,9 @@ ${createImportStatement(componentLibImports, './angular-component-lib/utils')}\n
);

proxyFileOutput.push(componentDefinition, '\n');
if (includeSingleComponentAngularModules) {
proxyFileOutput.push(moduleDefinition, '\n');
}
proxyFileOutput.push(componentTypeDefinition, '\n');
}

Expand Down
6 changes: 6 additions & 0 deletions packages/angular-output-target/src/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,5 +42,11 @@ export function normalizeOutputTarget(config: Config, outputTarget: OutputTarget
results.directivesArrayFile = normalizePath(path.join(config.rootDir, outputTarget.directivesArrayFile));
}

if (outputTarget.includeSingleComponentAngularModules !== undefined) {
console.warn(
'**Experimental**: includeSingleComponentAngularModules is a developer preview feature and may change or be removed in the future.'
);
}

return results;
}
6 changes: 6 additions & 0 deletions packages/angular-output-target/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,12 @@ export interface OutputTargetAngular {
excludeComponents?: string[];
includeImportCustomElements?: boolean;
customElementsDir?: string;
/**
* @experimental
*
* `true` to generate a single component Angular module for each component.
*/
includeSingleComponentAngularModules?: boolean;
}

export type ValueAccessorTypes = 'text' | 'radio' | 'select' | 'number' | 'boolean';
Expand Down

0 comments on commit ba1f285

Please sign in to comment.