Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(angular-output-target): outputType configuration #365

Merged
merged 10 commits into from
Jul 27, 2023
19 changes: 10 additions & 9 deletions packages/angular-output-target/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ export const config: Config = {
outputTargets: [
angularOutputTarget({
componentCorePackage: 'component-library',
outputType: 'component',
directivesProxyFile: '../component-library-angular/src/directives/proxies.ts',
directivesArrayFile: '../component-library-angular/src/directives/index.ts',
}),
Expand All @@ -40,12 +41,12 @@ export const config: Config = {

## Config Options

| Property | Description |
| ----------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `componentCorePackage` | The NPM package name of your Stencil component library. This package is used as a dependency for your Angular wrappers. |
| `directivesProxyFile` | The output file of all the component wrappers generated by the output target. This file path should point to a location within your Angular library/project. |
| `directivesArrayFile` | The output file of a constant of all the generated component wrapper classes. Used for easily declaring and exporting the generated components from an `NgModule`. This file path should point to a location within your Angular library/project. |
| `valueAccessorConfigs` | The configuration object for how individual web components behave with Angular control value accessors. |
| `excludeComponents` | An array of tag names to exclude from generating component wrappers for. This is helpful when have a custom framework implementation of a specific component or need to extend the base component wrapper behavior. |
| `includeImportCustomElements` | If `true`, the output target will import the custom element instance and register it with the Custom Elements Registry when the component is imported inside of a user's app. This can only be used with the [Custom Elements Bundle](https://stenciljs.com/docs/custom-elements) and will not work with lazy loaded components. |
| `customElementsDir` | This is the directory where the custom elements are imported from when using the [Custom Elements Bundle](https://stenciljs.com/docs/custom-elements). Defaults to the `components` directory. Only applies when `includeImportCustomElements` is `true`. |
| Property | Description |
| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `componentCorePackage` | The NPM package name of your Stencil component library. This package is used as a dependency for your Angular wrappers. |
| `directivesProxyFile` | The output file of all the component wrappers generated by the output target. This file path should point to a location within your Angular library/project. |
| `directivesArrayFile` | The output file of a constant of all the generated component wrapper classes. Used for easily declaring and exporting the generated components from an `NgModule`. This file path should point to a location within your Angular library/project. |
| `valueAccessorConfigs` | The configuration object for how individual web components behave with Angular control value accessors. |
| `excludeComponents` | An array of tag names to exclude from generating component wrappers for. This is helpful when have a custom framework implementation of a specific component or need to extend the base component wrapper behavior. |
| `outputType` | Specifies the type of output to be generated. It can take one of the following values: <br />1. `component`: Generates all the component wrappers to be declared on an Angular module. This option is required for Stencil projects using the `dist` hydrated output.<br /> 2. `scam`: Generates a separate Angular module for each component.<br /> 3. `standalone`: Generates standalone component wrappers.<br /> Both `scam` and `standalone` options are compatible with the `dist-custom-elements` output. Developers **must** set a `customElementsDir` in the output target config when using either 'scam' or 'standalone'.<br />Note: Please choose the appropriate `outputType` based on your project's requirements and the desired output structure. |
| `customElementsDir` | This is the directory where the custom elements are imported from when using the [Custom Elements Bundle](https://stenciljs.com/docs/custom-elements). Required to be set for `outputType: "scam"` or `outputType: "standalone"`. |
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And....there's my answer 😆

Original file line number Diff line number Diff line change
Expand Up @@ -266,7 +266,7 @@ describe('createComponentTypeDefinition()', () => {

describe('www build', () => {
it('creates a type definition', () => {
const definition = createComponentTypeDefinition('MyComponent', testEvents, '@ionic/core', false);
const definition = createComponentTypeDefinition('MyComponent', testEvents, '@ionic/core');

expect(definition).toEqual(
`import type { MyEvent as IMyComponentMyEvent } from '@ionic/core';
Expand Down Expand Up @@ -295,48 +295,14 @@ export declare interface MyComponent extends Components.MyComponent {
describe('custom elements output', () => {
describe('with a custom elements directory provided', () => {
it('creates a type definition', () => {
const definition = createComponentTypeDefinition(
'MyComponent',
testEvents,
'@ionic/core',
true,
'custom-elements'
);
const definition = createComponentTypeDefinition('MyComponent', testEvents, '@ionic/core', 'custom-elements');

expect(definition).toEqual(
`import type { MyEvent as IMyComponentMyEvent } from '@ionic/core/custom-elements';
import type { MyOtherEvent as IMyComponentMyOtherEvent } from '@ionic/core/custom-elements';
import type { MyDoclessEvent as IMyComponentMyDoclessEvent } from '@ionic/core/custom-elements';
import type { MyKebabEvent as IMyComponentMyKebabEvent } from '@ionic/core/custom-elements';

export declare interface MyComponent extends Components.MyComponent {
/**
* This is an example event. @Foo Bar
*/
myEvent: EventEmitter<CustomEvent<IMyComponentMyEvent>>;
/**
* This is the other event.
*/
myOtherEvent: EventEmitter<CustomEvent<IMyComponentMyOtherEvent>>;

myDoclessEvent: EventEmitter<CustomEvent<IMyComponentMyDoclessEvent>>;

'my-kebab-event': EventEmitter<CustomEvent<IMyComponentMyKebabEvent>>;
}`
);
});
});

describe('without a custom elements directory provided', () => {
it('creates a type definition', () => {
const definition = createComponentTypeDefinition('MyComponent', testEvents, '@ionic/core', true);

expect(definition).toEqual(
`import type { MyEvent as IMyComponentMyEvent } from '@ionic/core/components';
import type { MyOtherEvent as IMyComponentMyOtherEvent } from '@ionic/core/components';
import type { MyDoclessEvent as IMyComponentMyDoclessEvent } from '@ionic/core/components';
import type { MyKebabEvent as IMyComponentMyKebabEvent } from '@ionic/core/components';

export declare interface MyComponent extends Components.MyComponent {
/**
* This is an example event. @Foo Bar
Expand Down
56 changes: 27 additions & 29 deletions packages/angular-output-target/__tests__/output-angular.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ describe('generateProxies', () => {
const outputTarget: OutputTargetAngular = {
componentCorePackage: 'component-library',
directivesProxyFile: '../component-library-angular/src/proxies.ts',
outputType: 'component',
};

const finalText = generateProxies(components, pkgData, outputTarget, rootDir);
Expand Down Expand Up @@ -59,7 +60,7 @@ describe('generateProxies', () => {
},
],
},
] as ComponentCompilerMeta[];
] as unknown as ComponentCompilerMeta[];

const finalText = generateProxies(components, pkgData, outputTarget, rootDir);
expect(
Expand Down Expand Up @@ -100,7 +101,7 @@ describe('generateProxies', () => {
},
],
},
] as ComponentCompilerMeta[];
] as unknown as ComponentCompilerMeta[];

const finalText = generateProxies(components, pkgData, outputTarget, rootDir);
expect(
Expand All @@ -111,43 +112,25 @@ describe('generateProxies', () => {
expect(finalText.includes(`import { ProxyCmp } from './angular-component-lib/utils';`)).toBeTruthy();
});

describe('when includeSingleComponentAngularModules is true', () => {
it('should throw an error if includeImportCustomElements is false', () => {
describe('when outputType is scam', () => {
it('should throw an error if customElementsDir is undefined', () => {
const outputTarget: OutputTargetAngular = {
directivesProxyFile: '../component-library-angular/src/proxies.ts',
includeSingleComponentAngularModules: true,
includeImportCustomElements: false,
outputType: 'scam',
} 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.'
)
new Error('Generating single component Angular modules requires the "customElementsDir" option to be set.')
);
});

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

Expand All @@ -166,12 +149,12 @@ describe('generateProxies', () => {
});
});

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

Expand All @@ -189,4 +172,19 @@ describe('generateProxies', () => {
expect(finalText.includes('export class MyComponentModule')).toBeFalsy();
});
});

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

expect(() => {
generateProxies(components, pkgData, outputTarget, rootDir);
}).toThrow(
new Error('Generating standalone Angular components requires the "customElementsDir" option to be set.')
);
});
});
});
15 changes: 0 additions & 15 deletions packages/angular-output-target/__tests__/utils.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,6 @@ import type { MyOtherEvent as IMyComponentMyOtherEvent } from '@ionic/core';`
it('should create an import statement for each event', () => {
const imports = createComponentEventTypeImports('MyComponent', testEvents, {
componentCorePackage: '@ionic/core',
includeImportCustomElements: true,
customElementsDir: 'custom-elements',
});

Expand All @@ -70,20 +69,6 @@ import type { MyOtherEvent as IMyComponentMyOtherEvent } from '@ionic/core/custo
);
});
});

describe('without custom elements dir', () => {
it('should create an import statement for each event', () => {
const imports = createComponentEventTypeImports('MyComponent', testEvents, {
componentCorePackage: '@ionic/core',
includeImportCustomElements: true,
});

expect(imports).toEqual(
`import type { MyEvent as IMyComponentMyEvent } from '@ionic/core/components';
import type { MyOtherEvent as IMyComponentMyOtherEvent } from '@ionic/core/components';`
);
});
});
});
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -129,22 +129,19 @@ const createDocComment = (doc: CompilerJsDoc) => {
* @param tagNameAsPascal The tag name as PascalCase.
* @param events The events to generate the interface properties for.
* @param componentCorePackage The component core package.
* @param includeImportCustomElements Whether to include the import for the custom element definition.
* @param customElementsDir The custom elements directory.
* @returns The component interface type definition as a string.
*/
export const createComponentTypeDefinition = (
tagNameAsPascal: string,
events: readonly ComponentCompilerEvent[],
componentCorePackage: string,
includeImportCustomElements = false,
customElementsDir?: string
) => {
const publicEvents = events.filter((ev) => !ev.internal);

const eventTypeImports = createComponentEventTypeImports(tagNameAsPascal, publicEvents, {
componentCorePackage,
includeImportCustomElements,
customElementsDir,
});
const eventTypes = publicEvents.map((event) => {
Expand Down
Loading